diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index 55043197d..dec9af1e5 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -96,7 +96,31 @@ class ChannelsApi { public async $getChannel(id: string): Promise { try { - const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.*, ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key WHERE (ns1.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node1_public_key) AND ns2.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node2_public_key)) AND channels.id = ?`; + const query = ` + SELECT n1.alias AS alias_left, n1.longitude as node1_longitude, n1.latitude as node1_latitude, + n2.alias AS alias_right, n2.longitude as node2_longitude, n2.latitude as node2_latitude, + channels.*, + ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right + FROM channels + LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key + LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key + LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key + LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key + WHERE ( + ns1.id = ( + SELECT MAX(id) + FROM node_stats + WHERE public_key = channels.node1_public_key + ) + AND ns2.id = ( + SELECT MAX(id) + FROM node_stats + WHERE public_key = channels.node2_public_key + ) + ) + AND channels.id = ? + `; + const [rows]: any = await DB.query(query, [id]); if (rows[0]) { return this.convertChannel(rows[0]); @@ -289,6 +313,8 @@ class ChannelsApi { 'max_htlc_mtokens': channel.node1_max_htlc_mtokens, 'min_htlc_mtokens': channel.node1_min_htlc_mtokens, 'updated_at': channel.node1_updated_at, + 'longitude': channel.node1_longitude, + 'latitude': channel.node1_latitude, }, 'node_right': { 'alias': channel.alias_right, @@ -302,6 +328,8 @@ class ChannelsApi { 'max_htlc_mtokens': channel.node2_max_htlc_mtokens, 'min_htlc_mtokens': channel.node2_min_htlc_mtokens, 'updated_at': channel.node2_updated_at, + 'longitude': channel.node2_longitude, + 'latitude': channel.node2_latitude, }, }; } diff --git a/backend/src/api/explorer/channels.routes.ts b/backend/src/api/explorer/channels.routes.ts index bbb075aa6..09c3da668 100644 --- a/backend/src/api/explorer/channels.routes.ts +++ b/backend/src/api/explorer/channels.routes.ts @@ -32,6 +32,9 @@ class ChannelsRoutes { res.status(404).send('Channel not found'); return; } + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(channel); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); diff --git a/frontend/src/app/lightning/channel/channel.component.html b/frontend/src/app/lightning/channel/channel.component.html index 41c2f3254..ec49c78a0 100644 --- a/frontend/src/app/lightning/channel/channel.component.html +++ b/frontend/src/app/lightning/channel/channel.component.html @@ -14,7 +14,9 @@
-
+ + +
diff --git a/frontend/src/app/lightning/channel/channel.component.ts b/frontend/src/app/lightning/channel/channel.component.ts index bc66f7180..9c3cdd57e 100644 --- a/frontend/src/app/lightning/channel/channel.component.ts +++ b/frontend/src/app/lightning/channel/channel.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { Observable, of } from 'rxjs'; -import { catchError, switchMap } from 'rxjs/operators'; +import { catchError, switchMap, tap } from 'rxjs/operators'; import { SeoService } from 'src/app/services/seo.service'; import { LightningApiService } from '../lightning-api.service'; @@ -14,6 +14,7 @@ import { LightningApiService } from '../lightning-api.service'; export class ChannelComponent implements OnInit { channel$: Observable; error: any = null; + channelGeo: number[] = []; constructor( private lightningApiService: LightningApiService, @@ -29,9 +30,23 @@ export class ChannelComponent implements OnInit { this.seoService.setTitle(`Channel: ${params.get('short_id')}`); return this.lightningApiService.getChannel$(params.get('short_id')) .pipe( + tap((data) => { + if (!data.node_left.longitude || !data.node_left.latitude || + !data.node_right.longitude || !data.node_right.latitude) { + this.channelGeo = []; + } else { + this.channelGeo = [ + data.node_left.public_key, + data.node_left.alias, + data.node_left.longitude, data.node_left.latitude, + data.node_right.public_key, + data.node_right.alias, + data.node_right.longitude, data.node_right.latitude, + ]; + } + }), catchError((err) => { this.error = err; - console.log(this.error); return of(null); }) ); diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts index b88621bba..43da510f0 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts @@ -16,8 +16,9 @@ import 'echarts-gl'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class NodesChannelsMap implements OnInit, OnDestroy { - @Input() style: 'graph' | 'nodepage' | 'widget' = 'graph'; + @Input() style: 'graph' | 'nodepage' | 'widget' | 'channelpage' = 'graph'; @Input() publicKey: string | undefined; + @Input() channel: any[] = []; observable$: Observable; @@ -25,6 +26,8 @@ export class NodesChannelsMap implements OnInit, OnDestroy { zoom: number | undefined; channelWidth = 0.6; channelOpacity = 0.1; + channelColor = '#466d9d'; + channelCurve = 0; chartInstance = undefined; chartOptions: EChartsOption = {}; @@ -67,13 +70,29 @@ export class NodesChannelsMap implements OnInit, OnDestroy { const nodes = []; const nodesPubkeys = {}; let thisNodeGPS: number[] | undefined = undefined; - for (const channel of data[1]) { + + let geoloc = data[1]; + if (this.style === 'channelpage') { + if (this.channel.length === 0) { + geoloc = []; + } else { + geoloc = [this.channel]; + } + } + for (const channel of geoloc) { if (!thisNodeGPS && data[2] === channel[0]) { thisNodeGPS = [channel[2], channel[3]]; } else if (!thisNodeGPS && data[2] === channel[4]) { thisNodeGPS = [channel[6], channel[7]]; } + // 0 - node1 pubkey + // 1 - node1 alias + // 2,3 - node1 GPS + // 4 - node2 pubkey + // 5 - node2 alias + // 6,7 - node2 GPS + // We add a bit of noise so nodes at the same location are not all // on top of each other let random = Math.random() * 2 * Math.PI; @@ -115,6 +134,22 @@ export class NodesChannelsMap implements OnInit, OnDestroy { this.channelWidth = 1; this.channelOpacity = 1; } + if (this.style === 'channelpage' && this.channel.length > 0) { + this.channelWidth = 2; + this.channelOpacity = 1; + this.channelColor = '#bafcff'; + this.channelCurve = 0.1; + this.center = [ + (this.channel[2] + this.channel[6]) / 2, + (this.channel[3] + this.channel[7]) / 2 + ]; + const distance = Math.sqrt( + Math.pow(this.channel[7] - this.channel[3], 2) + + Math.pow(this.channel[6] - this.channel[2], 2) + ); + + this.zoom = -0.05 * distance + 8; + } this.prepareChartOptions(nodes, channelsLoc); })); @@ -202,8 +237,8 @@ export class NodesChannelsMap implements OnInit, OnDestroy { lineStyle: { opacity: this.channelOpacity, width: this.channelWidth, - curveness: 0, - color: '#466d9d', + curveness: this.channelCurve, + color: this.channelColor, }, blendMode: 'lighter', tooltip: {