Merge branch 'master' into nymkappa/bugfix/isp-chart-color
This commit is contained in:
@@ -19,31 +19,31 @@
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Fee rate</td>
|
||||
<td>
|
||||
{{ channel.fee_rate }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
||||
{{ channel.fee_rate ?? '-' }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Base fee</td>
|
||||
<td>
|
||||
<app-sats [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
|
||||
<app-sats [valueOverride]="!channel.base_fee_mtokens ? '- ' : undefined" [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Min HTLC</td>
|
||||
<td>
|
||||
<app-sats [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
||||
<app-sats [valueOverride]="!channel.min_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Max HTLC</td>
|
||||
<td>
|
||||
<app-sats [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
||||
<app-sats [valueOverride]="!channel.max_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Timelock delta</td>
|
||||
<td>
|
||||
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta }"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -25,13 +25,17 @@
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Created</td>
|
||||
<td i18n="lightning.created">Created</td>
|
||||
<td><app-timestamp [dateString]="channel.created"></app-timestamp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="address.total-sent">Last update</td>
|
||||
<tr *ngIf="channel.status !== 2">
|
||||
<td i18n="lightning.last-update">Last update</td>
|
||||
<td><app-timestamp [dateString]="channel.updated_at"></app-timestamp></td>
|
||||
</tr>
|
||||
<tr *ngIf="channel.status === 2">
|
||||
<td i18n="lightning.closing_date">Closing date</td>
|
||||
<td><app-timestamp [dateString]="channel.closing_date"></app-timestamp></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { Observable, of, zip } from 'rxjs';
|
||||
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { catchError, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { IChannel } from 'src/app/interfaces/node-api.interface';
|
||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
@@ -31,9 +31,11 @@ export class ChannelComponent implements OnInit {
|
||||
.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
this.error = null;
|
||||
this.seoService.setTitle(`Channel: ${params.get('short_id')}`);
|
||||
return this.lightningApiService.getChannel$(params.get('short_id'))
|
||||
.pipe(
|
||||
tap((value) => {
|
||||
this.seoService.setTitle(`Channel: ${value.short_id}`);
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.error = err;
|
||||
return of(null);
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
<th class="alias text-left" i18n="nodes.alias">Node Alias</th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="channels.transaction"> </th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="status">Status</th>
|
||||
<th class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
||||
<th *ngIf="status !== 'closed'" class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
||||
<th *ngIf="status === 'closed'" class="channels text-left d-none d-md-table-cell" i18n="channels.closing_date">Closing date</th>
|
||||
<th class="capacity text-right d-none d-md-table-cell" i18n="nodes.capacity">Capacity</th>
|
||||
<th class="capacity text-right" i18n="channels.id">Channel ID</th>
|
||||
</thead>
|
||||
@@ -71,9 +72,12 @@
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="capacity text-left d-none d-md-table-cell">
|
||||
<td *ngIf="status !== 'closed'" class="capacity text-left d-none d-md-table-cell">
|
||||
{{ channel.fee_rate }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
||||
</td>
|
||||
<td *ngIf="status === 'closed'" class="capacity text-left d-none d-md-table-cell">
|
||||
<app-timestamp [unixTime]="channel.closing_date"></app-timestamp>
|
||||
</td>
|
||||
<td class="capacity text-right d-none d-md-table-cell">
|
||||
<app-amount *ngIf="channel.capacity > 100000000; else smallchannel" [satoshis]="channel.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||
<ng-template #smallchannel>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
<div class="card-header">
|
||||
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
||||
<span i18n="lightning.nodes-heatmap">Lightning nodes world heat map</span>
|
||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px">
|
||||
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true" (click)="onSaveChart()"></fa-icon>
|
||||
</button>
|
||||
<span i18n="lightning.nodes-world-map">Lightning nodes world map</span>
|
||||
</div>
|
||||
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { mempoolFeeColors } from 'src/app/app.constants';
|
||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { combineLatest, Observable, tap } from 'rxjs';
|
||||
import { Observable, tap, zip } from 'rxjs';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { EChartsOption, registerMap } from 'echarts';
|
||||
import { download } from 'src/app/shared/graphs.utils';
|
||||
import { lerpColor } from 'src/app/shared/graphs.utils';
|
||||
import { Router } from '@angular/router';
|
||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe';
|
||||
import { getFlagEmoji } from 'src/app/shared/common.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nodes-map',
|
||||
@@ -16,7 +17,7 @@ import { StateService } from 'src/app/services/state.service';
|
||||
styleUrls: ['./nodes-map.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NodesMap implements OnInit, OnDestroy {
|
||||
export class NodesMap implements OnInit {
|
||||
observable$: Observable<any>;
|
||||
|
||||
chartInstance = undefined;
|
||||
@@ -26,44 +27,52 @@ export class NodesMap implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
private seoService: SeoService,
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
private assetsService: AssetsService,
|
||||
private router: Router,
|
||||
private zone: NgZone,
|
||||
private amountShortenerPipe: AmountShortenerPipe
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`Lightning nodes world map`);
|
||||
|
||||
this.observable$ = combineLatest([
|
||||
this.observable$ = zip(
|
||||
this.assetsService.getWorldMapJson$,
|
||||
this.apiService.getNodesPerCountry()
|
||||
]).pipe(tap((data) => {
|
||||
this.apiService.getWorldNodes$()
|
||||
).pipe(tap((data) => {
|
||||
registerMap('world', data[0]);
|
||||
|
||||
const countries = [];
|
||||
let max = 0;
|
||||
for (const country of data[1]) {
|
||||
countries.push({
|
||||
name: country.name.en,
|
||||
value: country.count,
|
||||
iso: country.iso.toLowerCase(),
|
||||
});
|
||||
max = Math.max(max, country.count);
|
||||
const nodes: any[] = [];
|
||||
console.log(data[1].nodes[0]);
|
||||
for (const node of data[1].nodes) {
|
||||
// We add a bit of noise so nodes at the same location are not all
|
||||
// on top of each other
|
||||
const random = Math.random() * 2 * Math.PI;
|
||||
const random2 = Math.random() * 0.01;
|
||||
nodes.push([
|
||||
node[0] + random2 * Math.cos(random),
|
||||
node[1] + random2 * Math.sin(random),
|
||||
node[4], // Liquidity
|
||||
node[3], // Alias
|
||||
node[2], // Public key
|
||||
node[5], // Channels
|
||||
node[6].en, // Country
|
||||
node[7], // ISO Code
|
||||
]);
|
||||
}
|
||||
|
||||
this.prepareChartOptions(countries, max);
|
||||
this.prepareChartOptions(nodes, data[1].maxLiquidity);
|
||||
}));
|
||||
}
|
||||
|
||||
prepareChartOptions(countries, max) {
|
||||
prepareChartOptions(nodes, maxLiquidity) {
|
||||
let title: object;
|
||||
if (countries.length === 0) {
|
||||
if (nodes.length === 0) {
|
||||
title = {
|
||||
textStyle: {
|
||||
color: 'grey',
|
||||
@@ -76,53 +85,80 @@ export class NodesMap implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.chartOptions = {
|
||||
title: countries.length === 0 ? title : undefined,
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
silent: false,
|
||||
title: title ?? undefined,
|
||||
tooltip: {},
|
||||
geo: {
|
||||
animation: false,
|
||||
silent: true,
|
||||
center: [0, 5],
|
||||
zoom: 1.3,
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: function(country) {
|
||||
if (country.data === undefined) {
|
||||
return `<b style="color: white">${country.name}<br>0 nodes</b><br>`;
|
||||
} else {
|
||||
return `<b style="color: white">${country.data.name}<br>${country.data.value} nodes</b><br>`;
|
||||
}
|
||||
map: 'world',
|
||||
roam: true,
|
||||
itemStyle: {
|
||||
borderColor: 'black',
|
||||
color: '#272b3f'
|
||||
},
|
||||
scaleLimit: {
|
||||
min: 1.3,
|
||||
max: 100000,
|
||||
},
|
||||
emphasis: {
|
||||
disabled: true,
|
||||
}
|
||||
},
|
||||
visualMap: {
|
||||
left: 'right',
|
||||
show: true,
|
||||
min: 1,
|
||||
max: max,
|
||||
text: ['High', 'Low'],
|
||||
calculable: true,
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
inRange: {
|
||||
color: mempoolFeeColors.map(color => `#${color}`),
|
||||
},
|
||||
},
|
||||
series: {
|
||||
type: 'map',
|
||||
map: 'world',
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
series: [
|
||||
{
|
||||
large: false,
|
||||
type: 'scatter',
|
||||
data: nodes,
|
||||
coordinateSystem: 'geo',
|
||||
geoIndex: 0,
|
||||
progressive: 500,
|
||||
symbolSize: function (params) {
|
||||
return 10 * Math.pow(params[2] / maxLiquidity, 0.2) + 3;
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
show: true,
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
textStyle: {
|
||||
color: '#b1b1b1',
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: (value) => {
|
||||
const data = value.data;
|
||||
const alias = data[3].length > 0 ? data[3] : data[4].slice(0, 20);
|
||||
const liquidity = data[2] >= 100000000 ?
|
||||
`${this.amountShortenerPipe.transform(data[2] / 100000000)} BTC` :
|
||||
`${this.amountShortenerPipe.transform(data[2], 2)} sats`;
|
||||
|
||||
return `
|
||||
<b style="color: white">${alias}</b><br>
|
||||
${liquidity}<br>
|
||||
${data[5]} channels<br>
|
||||
${getFlagEmoji(data[7])} ${data[6]}
|
||||
`;
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
areaColor: '#FDD835',
|
||||
}
|
||||
color: function (params) {
|
||||
return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[2] / maxLiquidity, 0.2))}`;
|
||||
},
|
||||
opacity: 1,
|
||||
borderColor: 'black',
|
||||
borderWidth: 0,
|
||||
},
|
||||
blendMode: 'lighter',
|
||||
zlevel: 2,
|
||||
},
|
||||
data: countries,
|
||||
itemStyle: {
|
||||
areaColor: '#5A6A6D'
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,30 +170,16 @@ export class NodesMap implements OnInit, OnDestroy {
|
||||
this.chartInstance = ec;
|
||||
|
||||
this.chartInstance.on('click', (e) => {
|
||||
if (e.data && e.data.value > 0) {
|
||||
if (e.data) {
|
||||
this.zone.run(() => {
|
||||
const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/nodes/country/${e.data.iso}`);
|
||||
const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/node/${e.data[4]}`);
|
||||
this.router.navigate([url]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSaveChart() {
|
||||
// @ts-ignore
|
||||
const prevBottom = this.chartOptions.grid.bottom;
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = 30;
|
||||
this.chartOptions.backgroundColor = '#11131f';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
download(this.chartInstance.getDataURL({
|
||||
pixelRatio: 2,
|
||||
excludeComponents: ['dataZoom'],
|
||||
}), `lightning-nodes-heatmap-clearnet-${Math.round(now.getTime() / 1000)}.svg`);
|
||||
// @ts-ignore
|
||||
this.chartOptions.grid.bottom = prevBottom;
|
||||
this.chartOptions.backgroundColor = 'none';
|
||||
this.chartInstance.setOption(this.chartOptions);
|
||||
this.chartInstance.on('georoam', (e) => {
|
||||
this.chartInstance.resize();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class NodesPerCountryChartComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`Lightning nodes per country`);
|
||||
|
||||
this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry()
|
||||
this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$()
|
||||
.pipe(
|
||||
map(data => {
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
|
||||
<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>
|
||||
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||
</thead>
|
||||
<tbody *ngIf="nodes$ | async as nodes">
|
||||
|
||||
<tbody *ngIf="nodes$ | async as nodes; else skeleton">
|
||||
<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>
|
||||
@@ -39,6 +41,32 @@
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-country'"></app-geolocation>
|
||||
</td>
|
||||
</tbody>
|
||||
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonLines">
|
||||
<td class="alias text-left text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="timestamp-first text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="timestamp-update text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="capacity text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="city text-right text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -16,11 +16,17 @@ export class NodesPerCountry implements OnInit {
|
||||
nodes$: Observable<any>;
|
||||
country: {name: string, flag: string};
|
||||
|
||||
skeletonLines: number[] = [];
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private route: ActivatedRoute,
|
||||
) { }
|
||||
) {
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
this.skeletonLines.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
|
||||
|
||||
@@ -47,7 +47,9 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.seoService.setTitle($localize`Lightning nodes per ISP`);
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`Lightning nodes per ISP`);
|
||||
}
|
||||
|
||||
this.nodesPerAsObservable$ = combineLatest([
|
||||
this.sortBySubject.pipe(startWith(true)),
|
||||
@@ -105,7 +107,7 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
}
|
||||
|
||||
generateChartSerieData(ispRanking): PieSeriesOption[] {
|
||||
let shareThreshold = 0.5;
|
||||
let shareThreshold = 0.4;
|
||||
if (this.widget && isMobile() || isMobile()) {
|
||||
shareThreshold = 1;
|
||||
} else if (this.widget) {
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
|
||||
<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>
|
||||
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||
</thead>
|
||||
<tbody *ngIf="nodes$ | async as nodes">
|
||||
|
||||
<tbody *ngIf="nodes$ | async as nodes; else skeleton">
|
||||
<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>
|
||||
@@ -36,6 +38,32 @@
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tbody>
|
||||
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonLines">
|
||||
<td class="alias text-left text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="timestamp-first text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="timestamp-update text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="capacity text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="city text-right text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,11 +15,17 @@ export class NodesPerISP implements OnInit {
|
||||
nodes$: Observable<any>;
|
||||
isp: {name: string, id: number};
|
||||
|
||||
skeletonLines: number[] = [];
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private route: ActivatedRoute,
|
||||
) { }
|
||||
) {
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
this.skeletonLines.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
{{ node?.city?.en ?? '-' }}
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { IOldestNodes } from '../../../interfaces/node-api.interface';
|
||||
import { LightningApiService } from '../../lightning-api.service';
|
||||
|
||||
@@ -15,19 +17,38 @@ export class OldestNodes implements OnInit {
|
||||
oldestNodes$: Observable<IOldestNodes[]>;
|
||||
skeletonRows: number[] = [];
|
||||
|
||||
constructor(private apiService: LightningApiService) {}
|
||||
constructor(
|
||||
private apiService: LightningApiService,
|
||||
private seoService: SeoService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`Oldest lightning nodes`);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= (this.widget ? 10 : 100); ++i) {
|
||||
this.skeletonRows.push(i);
|
||||
}
|
||||
|
||||
if (this.widget === false) {
|
||||
this.oldestNodes$ = this.apiService.getOldestNodes$();
|
||||
this.oldestNodes$ = this.apiService.getOldestNodes$().pipe(
|
||||
map((ranking) => {
|
||||
for (const i in ranking) {
|
||||
ranking[i].geolocation = <GeolocationData>{
|
||||
country: ranking[i].country?.en,
|
||||
city: ranking[i].city?.en,
|
||||
subdivision: ranking[i].subdivision?.en,
|
||||
iso: ranking[i].iso_code,
|
||||
};
|
||||
}
|
||||
return ranking;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.oldestNodes$ = this.apiService.getOldestNodes$().pipe(
|
||||
map((nodes: IOldestNodes[]) => {
|
||||
return nodes.slice(0, 10);
|
||||
return nodes.slice(0, 7);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
{{ node?.city?.en ?? '-' }}
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { INodesRanking, ITopNodesPerCapacity } from 'src/app/interfaces/node-api.interface';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { isMobile } from 'src/app/shared/common.utils';
|
||||
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
|
||||
import { LightningApiService } from '../../lightning-api.service';
|
||||
|
||||
@Component({
|
||||
@@ -17,15 +19,34 @@ export class TopNodesPerCapacity implements OnInit {
|
||||
topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>;
|
||||
skeletonRows: number[] = [];
|
||||
|
||||
constructor(private apiService: LightningApiService) {}
|
||||
constructor(
|
||||
private apiService: LightningApiService,
|
||||
private seoService: SeoService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`Liquidity Ranking`);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||
this.skeletonRows.push(i);
|
||||
}
|
||||
|
||||
if (this.widget === false) {
|
||||
this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$();
|
||||
this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$().pipe(
|
||||
map((ranking) => {
|
||||
for (const i in ranking) {
|
||||
ranking[i].geolocation = <GeolocationData>{
|
||||
country: ranking[i].country?.en,
|
||||
city: ranking[i].city?.en,
|
||||
subdivision: ranking[i].subdivision?.en,
|
||||
iso: ranking[i].iso_code,
|
||||
};
|
||||
}
|
||||
return ranking;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.topNodesPerCapacity$ = this.nodes$.pipe(
|
||||
map((ranking) => {
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
{{ node?.city?.en ?? '-' }}
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { INodesRanking, ITopNodesPerChannels } from 'src/app/interfaces/node-api.interface';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { isMobile } from 'src/app/shared/common.utils';
|
||||
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
|
||||
import { LightningApiService } from '../../lightning-api.service';
|
||||
|
||||
@Component({
|
||||
@@ -17,15 +19,34 @@ export class TopNodesPerChannels implements OnInit {
|
||||
topNodesPerChannels$: Observable<ITopNodesPerChannels[]>;
|
||||
skeletonRows: number[] = [];
|
||||
|
||||
constructor(private apiService: LightningApiService) {}
|
||||
constructor(
|
||||
private apiService: LightningApiService,
|
||||
private seoService: SeoService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`Connectivity Ranking`);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||
this.skeletonRows.push(i);
|
||||
}
|
||||
|
||||
if (this.widget === false) {
|
||||
this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$();
|
||||
this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$().pipe(
|
||||
map((ranking) => {
|
||||
for (const i in ranking) {
|
||||
ranking[i].geolocation = <GeolocationData>{
|
||||
country: ranking[i].country?.en,
|
||||
city: ranking[i].city?.en,
|
||||
subdivision: ranking[i].subdivision?.en,
|
||||
iso: ranking[i].iso_code,
|
||||
};
|
||||
}
|
||||
return ranking;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.topNodesPerChannels$ = this.nodes$.pipe(
|
||||
map((ranking) => {
|
||||
|
||||
Reference in New Issue
Block a user