diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 3d754c979..d56b4bfeb 100644 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -24,8 +24,8 @@ __MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool} __MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info} __MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=false} __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=false} -__MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=false} -__MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=false} +__MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json} +__MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master} # CORE_RPC __CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1} @@ -116,8 +116,8 @@ sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.jso sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/g" mempool-config.json -sed -i "s/__MEMPOOL_POOLS_JSON_URL__/${__MEMPOOL_POOLS_JSON_URL__}/g" mempool-config.json -sed -i "s/__MEMPOOL_POOLS_JSON_TREE_URL__/${__MEMPOOL_POOLS_JSON_TREE_URL__}/g" mempool-config.json +sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}/g" mempool-config.json +sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}/g" mempool-config.json sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 000a0f177..bd2d8c541 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -15,7 +15,6 @@ import { TermsOfServiceComponent } from './components/terms-of-service/terms-of- import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component'; import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component'; import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-master-page.component'; -import { SponsorComponent } from './components/sponsor/sponsor.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; import { BlocksList } from './components/blocks-list/blocks-list.component'; import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; @@ -367,16 +366,24 @@ let routes: Routes = [ children: [], component: AddressPreviewComponent }, + { + 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: 'status', component: StatusViewComponent }, - { - path: 'sponsor', - component: SponsorComponent, - }, { path: '', loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) @@ -642,10 +649,6 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'status', component: StatusViewComponent }, - { - path: 'sponsor', - component: SponsorComponent, - }, { path: '', loadChildren: () => import('./graphs/graphs.module').then(m => m.GraphsModule) diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index e09eddc78..45ed55abd 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -25,12 +25,6 @@ -

- -

Enterprise Sponsors 🚀

diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index a373683c6..8ede1406f 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -65,6 +65,10 @@ height: 45px; width: 100%; } + img { + width: 67px; + height: 67px; + } } .alliances { diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 4f348fbbf..693be2d34 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -61,9 +61,9 @@ export class AboutComponent implements OnInit { ); } - sponsor() { + sponsor(): void { if (this.officialMempoolSpace && this.stateService.env.BASE_MODULE === 'mempool') { - this.router.navigateByUrl('/sponsor'); + this.router.navigateByUrl('/enterprise'); } else { this.showNavigateToSponsor = true; } diff --git a/frontend/src/app/components/master-page-preview/master-page-preview.component.html b/frontend/src/app/components/master-page-preview/master-page-preview.component.html index 52a3e7026..7d4f5364a 100644 --- a/frontend/src/app/components/master-page-preview/master-page-preview.component.html +++ b/frontend/src/app/components/master-page-preview/master-page-preview.component.html @@ -7,12 +7,12 @@
- logo Signet - testnet logo Testnet + logo Signet Lightning + testnet logo Testnet Lightning bisq logo Bisq liquid mainnet logo Liquid liquid testnet logo Liquid Testnet - bitcoin logo Mainnet + bitcoin logo Mainnet Lightning
diff --git a/frontend/src/app/components/master-page-preview/master-page-preview.component.ts b/frontend/src/app/components/master-page-preview/master-page-preview.component.ts index 9678aa32d..61a392b5e 100644 --- a/frontend/src/app/components/master-page-preview/master-page-preview.component.ts +++ b/frontend/src/app/components/master-page-preview/master-page-preview.component.ts @@ -10,6 +10,7 @@ import { LanguageService } from 'src/app/services/language.service'; }) export class MasterPagePreviewComponent implements OnInit { network$: Observable; + lightning$: Observable; officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; urlLanguage: string; @@ -20,6 +21,7 @@ export class MasterPagePreviewComponent implements OnInit { ngOnInit() { this.network$ = merge(of(''), this.stateService.networkChanged$); + this.lightning$ = this.stateService.lightningChanged$; this.urlLanguage = this.languageService.getLanguageForUrl(); } } diff --git a/frontend/src/app/components/sponsor/sponsor.component.html b/frontend/src/app/components/sponsor/sponsor.component.html deleted file mode 100644 index 2e1dae48e..000000000 --- a/frontend/src/app/components/sponsor/sponsor.component.html +++ /dev/null @@ -1,157 +0,0 @@ -
- - - - -
diff --git a/frontend/src/app/components/sponsor/sponsor.component.scss b/frontend/src/app/components/sponsor/sponsor.component.scss deleted file mode 100644 index e485a97d9..000000000 --- a/frontend/src/app/components/sponsor/sponsor.component.scss +++ /dev/null @@ -1,133 +0,0 @@ -.sponsor-page { - text-align: center; -} - -.qr-wrapper { - background-color: #FFF; - padding: 10px; - display: inline-block; - padding-bottom: 5px; - margin: 20px auto 0px; -} - -.info-group { - max-width: 400px; -} - -.card { - width: 240px; - height: 220px; - background-color: #1d1f31; - border: 2px solid #1d1f31; - cursor: pointer; - position: relative; - transition: 100ms all; - margin: 30px 30px 20px 30px; - @media(min-width: 476px) { - margin: 30px 100px 20px 100px; - } - @media(min-width: 851px) { - margin: 60px 20px 40px 20px; - } - - .card-title { - font-weight: bold; - span { - font-weight: 100; - } - } - - &.bigger { - height: 220px; - width: 240px; - margin-top: 40px; - } - - &:hover { - background-color: #5058926b; - border: 2px solid #505892; - transform: scale(1.1) translateY(-10px); - margin-top: 70px; - - .card-header { - background-color: #505892; - } - } -} - -.donation-form { - max-width: 280px; - margin: auto; - button { - width: 100%; - } -} - -.card-header { - background-color: #171929; -} - -.flex-container { - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; -} - -.middle-card { - width: 280px; - height: 260px; - margin-top: 40px; - &:hover { - margin-top: 50px; - } -} - -.shiny-border { - background-color: #5058926b; - border: 2px solid #505892; - transform: scale(1.1) translateY(-10px); - margin-top: 70px; - box-shadow: 0px 0px 100px #9858ff52; - .card-header { - background-color: #505892; - } - - &.middle-card { - margin-top: 50px; - } -} - -.input-group { - margin: 20px auto; -} - -.donation-confirmed { - h2 { - margin-top: 50px; - span { - display: block; - &:last-child { - color: #9858ff; - font-weight: bold; - font-size: 2rem; - } - } - } - - .order-details { - margin-top: 50px; - span { - color: #d81b60; - margin-left: 10px; - } - } -} - -.card-body { - align-items: center; - display: flex; - justify-content: center; - flex-direction: column; - height: 100%; -} \ No newline at end of file diff --git a/frontend/src/app/components/sponsor/sponsor.component.ts b/frontend/src/app/components/sponsor/sponsor.component.ts deleted file mode 100644 index f5f95debc..000000000 --- a/frontend/src/app/components/sponsor/sponsor.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; -import { Subscription } from 'rxjs'; -import { delay, retryWhen, switchMap, tap } from 'rxjs/operators'; -import { ApiService } from 'src/app/services/api.service'; -import { SeoService } from 'src/app/services/seo.service'; -import { StateService } from 'src/app/services/state.service'; -import { WebsocketService } from 'src/app/services/websocket.service'; - -@Component({ - selector: 'app-sponsor', - templateUrl: './sponsor.component.html', - styleUrls: ['./sponsor.component.scss'] -}) -export class SponsorComponent implements OnInit, OnDestroy { - sponsorsEnabled = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; - donationForm: FormGroup; - paymentForm: FormGroup; - requestSubscription: Subscription | undefined; - donationObj: any; - donationStatus = 1; - - constructor( - private formBuilder: FormBuilder, - private apiService: ApiService, - private sanitizer: DomSanitizer, - private stateService: StateService, - private websocketService: WebsocketService, - private seoService: SeoService, - ) { } - - ngOnInit(): void { - this.seoService.setTitle($localize`:@@dfd99c62b5b308fc5b1ad7adbbf9d526d2b31516:Sponsor`); - this.websocketService.want(['blocks']); - - this.paymentForm = this.formBuilder.group({ - 'method': 'chain' - }); - - this.donationForm = this.formBuilder.group({ - selection: [0.01], - handle: [''], - }); - } - - submitDonation() { - if (this.donationForm.invalid) { - return; - } - this.requestSubscription = this.apiService.requestDonation$( - this.donationForm.get('selection').value, - this.donationForm.get('handle').value - ) - .pipe( - tap((response) => { - this.donationObj = response; - this.donationStatus = 2; - }), - switchMap(() => this.apiService.checkDonation$(this.donationObj.id) - .pipe( - retryWhen((errors) => errors.pipe(delay(2000))) - ) - ) - ).subscribe(() => { - this.donationStatus = 3; - /* - if (this.donationForm.get('handle').value) { - this.sponsors.unshift({ handle: this.donationForm.get('handle').value }); - } - */ - }); - } - - setSelection(amount: number): void { - this.donationForm.get('selection').setValue(amount); - } - - bypassSecurityTrustUrl(text: string): SafeUrl { - return this.sanitizer.bypassSecurityTrustUrl(text); - } - - ngOnDestroy() { - if (this.requestSubscription) { - this.requestSubscription.unsubscribe(); - } - } -} diff --git a/frontend/src/app/lightning/channel/channel-preview.component.html b/frontend/src/app/lightning/channel/channel-preview.component.html new file mode 100644 index 000000000..c98929931 --- /dev/null +++ b/frontend/src/app/lightning/channel/channel-preview.component.html @@ -0,0 +1,72 @@ +
+
+

+ Channel: + {{ channel.short_id }} +

+
+ Inactive + Active + Closed + + +
+
+
+ + {{ channel.node_left.alias || '?' }} + + + + {{ channel.node_right.alias || '?' }} + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
Created{{ channel.created | date:'yyyy-MM-dd HH:mm' }}
Capacity
Fee rate +
+ {{ channel.node_left.fee_rate }} ppm + + {{ channel.node_right.fee_rate }} ppm +
+
Base fee +
+ + + +
+
+
+
+ +
+
+
+ + +
+ Error loading data. +

+ {{ error.status }}: {{ error.error }} +
+
diff --git a/frontend/src/app/lightning/channel/channel-preview.component.scss b/frontend/src/app/lightning/channel/channel-preview.component.scss new file mode 100644 index 000000000..e89733ff3 --- /dev/null +++ b/frontend/src/app/lightning/channel/channel-preview.component.scss @@ -0,0 +1,76 @@ +.title { + font-size: 52px; + margin: 0; +} + +.table { + font-size: 32px; + margin-top: 36px; +} + +.badges { + font-size: 28px; + + ::ng-deep .badge { + margin-left: 0.5em; + } +} + +.row { + margin-right: 0; +} + +.full-width-row { + padding-left: 15px; + padding-right: 15px; + + &:nth-child(even) { + background: #181b2d; + margin: 15px 0; + } +} + +.nodes { + font-size: 36px; + align-items: center; +} + +.between-arrow { + font-size: 24px; +} + +.map-col { + flex-grow: 0; + flex-shrink: 0; + width: 470px; + min-width: 470px; + padding: 0; + background: #181b2d; + max-height: 470px; + overflow: hidden; +} + +::ng-deep .symbol { + font-size: 24px; +} + +.dual-cell { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; + + & > * { + width: 0; + flex-grow: 1; + + &:nth-child(2) { + text-align: center; + max-width: 1.5em; + } + &:nth-child(3) { + text-align: right; + } + } +} diff --git a/frontend/src/app/lightning/channel/channel-preview.component.ts b/frontend/src/app/lightning/channel/channel-preview.component.ts new file mode 100644 index 000000000..c82adba66 --- /dev/null +++ b/frontend/src/app/lightning/channel/channel-preview.component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { catchError, switchMap, tap } from 'rxjs/operators'; +import { SeoService } from 'src/app/services/seo.service'; +import { OpenGraphService } from 'src/app/services/opengraph.service'; +import { LightningApiService } from '../lightning-api.service'; + +@Component({ + selector: 'app-channel-preview', + templateUrl: './channel-preview.component.html', + styleUrls: ['./channel-preview.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ChannelPreviewComponent implements OnInit { + channel$: Observable; + error: any = null; + channelGeo: number[] = []; + + constructor( + private lightningApiService: LightningApiService, + private activatedRoute: ActivatedRoute, + private seoService: SeoService, + private openGraphService: OpenGraphService, + ) { } + + ngOnInit(): void { + this.channel$ = this.activatedRoute.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.openGraphService.waitFor('channel-map'); + this.openGraphService.waitFor('channel-data'); + this.error = null; + 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, + ]; + } + this.openGraphService.waitOver('channel-data'); + }), + catchError((err) => { + this.error = err; + this.openGraphService.fail('channel-map'); + this.openGraphService.fail('channel-data'); + return of(null); + }) + ); + }) + ); + } + + onMapReady() { + this.openGraphService.waitOver('channel-map'); + } +} diff --git a/frontend/src/app/lightning/lightning-previews.module.ts b/frontend/src/app/lightning/lightning-previews.module.ts new file mode 100644 index 000000000..4d5d6cee9 --- /dev/null +++ b/frontend/src/app/lightning/lightning-previews.module.ts @@ -0,0 +1,28 @@ +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 { LightningModule } from './lightning.module'; +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'; +@NgModule({ + declarations: [ + NodePreviewComponent, + ChannelPreviewComponent, + ], + imports: [ + CommonModule, + SharedModule, + RouterModule, + GraphsModule, + LightningPreviewsRoutingModule, + LightningModule, + ], + providers: [ + LightningApiService, + ] +}) +export class LightningPreviewsModule { } diff --git a/frontend/src/app/lightning/lightning-previews.routing.module.ts b/frontend/src/app/lightning/lightning-previews.routing.module.ts new file mode 100644 index 000000000..69de2aadf --- /dev/null +++ b/frontend/src/app/lightning/lightning-previews.routing.module.ts @@ -0,0 +1,25 @@ +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'; + +const routes: Routes = [ + { + path: 'node/:public_key', + component: NodePreviewComponent, + }, + { + path: 'channel/:short_id', + component: ChannelPreviewComponent, + }, + { + path: '**', + redirectTo: '' + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class LightningPreviewsRoutingModule { } diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index 781418bfd..c01792815 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -53,6 +53,27 @@ import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels LightningRoutingModule, GraphsModule, ], + exports: [ + LightningDashboardComponent, + NodesListComponent, + NodeStatisticsComponent, + NodeStatisticsChartComponent, + NodeComponent, + ChannelsListComponent, + ChannelComponent, + LightningWrapperComponent, + ChannelBoxComponent, + ClosingTypeComponent, + LightningStatisticsChartComponent, + NodesNetworksChartComponent, + ChannelsStatisticsComponent, + NodesPerISPChartComponent, + NodesPerCountry, + NodesPerISP, + NodesPerCountryChartComponent, + NodesMap, + NodesChannelsMap, + ], providers: [ LightningApiService, ] diff --git a/frontend/src/app/lightning/node/node-preview.component.html b/frontend/src/app/lightning/node/node-preview.component.html new file mode 100644 index 000000000..0bb7255a6 --- /dev/null +++ b/frontend/src/app/lightning/node/node-preview.component.html @@ -0,0 +1,64 @@ +
+
+

+ Node: + {{ node.alias }} +

+
+ {{ socketType }} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Active capacity + +
Active channels + {{ node.active_channel_count }} +
Average size + +
Location + {{ node.city.en }} +
Country + {{ node.country.en }} {{ node.flag }} +
Location + unknown +
+
+
+ +
+
+
+ + +
+ Error loading data. +
+
diff --git a/frontend/src/app/lightning/node/node-preview.component.scss b/frontend/src/app/lightning/node/node-preview.component.scss new file mode 100644 index 000000000..c6b2ea9d7 --- /dev/null +++ b/frontend/src/app/lightning/node/node-preview.component.scss @@ -0,0 +1,43 @@ +.title { + font-size: 52px; + margin-bottom: 0; +} + +.table { + margin-top: 48px; + font-size: 32px; +} + +.badges { + font-size: 28px; + + ::ng-deep .badge { + margin-left: 0.5em; + } +} + +.map-col { + flex-grow: 0; + flex-shrink: 0; + width: 470px; + height: 390px; + min-width: 470px; + min-height: 390px; + max-height: 390px; + padding: 0; + background: #181b2d; + overflow: hidden; + margin-top: 18px; +} + +.row { + margin-right: 0; +} + +.full-width-row { + padding-left: 15px; +} + +::ng-deep .symbol { + font-size: 24px; +} diff --git a/frontend/src/app/lightning/node/node-preview.component.ts b/frontend/src/app/lightning/node/node-preview.component.ts new file mode 100644 index 000000000..6344a38b2 --- /dev/null +++ b/frontend/src/app/lightning/node/node-preview.component.ts @@ -0,0 +1,105 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { Observable } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; +import { SeoService } from 'src/app/services/seo.service'; +import { OpenGraphService } from 'src/app/services/opengraph.service'; +import { getFlagEmoji } from 'src/app/shared/graphs.utils'; +import { LightningApiService } from '../lightning-api.service'; +import { isMobile } from '../../shared/common.utils'; + +@Component({ + selector: 'app-node-preview', + templateUrl: './node-preview.component.html', + styleUrls: ['./node-preview.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NodePreviewComponent implements OnInit { + node$: Observable; + statistics$: Observable; + publicKey$: Observable; + selectedSocketIndex = 0; + qrCodeVisible = false; + channelsListStatus: string; + error: Error; + publicKey: string; + socketTypes: string[]; + + publicKeySize = 99; + + constructor( + private lightningApiService: LightningApiService, + private activatedRoute: ActivatedRoute, + private seoService: SeoService, + private openGraphService: OpenGraphService, + ) { + if (isMobile()) { + this.publicKeySize = 12; + } + } + + ngOnInit(): void { + this.node$ = this.activatedRoute.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.openGraphService.waitFor('node-map'); + this.openGraphService.waitFor('node-data'); + this.publicKey = params.get('public_key'); + return this.lightningApiService.getNode$(params.get('public_key')); + }), + map((node) => { + this.seoService.setTitle(`Node: ${node.alias}`); + + const socketsObject = []; + const socketTypesMap = {}; + for (const socket of node.sockets.split(',')) { + if (socket === '') { + continue; + } + let label = ''; + if (socket.match(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)) { + label = 'IPv4'; + } else if (socket.indexOf('[') > -1) { + label = 'IPv6'; + } else if (socket.indexOf('onion') > -1) { + label = 'Tor'; + } + node.flag = getFlagEmoji(node.iso_code); + socketsObject.push({ + label: label, + socket: node.public_key + '@' + socket, + }); + socketTypesMap[label] = true + } + node.socketsObject = socketsObject; + this.socketTypes = Object.keys(socketTypesMap); + node.avgCapacity = node.capacity / Math.max(1, node.active_channel_count); + + this.openGraphService.waitOver('node-data'); + + return node; + }), + catchError(err => { + this.error = err; + this.openGraphService.fail('node-map'); + this.openGraphService.fail('node-data'); + return [{ + alias: this.publicKey, + public_key: this.publicKey, + }]; + }) + ); + } + + changeSocket(index: number) { + this.selectedSocketIndex = index; + } + + onChannelsListStatusChanged(e) { + this.channelsListStatus = e; + } + + onMapReady() { + this.openGraphService.waitOver('node-map'); + } +} diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html index 5ccb9f3bc..87082257e 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,7 +8,7 @@
+ (chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.scss b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.scss index 578bffc3a..ca887ad13 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.scss +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.scss @@ -29,6 +29,18 @@ min-height: 250px; } +.full-container.fit-container { + margin: 0; + padding: 0; + height: 100%; + min-height: 100px; + + .chart { + padding: 0; + min-height: 100px; + } +} + .widget { width: 90vw; margin-left: auto; 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 d8952d632..033f944b1 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 @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, HostListener, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostListener, Input, Output, EventEmitter, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { SeoService } from 'src/app/services/seo.service'; import { ApiService } from 'src/app/services/api.service'; import { Observable, switchMap, tap, zip } from 'rxjs'; @@ -20,9 +20,11 @@ export class NodesChannelsMap implements OnInit, OnDestroy { @Input() style: 'graph' | 'nodepage' | 'widget' | 'channelpage' = 'graph'; @Input() publicKey: string | undefined; @Input() channel: any[] = []; + @Input() fitContainer = false; + @Output() readyEvent = new EventEmitter(); observable$: Observable; - + center: number[] | undefined; zoom: number | undefined; channelWidth = 0.6; @@ -313,4 +315,8 @@ export class NodesChannelsMap implements OnInit, OnDestroy { this.chartInstance.setOption(chartOptions); }); } + + onChartFinished(e) { + this.readyEvent.emit(); + } } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 0d0b05556..466837f98 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -71,11 +71,13 @@ const defaultEnv: Env = { export class StateService { isBrowser: boolean = isPlatformBrowser(this.platformId); network = ''; + lightning = false; blockVSize: number; env: Env; latestBlockHeight = -1; networkChanged$ = new ReplaySubject(1); + lightningChanged$ = new ReplaySubject(1); blocks$: ReplaySubject<[BlockExtended, boolean]>; transactions$ = new ReplaySubject(6); conversions$ = new ReplaySubject(1); @@ -122,15 +124,18 @@ export class StateService { if (this.isBrowser) { this.setNetworkBasedonUrl(window.location.pathname); + this.setLightningBasedonUrl(window.location.pathname); this.isTabHidden$ = fromEvent(document, 'visibilitychange').pipe(map(() => this.isHidden()), shareReplay()); } else { this.setNetworkBasedonUrl('/'); + this.setLightningBasedonUrl('/'); this.isTabHidden$ = new BehaviorSubject(false); } this.router.events.subscribe((event) => { if (event instanceof NavigationStart) { this.setNetworkBasedonUrl(event.url); + this.setLightningBasedonUrl(event.url); } }); @@ -198,6 +203,15 @@ export class StateService { } } + setLightningBasedonUrl(url: string) { + if (this.env.BASE_MODULE !== 'mempool') { + return; + } + const networkMatches = url.match(/\/lightning\//); + this.lightning = !!networkMatches; + this.lightningChanged$.next(this.lightning); + } + getHiddenProp(){ const prefixes = ['webkit', 'moz', 'ms', 'o']; if ('hidden' in document) { return 'hidden'; } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index ac9de747e..48e9eb46e 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -4,7 +4,7 @@ import { NgbCollapse, NgbCollapseModule, NgbRadioGroup, NgbTypeaheadModule } fro import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode } from '@fortawesome/free-solid-svg-icons'; + 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'; @@ -64,7 +64,6 @@ import { DifficultyComponent } from '../components/difficulty/difficulty.compone import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component'; import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component'; import { TrademarkPolicyComponent } from '../components/trademark-policy/trademark-policy.component'; -import { SponsorComponent } from '../components/sponsor/sponsor.component'; import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component'; import { AssetGroupComponent } from '../components/assets/asset-group/asset-group.component'; @@ -139,7 +138,6 @@ import { ToggleComponent } from './components/toggle/toggle.component'; TermsOfServiceComponent, PrivacyPolicyComponent, TrademarkPolicyComponent, - SponsorComponent, PushTransactionComponent, AssetsNavComponent, AssetsFeaturedComponent, @@ -242,7 +240,6 @@ import { ToggleComponent } from './components/toggle/toggle.component'; TermsOfServiceComponent, PrivacyPolicyComponent, TrademarkPolicyComponent, - SponsorComponent, PushTransactionComponent, AssetsNavComponent, AssetsFeaturedComponent, @@ -300,5 +297,6 @@ export class SharedModule { library.addIcons(faListUl); library.addIcons(faDownload); library.addIcons(faQrcode); + library.addIcons(faArrowRightArrowLeft); } } diff --git a/unfurler/src/index.ts b/unfurler/src/index.ts index ca85ae5cc..cd6b7762f 100644 --- a/unfurler/src/index.ts +++ b/unfurler/src/index.ts @@ -150,12 +150,27 @@ class Server { } // handle supported preview routes - if (parts[0] === 'block') { - ogTitle = `Block: ${parts[1]}`; - } else if (parts[0] === 'address') { - ogTitle = `Address: ${parts[1]}`; - } else { - previewSupported = false; + switch (parts[0]) { + case 'block': + ogTitle = `Block: ${parts[1]}`; + break; + case 'address': + ogTitle = `Address: ${parts[1]}`; + break; + case 'lightning': + switch (parts[1]) { + case 'node': + ogTitle = `Lightning Node: ${parts[2]}`; + break; + case 'channel': + ogTitle = `Lightning Channel: ${parts[2]}`; + break; + default: + previewSupported = false; + } + break; + default: + previewSupported = false; } if (previewSupported) {