diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index 2d12bc2e7..260efcafe 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -3,14 +3,10 @@ import { Routes, RouterModule } from '@angular/router';
import { AppPreloadingStrategy } from './app.preloading-strategy'
import { StartComponent } from './components/start/start.component';
import { TransactionComponent } from './components/transaction/transaction.component';
-import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
import { BlockComponent } from './components/block/block.component';
import { BlockAuditComponent } from './components/block-audit/block-audit.component';
-import { BlockPreviewComponent } from './components/block/block-preview.component';
import { AddressComponent } from './components/address/address.component';
-import { AddressPreviewComponent } from './components/address/address-preview.component';
import { MasterPageComponent } from './components/master-page/master-page.component';
-import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
import { AboutComponent } from './components/about/about.component';
import { StatusViewComponent } from './components/status-view/status-view.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
@@ -346,61 +342,18 @@ let routes: Routes = [
},
{
path: 'preview',
- component: MasterPagePreviewComponent,
children: [
{
- path: 'block/:id',
- component: BlockPreviewComponent
+ path: '',
+ loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
{
- path: 'testnet/block/:id',
- component: BlockPreviewComponent
+ path: 'testnet',
+ loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
{
- path: 'signet/block/:id',
- component: BlockPreviewComponent
- },
- {
- path: 'address/:id',
- children: [],
- component: AddressPreviewComponent
- },
- {
- path: 'testnet/address/:id',
- children: [],
- component: AddressPreviewComponent
- },
- {
- path: 'signet/address/:id',
- children: [],
- component: AddressPreviewComponent
- },
- {
- path: 'tx/:id',
- children: [],
- component: TransactionPreviewComponent
- },
- {
- path: 'testnet/tx/:id',
- children: [],
- component: TransactionPreviewComponent
- },
- {
- path: 'signet/tx/:id',
- children: [],
- component: TransactionPreviewComponent
- },
- {
- path: 'lightning',
- loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
- },
- {
- path: 'testnet/lightning',
- loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
- },
- {
- path: 'signet/lightning',
- loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
+ path: 'signet',
+ loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
],
},
@@ -643,35 +596,14 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
},
{
path: 'preview',
- component: MasterPagePreviewComponent,
children: [
{
- path: 'block/:id',
- component: BlockPreviewComponent
+ path: '',
+ loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
{
- path: 'testnet/block/:id',
- component: BlockPreviewComponent
- },
- {
- path: 'address/:id',
- children: [],
- component: AddressPreviewComponent
- },
- {
- path: 'testnet/address/:id',
- children: [],
- component: AddressPreviewComponent
- },
- {
- path: 'tx/:id',
- children: [],
- component: TransactionPreviewComponent
- },
- {
- path: 'testnet/tx/:id',
- children: [],
- component: TransactionPreviewComponent
+ path: 'testnet',
+ loadChildren: () => import('./previews.module').then(m => m.PreviewsModule)
},
],
},
diff --git a/frontend/src/app/lightning/lightning-previews.module.ts b/frontend/src/app/lightning/lightning-previews.module.ts
index 4d5d6cee9..0400acc55 100644
--- a/frontend/src/app/lightning/lightning-previews.module.ts
+++ b/frontend/src/app/lightning/lightning-previews.module.ts
@@ -8,10 +8,12 @@ import { LightningApiService } from './lightning-api.service';
import { NodePreviewComponent } from './node/node-preview.component';
import { LightningPreviewsRoutingModule } from './lightning-previews.routing.module';
import { ChannelPreviewComponent } from './channel/channel-preview.component';
+import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component';
@NgModule({
declarations: [
NodePreviewComponent,
ChannelPreviewComponent,
+ NodesPerISPPreview,
],
imports: [
CommonModule,
diff --git a/frontend/src/app/lightning/lightning-previews.routing.module.ts b/frontend/src/app/lightning/lightning-previews.routing.module.ts
index 69de2aadf..11250214d 100644
--- a/frontend/src/app/lightning/lightning-previews.routing.module.ts
+++ b/frontend/src/app/lightning/lightning-previews.routing.module.ts
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NodePreviewComponent } from './node/node-preview.component';
import { ChannelPreviewComponent } from './channel/channel-preview.component';
+import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component';
const routes: Routes = [
{
@@ -12,6 +13,10 @@ const routes: Routes = [
path: 'channel/:short_id',
component: ChannelPreviewComponent,
},
+ {
+ path: 'nodes/isp/:isp',
+ component: NodesPerISPPreview,
+ },
{
path: '**',
redirectTo: ''
diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.html b/frontend/src/app/lightning/node-statistics/node-statistics.component.html
index ae4ea3dd7..74c14c8b0 100644
--- a/frontend/src/app/lightning/node-statistics/node-statistics.component.html
+++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.html
@@ -43,6 +43,13 @@
\ No newline at end of file
diff --git a/frontend/src/app/lightning/node/node-preview.component.ts b/frontend/src/app/lightning/node/node-preview.component.ts
index c2c3ab72c..574ee51de 100644
--- a/frontend/src/app/lightning/node/node-preview.component.ts
+++ b/frontend/src/app/lightning/node/node-preview.component.ts
@@ -42,7 +42,7 @@ export class NodePreviewComponent implements OnInit {
this.node$ = this.activatedRoute.paramMap
.pipe(
switchMap((params: ParamMap) => {
- this.publicKey = params.get('public_key');
+ this.publicKey = params.get('public_key');
this.openGraphService.waitFor('node-map-' + this.publicKey);
this.openGraphService.waitFor('node-data-' + this.publicKey);
return this.lightningApiService.getNode$(params.get('public_key'));
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.html b/frontend/src/app/lightning/nodes-map/nodes-map.component.html
index d739dd2c9..f6a6f6009 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.html
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.html
@@ -1,4 +1,4 @@
-
+
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
index d7ad42b46..7a67e4eb4 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss
@@ -21,6 +21,17 @@
height: 240px;
padding: 0px;
}
+.full-container.fit-container {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ min-height: 100px;
+
+ .chart {
+ padding: 0;
+ min-height: 100px;
+ }
+}
.chart {
width: 100%;
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
index b783e225a..8ec853aaa 100644
--- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
+++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts
@@ -1,7 +1,7 @@
-import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, Inject, Input, Output, EventEmitter, LOCALE_ID, NgZone, OnDestroy, OnInit, OnChanges } from '@angular/core';
import { SeoService } from 'src/app/services/seo.service';
import { ApiService } from 'src/app/services/api.service';
-import { Observable, tap, zip } from 'rxjs';
+import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs';
import { AssetsService } from 'src/app/services/assets.service';
import { EChartsOption, registerMap } from 'echarts';
import { lerpColor } from 'src/app/shared/graphs.utils';
@@ -17,11 +17,14 @@ import { getFlagEmoji } from 'src/app/shared/common.utils';
styleUrls: ['./nodes-map.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class NodesMap implements OnInit {
+export class NodesMap implements OnInit, OnChanges {
@Input() widget: boolean = false;
@Input() nodes: any[] | undefined = undefined;
@Input() type: 'none' | 'isp' | 'country' = 'none';
-
+ @Input() fitContainer = false;
+ @Output() readyEvent = new EventEmitter();
+ inputNodes$: BehaviorSubject
;
+ nodes$: Observable;
observable$: Observable;
chartInstance = undefined;
@@ -45,9 +48,17 @@ export class NodesMap implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`Lightning nodes world map`);
- this.observable$ = zip(
+ if (!this.inputNodes$) {
+ this.inputNodes$ = new BehaviorSubject(this.nodes);
+ }
+
+ this.nodes$ = this.inputNodes$.pipe(
+ switchMap((nodes) => nodes ? [nodes] : this.apiService.getWorldNodes$())
+ );
+
+ this.observable$ = combineLatest(
this.assetsService.getWorldMapJson$,
- this.nodes ? [this.nodes] : this.apiService.getWorldNodes$()
+ this.nodes$
).pipe(tap((data) => {
registerMap('world', data[0]);
@@ -110,6 +121,16 @@ export class NodesMap implements OnInit {
}));
}
+ ngOnChanges(changes): void {
+ if (changes.nodes) {
+ if (!this.inputNodes$) {
+ this.inputNodes$ = new BehaviorSubject(changes.nodes.currentValue);
+ } else {
+ this.inputNodes$.next(changes.nodes.currentValue);
+ }
+ }
+ }
+
prepareChartOptions(nodes, maxLiquidity, mapCenter, mapZoom) {
let title: object;
if (nodes.length === 0) {
@@ -224,4 +245,8 @@ export class NodesMap implements OnInit {
this.chartInstance.resize();
});
}
+
+ onChartFinished(e) {
+ this.readyEvent.emit();
+ }
}
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html
new file mode 100644
index 000000000..4db69156f
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html
@@ -0,0 +1,63 @@
+
+
+ lightning ISP
+
+
+
+
+
+
+
+
+ Nodes |
+ {{ ispNodes.nodes.length }} |
+
+
+ Liquidity |
+
+ 100000000; else smallnode" [satoshis]="ispNodes.sumLiquidity" [digitsInfo]="'1.2-2'" [noFiat]="false">
+
+
+
+ |
+
+
+ Channels |
+ {{ ispNodes.sumChannels }} |
+
+
+ Top country |
+
+ {{ ispNodes.topCountry.country }} {{ ispNodes.topCountry.flag }}
+ |
+
+
+ Top node |
+
+ {{ ispNodes.nodes[0].alias }}
+ |
+
+
+
+
+
+
+
+
+
+
+ Error loading data.
+
+
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss
new file mode 100644
index 000000000..2fe34ef5e
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss
@@ -0,0 +1,31 @@
+.table {
+ font-size: 32px;
+ margin-top: 0px;
+}
+
+.map-col {
+ flex-grow: 0;
+ flex-shrink: 0;
+ width: 470px;
+ height: 360px;
+ min-width: 470px;
+ min-height: 360px;
+ max-height: 360px;
+ padding: 0;
+ background: #181b2d;
+ overflow: hidden;
+ margin-top: 0;
+}
+
+.row {
+ margin-right: 0;
+}
+
+.full-width-row {
+ padding-left: 15px;
+ flex-wrap: nowrap;
+}
+
+::ng-deep .symbol {
+ font-size: 24px;
+}
\ No newline at end of file
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts
new file mode 100644
index 000000000..18e2f2d6c
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts
@@ -0,0 +1,103 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { catchError, map, switchMap, Observable, share, of } from 'rxjs';
+import { ApiService } from 'src/app/services/api.service';
+import { SeoService } from 'src/app/services/seo.service';
+import { OpenGraphService } from 'src/app/services/opengraph.service';
+import { getFlagEmoji } from 'src/app/shared/common.utils';
+import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
+
+@Component({
+ selector: 'app-nodes-per-isp-preview',
+ templateUrl: './nodes-per-isp-preview.component.html',
+ styleUrls: ['./nodes-per-isp-preview.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NodesPerISPPreview implements OnInit {
+ nodes$: Observable;
+ isp: {name: string, id: number};
+ id: string;
+ error: Error;
+
+ constructor(
+ private apiService: ApiService,
+ private seoService: SeoService,
+ private openGraphService: OpenGraphService,
+ private route: ActivatedRoute,
+ ) { }
+
+ ngOnInit(): void {
+ this.nodes$ = this.route.paramMap
+ .pipe(
+ switchMap((params: ParamMap) => {
+ this.id = params.get('isp');
+ this.isp = null;
+ this.openGraphService.waitFor('isp-map-' + this.id);
+ this.openGraphService.waitFor('isp-data-' + this.id);
+ return this.apiService.getNodeForISP$(params.get('isp'));
+ }),
+ map(response => {
+ this.isp = {
+ name: response.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}]`);
+
+ for (const i in response.nodes) {
+ response.nodes[i].geolocation = {
+ country: response.nodes[i].country?.en,
+ city: response.nodes[i].city?.en,
+ subdivision: response.nodes[i].subdivision?.en,
+ iso: response.nodes[i].iso_code,
+ };
+ }
+
+ 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);
+
+ this.openGraphService.waitOver('isp-data-' + this.id);
+
+ return {
+ nodes: response.nodes,
+ sumLiquidity: sumLiquidity,
+ sumChannels: sumChannels,
+ topCountry: topCountry,
+ };
+ }),
+ catchError(err => {
+ this.error = err;
+ this.openGraphService.fail('isp-map-' + this.id);
+ this.openGraphService.fail('isp-data-' + this.id);
+ return of({
+ nodes: [],
+ sumLiquidity: 0,
+ sumChannels: 0,
+ topCountry: {},
+ });
+ })
+ );
+ }
+
+ onMapReady() {
+ this.openGraphService.waitOver('isp-map-' + this.id);
+ }
+}
diff --git a/frontend/src/app/previews.module.ts b/frontend/src/app/previews.module.ts
new file mode 100644
index 000000000..166670ced
--- /dev/null
+++ b/frontend/src/app/previews.module.ts
@@ -0,0 +1,26 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { SharedModule } from './shared/shared.module';
+import { RouterModule } from '@angular/router';
+import { GraphsModule } from './graphs/graphs.module';
+import { PreviewsRoutingModule } from './previews.routing.module';
+import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
+import { BlockPreviewComponent } from './components/block/block-preview.component';
+import { AddressPreviewComponent } from './components/address/address-preview.component';
+import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
+@NgModule({
+ declarations: [
+ TransactionPreviewComponent,
+ BlockPreviewComponent,
+ AddressPreviewComponent,
+ MasterPagePreviewComponent,
+ ],
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterModule,
+ GraphsModule,
+ PreviewsRoutingModule,
+ ],
+})
+export class PreviewsModule { }
diff --git a/frontend/src/app/previews.routing.module.ts b/frontend/src/app/previews.routing.module.ts
new file mode 100644
index 000000000..5ac13c36d
--- /dev/null
+++ b/frontend/src/app/previews.routing.module.ts
@@ -0,0 +1,39 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component';
+import { BlockPreviewComponent } from './components/block/block-preview.component';
+import { AddressPreviewComponent } from './components/address/address-preview.component';
+import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: MasterPagePreviewComponent,
+ children: [
+ {
+ path: 'block/:id',
+ component: BlockPreviewComponent
+ },
+ {
+ path: 'address/:id',
+ children: [],
+ component: AddressPreviewComponent
+ },
+ {
+ path: 'tx/:id',
+ children: [],
+ component: TransactionPreviewComponent
+ },
+ {
+ path: 'lightning',
+ loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule)
+ },
+ ],
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class PreviewsRoutingModule { }
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 8c2c9e803..be4ba2fe0 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -7,7 +7,6 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MasterPageComponent } from '../components/master-page/master-page.component';
-import { MasterPagePreviewComponent } from '../components/master-page-preview/master-page-preview.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
import { BisqMasterPageComponent } from '../components/bisq-master-page/bisq-master-page.component';
import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component';
@@ -44,15 +43,12 @@ import { RouterModule } from '@angular/router';
import { CapAddressPipe } from './pipes/cap-address-pipe/cap-address-pipe';
import { StartComponent } from '../components/start/start.component';
import { TransactionComponent } from '../components/transaction/transaction.component';
-import { TransactionPreviewComponent } from '../components/transaction/transaction-preview.component';
import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component';
import { BlockComponent } from '../components/block/block.component';
-import { BlockPreviewComponent } from '../components/block/block-preview.component';
import { BlockAuditComponent } from '../components/block-audit/block-audit.component';
import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component';
import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component';
import { AddressComponent } from '../components/address/address.component';
-import { AddressPreviewComponent } from '../components/address/address-preview.component';
import { SearchFormComponent } from '../components/search-form/search-form.component';
import { AddressLabelsComponent } from '../components/address-labels/address-labels.component';
import { FooterComponent } from '../components/footer/footer.component';
@@ -117,21 +113,17 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
AmountComponent,
AboutComponent,
MasterPageComponent,
- MasterPagePreviewComponent,
PreviewTitleComponent,
BisqMasterPageComponent,
LiquidMasterPageComponent,
StartComponent,
TransactionComponent,
- TransactionPreviewComponent,
BlockComponent,
- BlockPreviewComponent,
BlockAuditComponent,
BlockOverviewGraphComponent,
BlockOverviewTooltipComponent,
TransactionsListComponent,
AddressComponent,
- AddressPreviewComponent,
SearchFormComponent,
TimeSpanComponent,
AddressLabelsComponent,
@@ -228,15 +220,12 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
AmountComponent,
StartComponent,
TransactionComponent,
- TransactionPreviewComponent,
BlockComponent,
- BlockPreviewComponent,
BlockAuditComponent,
BlockOverviewGraphComponent,
BlockOverviewTooltipComponent,
TransactionsListComponent,
AddressComponent,
- AddressPreviewComponent,
SearchFormComponent,
TimeSpanComponent,
AddressLabelsComponent,
diff --git a/unfurler/src/routes.ts b/unfurler/src/routes.ts
index c6e77e79a..4c25bf93b 100644
--- a/unfurler/src/routes.ts
+++ b/unfurler/src/routes.ts
@@ -46,6 +46,17 @@ const routes = {
return `Lightning Channel: ${path[0]}`;
}
},
+ nodes: {
+ routes: {
+ isp: {
+ render: true,
+ params: 1,
+ getTitle(path) {
+ return `Lightning ISP: ${path[0]}`;
+ }
+ }
+ }
+ }
}
},
mining: {