Merge pull request #2558 from mempool/simon/mempool-node-group-page
Mempool node group page
This commit is contained in:
		
						commit
						59931afd62
					
				| @ -21,6 +21,7 @@ class NodesRoutes { | |||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/age', this.$getOldestNodes) |       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/age', this.$getOldestNodes) | ||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) |       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) | ||||||
|       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) |       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) | ||||||
|  |       .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/group/:name', this.$getNodeGroup) | ||||||
|     ; |     ; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -33,6 +34,39 @@ class NodesRoutes { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private async $getNodeGroup(req: Request, res: Response) { | ||||||
|  |     try { | ||||||
|  |       let nodesList; | ||||||
|  |       let nodes: any[] = []; | ||||||
|  |       switch (config.MEMPOOL.NETWORK) { | ||||||
|  |         case 'testnet': | ||||||
|  |           nodesList = ['032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b', '025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7', '0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55', '032ab2028c0b614c6d87824e2373529652fd7e4221b4c70cc4da7c7005c49afcf0', '029001b22fe70b48bee12d014df91982eb85ff1bd404ec772d5c83c4ee3e88d2c3', '0212e2848d79f928411da5f2ff0a8c95ec6ccb5a09d2031b6f71e91309dcde63af', '03e871a2229523d34f76e6311ff197cfe7f26c2fbec13554b93a46f4e710c47dab', '032202ec98d976b0e928bd1d91924e8bd3eab07231fc39feb3737b010071073df8', '02fa7c5a948d03d563a9f36940c2205a814e594d17c0042ced242c71a857d72605', '039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205', '033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18', '029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584']; | ||||||
|  |           break; | ||||||
|  |         case 'signet': | ||||||
|  |           nodesList = ['03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956', '033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de', '02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781', '025196512905b8a3f1597428b867bec63ec9a95e5089eb7dc7e63e2d2691669029', '027c625aa1fbe3768db68ebcb05b53b6dc0ce68b7b54b8900d326d167363e684fe', '03f1629af3101fcc56b7aac2667016be84e3defbf3d0c8719f836c9b41c9a57a43', '02dfb81e2f7a3c4c9e8a51b70ef82b4a24549cc2fab1f5b2fd636501774a918991', '02d01ccf832944c68f10d39006093769c5b8bda886d561b128534e313d729fdb34', '02499ed23027d4698a6904ff4ec1b6085a61f10b9a6937f90438f9947e38e8ea86', '038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7', '03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761', '028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7']; | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           nodesList = ['03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61', '03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437', '03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144', '0238bd27f02d67d6c51e269692bc8c9a32357a00e7777cba7f4f1f18a2a700b108', '03f983dcabed6baa1eab5b56c8b2e8fdc846ab3fd931155377897335e85a9fa57c', '03e399589533581e48796e29a825839a010036a61b20744fda929d6709fcbffcc5', '021f5288b5f72c42cd0d8801086af7ce09a816d8ee9a4c47a4b436399b26cb601a', '032b01b7585f781420cd4148841a82831ba37fa952342052cec16750852d4f2dd9', '02848036488d4b8fb1f1c4064261ec36151f43b085f0b51bd239ade3ddfc940c34', '02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf', '03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c', '0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43']; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       for (let pubKey of nodesList) { | ||||||
|  |         try { | ||||||
|  |           const node = await nodesApi.$getNode(pubKey); | ||||||
|  |           if (node) { | ||||||
|  |             nodes.push(node); | ||||||
|  |           } | ||||||
|  |         } catch (e) {} | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       res.header('Pragma', 'public'); | ||||||
|  |       res.header('Cache-control', 'public'); | ||||||
|  |       res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); | ||||||
|  |       res.json(nodes); | ||||||
|  |     } catch (e) { | ||||||
|  |       res.status(500).send(e instanceof Error ? e.message : e); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private async $getNode(req: Request, res: Response) { |   private async $getNode(req: Request, res: Response) { | ||||||
|     try { |     try { | ||||||
|       const node = await nodesApi.$getNode(req.params.public_key); |       const node = await nodesApi.$getNode(req.params.public_key); | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								frontend/src/app/lightning/group/group.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								frontend/src/app/lightning/group/group.component.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | |||||||
|  | <div class="container-xl full-height" style="min-height: 335px"> | ||||||
|  |   <h5 class="mb-1" style="color: #ffffff66" i18n="lightning.node">Lightning node group</h5> | ||||||
|  | 
 | ||||||
|  |   <div class="header"> | ||||||
|  |     <div class="logo-container"> | ||||||
|  |       <app-svg-images name="officialMempoolSpace" viewBox="0 0 125 126"></app-svg-images> | ||||||
|  |     </div> | ||||||
|  |     <h1>The Mempool Open Source Project</h1> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   <div class="box"> | ||||||
|  |     <div class="row" *ngIf="nodes$ | async as nodes"> | ||||||
|  |       <div class="col-12 col-md-6"> | ||||||
|  |         <table class="table table-borderless table-striped"> | ||||||
|  |           <tbody> | ||||||
|  |             <tr> | ||||||
|  |               <td>Description</td> | ||||||
|  |               <td><div class="description-text">These are the Lightning nodes operated by The Mempool Open Source Project that provide data for the mempool.space website. Connect to us! | ||||||
|  |                 </div> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr> | ||||||
|  |               <td i18n="lightning.node-count">Nodes</td> | ||||||
|  |               <td>{{ nodes.nodes.length }}</td> | ||||||
|  |             </tr> | ||||||
|  |             <tr> | ||||||
|  |               <td i18n="lightning.liquidity">Liquidity</td> | ||||||
|  |               <td> | ||||||
|  |                 <app-amount *ngIf="nodes.sumLiquidity > 100000000; else smallnode" [satoshis]="nodes.sumLiquidity" [digitsInfo]="'1.2-2'" [noFiat]="false"></app-amount> | ||||||
|  |                 <ng-template #smallnode> | ||||||
|  |                   {{ nodes.sumLiquidity | amountShortener: 1 }} | ||||||
|  |                   <span class="sats" i18n="shared.sats">sats</span> | ||||||
|  |                 </ng-template> | ||||||
|  |                 <span class="d-none d-md-inline-block"> </span> | ||||||
|  |                 <span class="d-block d-md-none"></span> | ||||||
|  |                 <app-fiat [value]="nodes.sumLiquidity" digitsInfo="1.0-0"></app-fiat> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr> | ||||||
|  |               <td i18n="lightning.channels">Channels</td> | ||||||
|  |               <td>{{ nodes.sumChannels }}</td> | ||||||
|  |             </tr> | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |       <div class="col-12 col-md-6 p-3 p-md-0 pr-md-3"> | ||||||
|  |         <div style="background-color: #181b2d"> | ||||||
|  |           <app-nodes-map [widget]="true" [nodes]="nodes.nodes" type="isp"></app-nodes-map> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   <br> | ||||||
|  | 
 | ||||||
|  |   <div class="toggle-holder"> | ||||||
|  |     <form [formGroup]="socketToggleForm" class="formRadioGroup"> | ||||||
|  |       <div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="socket"> | ||||||
|  |         <label ngbButtonLabel class="btn-primary btn-sm"> | ||||||
|  |           <input ngbButton type="radio" [value]="0">IPv4 | ||||||
|  |         </label> | ||||||
|  |         <label ngbButtonLabel class="btn-primary btn-sm"> | ||||||
|  |           <input ngbButton type="radio" [value]="1">IPv6 | ||||||
|  |         </label> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  |   <div style="min-height: 295px"> | ||||||
|  |     <table class="table table-borderless"> | ||||||
|  |       <thead> | ||||||
|  |         <th class="alias text-left" i18n="lightning.alias">Alias</th> | ||||||
|  |         <th class="text-left">Connect</th> | ||||||
|  |         <th class="city text-right d-none d-md-table-cell" i18n="lightning.location">Location</th> | ||||||
|  |       </thead> | ||||||
|  |       <tbody *ngIf="nodes$ | async as response; else skeleton"> | ||||||
|  |         <tr *ngFor="let node of response.nodes; let i = index; trackBy: trackByPublicKey"> | ||||||
|  |           <td class="alias text-left"> | ||||||
|  |             <div class="text-truncate"> | ||||||
|  |             <a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a> | ||||||
|  |             <div class="second-line">{{ node.opened_channel_count }} channel(s), <app-amount *ngIf="node.capacity > 100000000; else smallnode" [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount> | ||||||
|  |               <ng-template #smallnode> | ||||||
|  |                 {{ node.capacity | amountShortener: 1 }} <span class="sats" i18n="shared.sats">sats</span> | ||||||
|  |               </ng-template> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           </td> | ||||||
|  |           <td class="timestamp-first text-left"> | ||||||
|  |             <div class="input-group" *ngIf="node.socketsObject.length"> | ||||||
|  |               <ng-template #noDropdown> | ||||||
|  |                 <span class="input-group-text" id="basic-addon3">{{ node.socketsObject[selectedSocketIndex].label }}</span> | ||||||
|  |               </ng-template> | ||||||
|  |               <input type="text" class="form-control" aria-label="Text input with dropdown button" | ||||||
|  |                 [value]="node.socketsObject[selectedSocketIndex].socket"> | ||||||
|  |               <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04" (mouseover)="qrCodeVisible[i] = 1" | ||||||
|  |                 (mouseout)="qrCodeVisible[i] = 0"> | ||||||
|  |                 <fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon> | ||||||
|  |                 <div class="qr-wrapper" [hidden]="!qrCodeVisible[i]"> | ||||||
|  |                   <app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode> | ||||||
|  |                 </div> | ||||||
|  |               </button> | ||||||
|  |               <button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04"> | ||||||
|  |                 <app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket" [leftPadding]="false"></app-clipboard> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </td> | ||||||
|  |           <td class="city text-right text-truncate d-none d-md-table-cell"> | ||||||
|  |             <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"> | ||||||
|  |               <span class="skeleton-loader"></span> | ||||||
|  |             </td> | ||||||
|  |             <td> | ||||||
|  |               <span class="skeleton-loader"></span> | ||||||
|  |             </td> | ||||||
|  |             <td class="timestamp-update d-none d-md-table-cell"> | ||||||
|  |               <span class="skeleton-loader"></span> | ||||||
|  |             </td> | ||||||
|  |           </tr> | ||||||
|  |         </tbody> | ||||||
|  |       </ng-template> | ||||||
|  |    | ||||||
|  |     </table> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
							
								
								
									
										59
									
								
								frontend/src/app/lightning/group/group.component.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								frontend/src/app/lightning/group/group.component.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | .logo-container { | ||||||
|  |   width: 50px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .header { | ||||||
|  |   text-align: center; | ||||||
|  |   display: flex; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h1 { | ||||||
|  |   margin-left: 15px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .qr-wrapper { | ||||||
|  |   background-color: #FFF; | ||||||
|  |   padding: 10px; | ||||||
|  |   padding-bottom: 5px; | ||||||
|  |   display: inline-block; | ||||||
|  | 
 | ||||||
|  |    | ||||||
|  |   position: absolute; | ||||||
|  |   bottom: 50px; | ||||||
|  |   left: -175px; | ||||||
|  |   z-index: 100; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dropdownLabel { | ||||||
|  |   min-width: 50px; | ||||||
|  |   display: inline-block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #inputGroupFileAddon04 { | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .toggle-holder { | ||||||
|  |   display: flex; | ||||||
|  |   width: 100%; | ||||||
|  |   justify-content: flex-end; | ||||||
|  | } | ||||||
|  | @media (max-width: 767.98px) { | ||||||
|  |   .text-truncate { | ||||||
|  |     width: 120px; | ||||||
|  |   } | ||||||
|  |   .btn { | ||||||
|  |     padding: 0.25rem 0.5rem; | ||||||
|  |     font-size: 0.875rem; | ||||||
|  |     line-height: 1.5; | ||||||
|  |     border-radius: 0.2rem; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .second-line { | ||||||
|  |   font-size: 12px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .description-text { | ||||||
|  |   white-space: break-spaces; | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								frontend/src/app/lightning/group/group.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/src/app/lightning/group/group.component.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | import { Component, OnInit } from '@angular/core'; | ||||||
|  | import { FormBuilder, FormGroup } from '@angular/forms'; | ||||||
|  | import { map, Observable, share } from 'rxjs'; | ||||||
|  | import { SeoService } from 'src/app/services/seo.service'; | ||||||
|  | import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; | ||||||
|  | import { LightningApiService } from '../lightning-api.service'; | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-group', | ||||||
|  |   templateUrl: './group.component.html', | ||||||
|  |   styleUrls: ['./group.component.scss'] | ||||||
|  | }) | ||||||
|  | export class GroupComponent implements OnInit { | ||||||
|  |   nodes$: Observable<any>; | ||||||
|  |   isp: {name: string, id: number}; | ||||||
|  | 
 | ||||||
|  |   skeletonLines: number[] = []; | ||||||
|  |   selectedSocketIndex = 0; | ||||||
|  |   qrCodeVisible = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ||||||
|  |   socketToggleForm: FormGroup; | ||||||
|  | 
 | ||||||
|  |   constructor( | ||||||
|  |     private lightningApiService: LightningApiService, | ||||||
|  |     private seoService: SeoService, | ||||||
|  |     private formBuilder: FormBuilder, | ||||||
|  |   ) { | ||||||
|  |     for (let i = 0; i < 20; ++i) { | ||||||
|  |       this.skeletonLines.push(i); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ngOnInit(): void { | ||||||
|  |     this.socketToggleForm = this.formBuilder.group({ | ||||||
|  |       socket: [this.selectedSocketIndex], | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     this.socketToggleForm.get('socket').valueChanges.subscribe((val) => { | ||||||
|  |       this.selectedSocketIndex = val; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     this.seoService.setTitle(`Mempool.space Lightning Nodes`); | ||||||
|  | 
 | ||||||
|  |     this.nodes$ = this.lightningApiService.getNodGroupNodes$('mempool.space') | ||||||
|  |       .pipe( | ||||||
|  |         map((nodes) => { | ||||||
|  |           for (const node of nodes) { | ||||||
|  |             const socketsObject = []; | ||||||
|  |             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'; | ||||||
|  |               } | ||||||
|  |               socketsObject.push({ | ||||||
|  |                 label: label, | ||||||
|  |                 socket: node.public_key + '@' + socket, | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  |             // @ts-ignore
 | ||||||
|  |             node.socketsObject = socketsObject; | ||||||
|  | 
 | ||||||
|  |             if (!node?.country && !node?.city && | ||||||
|  |               !node?.subdivision) { | ||||||
|  |                 // @ts-ignore
 | ||||||
|  |                 node.geolocation = null; | ||||||
|  |             } else { | ||||||
|  |               // @ts-ignore
 | ||||||
|  |               node.geolocation = <GeolocationData>{ | ||||||
|  |                 country: node.country?.en, | ||||||
|  |                 city: node.city?.en, | ||||||
|  |                 subdivision: node.subdivision?.en, | ||||||
|  |                 iso: node.iso_code, | ||||||
|  |               }; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           const sumLiquidity = nodes.reduce((partialSum, a) => partialSum + parseInt(a.capacity, 10), 0); | ||||||
|  |           const sumChannels = nodes.reduce((partialSum, a) => partialSum + a.opened_channel_count, 0); | ||||||
|  |            | ||||||
|  |           return { | ||||||
|  |             nodes: nodes, | ||||||
|  |             sumLiquidity: sumLiquidity, | ||||||
|  |             sumChannels: sumChannels, | ||||||
|  |           }; | ||||||
|  |         }), | ||||||
|  |         share() | ||||||
|  |       ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   trackByPublicKey(index: number, node: any): string { | ||||||
|  |     return node.public_key; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   changeSocket(index: number) { | ||||||
|  |     this.selectedSocketIndex = index; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -27,6 +27,10 @@ export class LightningApiService { | |||||||
|     return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey); |     return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   getNodGroupNodes$(name: string): Observable<any[]> { | ||||||
|  |     return this.httpClient.get<any[]>(this.apiBasePath + '/api/v1/lightning/nodes/group/' + name); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getChannel$(shortId: string): Observable<any> { |   getChannel$(shortId: string): Observable<any> { | ||||||
|     return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels/' + shortId); |     return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels/' + shortId); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -84,3 +84,10 @@ | |||||||
| 
 | 
 | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <div class="text-small text-center mt-1" *ngIf="officialMempoolSpace"> | ||||||
|  |   <a [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]">Connect to our nodes</a> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <br> | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import { Observable } from 'rxjs'; | |||||||
| import { share } from 'rxjs/operators'; | import { share } from 'rxjs/operators'; | ||||||
| import { INodesRanking } from 'src/app/interfaces/node-api.interface'; | import { INodesRanking } from 'src/app/interfaces/node-api.interface'; | ||||||
| import { SeoService } from 'src/app/services/seo.service'; | import { SeoService } from 'src/app/services/seo.service'; | ||||||
|  | import { StateService } from 'src/app/services/state.service'; | ||||||
| import { LightningApiService } from '../lightning-api.service'; | import { LightningApiService } from '../lightning-api.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
| @ -14,10 +15,12 @@ import { LightningApiService } from '../lightning-api.service'; | |||||||
| export class LightningDashboardComponent implements OnInit { | export class LightningDashboardComponent implements OnInit { | ||||||
|   statistics$: Observable<any>; |   statistics$: Observable<any>; | ||||||
|   nodesRanking$: Observable<INodesRanking>; |   nodesRanking$: Observable<INodesRanking>; | ||||||
|  |   officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private lightningApiService: LightningApiService, |     private lightningApiService: LightningApiService, | ||||||
|     private seoService: SeoService, |     private seoService: SeoService, | ||||||
|  |     private stateService: StateService, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  | |||||||
| @ -30,6 +30,7 @@ import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-ca | |||||||
| import { OldestNodes } from '../lightning/nodes-ranking/oldest-nodes/oldest-nodes.component'; | import { OldestNodes } from '../lightning/nodes-ranking/oldest-nodes/oldest-nodes.component'; | ||||||
| import { NodesRankingsDashboard } from '../lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component'; | import { NodesRankingsDashboard } from '../lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component'; | ||||||
| import { NodeChannels } from '../lightning/nodes-channels/node-channels.component'; | import { NodeChannels } from '../lightning/nodes-channels/node-channels.component'; | ||||||
|  | import { GroupComponent } from './group/group.component'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|   declarations: [ |   declarations: [ | ||||||
| @ -58,6 +59,7 @@ import { NodeChannels } from '../lightning/nodes-channels/node-channels.componen | |||||||
|     OldestNodes, |     OldestNodes, | ||||||
|     NodesRankingsDashboard, |     NodesRankingsDashboard, | ||||||
|     NodeChannels, |     NodeChannels, | ||||||
|  |     GroupComponent, | ||||||
|   ], |   ], | ||||||
|   imports: [ |   imports: [ | ||||||
|     CommonModule, |     CommonModule, | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import { NodesPerCountry } from './nodes-per-country/nodes-per-country.component | |||||||
| import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component'; | import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component'; | ||||||
| import { NodesRanking } from './nodes-ranking/nodes-ranking.component'; | import { NodesRanking } from './nodes-ranking/nodes-ranking.component'; | ||||||
| import { NodesRankingsDashboard } from './nodes-rankings-dashboard/nodes-rankings-dashboard.component'; | import { NodesRankingsDashboard } from './nodes-rankings-dashboard/nodes-rankings-dashboard.component'; | ||||||
|  | import { GroupComponent } from './group/group.component'; | ||||||
| 
 | 
 | ||||||
| const routes: Routes = [ | const routes: Routes = [ | ||||||
|     { |     { | ||||||
| @ -34,6 +35,10 @@ const routes: Routes = [ | |||||||
|           path: 'nodes/isp/:isp', |           path: 'nodes/isp/:isp', | ||||||
|           component: NodesPerISP, |           component: NodesPerISP, | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           path: 'group/the-mempool-open-source-project', | ||||||
|  |           component: GroupComponent, | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|           path: 'nodes/rankings', |           path: 'nodes/rankings', | ||||||
|           component: NodesRankingsDashboard, |           component: NodesRankingsDashboard, | ||||||
|  | |||||||
| @ -46,7 +46,9 @@ export class NodesMap implements OnInit, OnChanges { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.seoService.setTitle($localize`Lightning nodes world map`); |     if (!this.widget) { | ||||||
|  |       this.seoService.setTitle($localize`Lightning nodes world map`); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (!this.inputNodes$) { |     if (!this.inputNodes$) { | ||||||
|       this.inputNodes$ = new BehaviorSubject(this.nodes); |       this.inputNodes$ = new BehaviorSubject(this.nodes); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user