Channel component
This commit is contained in:
		
							parent
							
								
									f5325b3a6d
								
							
						
					
					
						commit
						795bb6a7a6
					
				
							
								
								
									
										144
									
								
								frontend/src/app/lightning/channel/channel.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								frontend/src/app/lightning/channel/channel.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,144 @@
 | 
			
		||||
<div class="container-xl" *ngIf="(channel$ | async) as channel">
 | 
			
		||||
  <div class="mb-2">
 | 
			
		||||
    <h1 i18n="shared.address" class="mb-0">Channel <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">{{ channel.id }}</a> <app-clipboard [text]="channel.id"></app-clipboard></h1>
 | 
			
		||||
    <div class="badges">
 | 
			
		||||
      <span class="badge rounded-pill badge-success">Open</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col-md">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Last update</td>
 | 
			
		||||
                <td>{{ channel.updated_at | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Transaction ID</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <a [routerLink]="['/tx' | relativeUrl, channel.transaction_id + ':' + channel.transaction_vout]" >
 | 
			
		||||
                    <span>{{ channel.transaction_id | shortenString : 10 }}</span>
 | 
			
		||||
                  </a>
 | 
			
		||||
                  <app-clipboard [text]="channel.transaction_id"></app-clipboard>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-100 d-block d-md-none"></div>
 | 
			
		||||
        <div class="col-md">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-received">Capacity</td>
 | 
			
		||||
                <td><app-sats [satoshis]="channel.capacity"></app-sats></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
    <h2>Peers</h2>
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col-md">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Node</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ channel.alias_left }}
 | 
			
		||||
                  <br>
 | 
			
		||||
                  <a [routerLink]="['/lightning/node' | relativeUrl, channel.node1_public_key]" >
 | 
			
		||||
                    {{ channel.node1_public_key | shortenString : 18 }}
 | 
			
		||||
                  </a>
 | 
			
		||||
                  <app-clipboard [text]="channel.node1_public_key"></app-clipboard>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Fee rate</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ channel.node1_fee_rate / 10000 | number }}%
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Base fee</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node1_base_fee_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Min HTLC</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node1_min_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Max HTLC</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node1_max_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-100 d-block d-md-none"></div>
 | 
			
		||||
        <div class="col-md">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Node</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ channel.alias_right }}
 | 
			
		||||
                  <br>
 | 
			
		||||
                  <a [routerLink]="['/lightning/node' | relativeUrl, channel.node2_public_key]" >
 | 
			
		||||
                    {{ channel.node2_public_key | shortenString : 18 }}
 | 
			
		||||
                  </a>
 | 
			
		||||
                  <app-clipboard [text]="channel.node1_public_key"></app-clipboard>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Fee rate</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  {{ channel.node2_fee_rate / 10000 | number }}%
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Base fee</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node2_base_fee_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Min HTLC</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node2_min_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Max HTLC</td>
 | 
			
		||||
                <td>
 | 
			
		||||
                  <app-sats [satoshis]="channel.node2_max_htlc_mtokens / 1000"></app-sats>
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
.badges {
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								frontend/src/app/lightning/channel/channel.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/src/app/lightning/channel/channel.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { switchMap } from 'rxjs/operators';
 | 
			
		||||
import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-channel',
 | 
			
		||||
  templateUrl: './channel.component.html',
 | 
			
		||||
  styleUrls: ['./channel.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class ChannelComponent implements OnInit {
 | 
			
		||||
  channel$: Observable<any>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private lightningApiService: LightningApiService,
 | 
			
		||||
    private activatedRoute: ActivatedRoute,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.channel$ = this.activatedRoute.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((params: ParamMap) => {
 | 
			
		||||
          return this.lightningApiService.getChannel$(params.get('short_id'));
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
<div>
 | 
			
		||||
 | 
			
		||||
  <table class="table table-borderless">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th class="alias text-left" i18n="nodes.alias">Node Alias</th>
 | 
			
		||||
      <th class="channels text-right" i18n="channels.rate">Fee Rate</th>
 | 
			
		||||
      <th class="capacity text-right" i18n="channels.id">Channel ID</th>
 | 
			
		||||
      <th class="capacity text-right" i18n="nodes.capacity">Capacity</th>
 | 
			
		||||
      <th class="alias text-right" i18n="channels.transaction">Transaction ID</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody *ngIf="channels$ | async as channels; else skeleton">
 | 
			
		||||
      <tr *ngFor="let channel of channels; let i = index;">
 | 
			
		||||
        <ng-template [ngIf]="channel.node2_public_key === publicKey" [ngIfElse]="right">
 | 
			
		||||
          <td class="alias text-left">
 | 
			
		||||
            <a [routerLink]="['/lightning/node' | relativeUrl, channel.node1_public_key]">{{ channel.alias_left }}</a>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="capacity text-right">
 | 
			
		||||
           {{ channel.node1_fee_rate / 10000 | number }}%
 | 
			
		||||
          </td>
 | 
			
		||||
        </ng-template>
 | 
			
		||||
        <ng-template #right>
 | 
			
		||||
          <td class="alias text-left" *ngIf="channel.node1_public_key === publicKey">
 | 
			
		||||
            <a [routerLink]="['/lightning/node' | relativeUrl, channel.node2_public_key]">{{ channel.alias_right }}</a>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="capacity text-right">
 | 
			
		||||
            {{ channel.node2_fee_rate / 10000 | number }}%
 | 
			
		||||
           </td>
 | 
			
		||||
        </ng-template>
 | 
			
		||||
        <td class="capacity text-right">
 | 
			
		||||
          <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">{{ channel.id }}</a>
 | 
			
		||||
         </td>
 | 
			
		||||
        <td class="capacity text-right">
 | 
			
		||||
          <app-amount [satoshis]="channel.capacity" digitsInfo="1.2-2"></app-amount>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="text-right">
 | 
			
		||||
          <a [routerLink]="['/tx' | relativeUrl, channel.transaction_id + ':' + channel.transaction_vout]" >
 | 
			
		||||
            <span>{{ channel.transaction_id | shortenString : 10 }}</span>
 | 
			
		||||
          </a>
 | 
			
		||||
          <app-clipboard [text]="channel.transaction_id"></app-clipboard>
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
    <ng-template #skeleton>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
 | 
			
		||||
          <td class="alias text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="capacity text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="channels text-left">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="channels text-right">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
          <td class="channels text-right">
 | 
			
		||||
            <span class="skeleton-loader"></span>
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-channels-list',
 | 
			
		||||
  templateUrl: './channels-list.component.html',
 | 
			
		||||
  styleUrls: ['./channels-list.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class ChannelsListComponent implements OnChanges {
 | 
			
		||||
  @Input() publicKey: string;
 | 
			
		||||
  channels$: Observable<any[]>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private lightningApiService: LightningApiService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(): void {
 | 
			
		||||
    this.channels$ = this.lightningApiService.getChannelsByNodeId$(this.publicKey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { HttpClient, HttpParams } from '@angular/common/http';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
const API_BASE_URL = '/lightning/api/v1';
 | 
			
		||||
@ -16,8 +16,16 @@ export class LightningApiService {
 | 
			
		||||
    return this.httpClient.get<any>(API_BASE_URL + '/nodes/' + publicKey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChannel$(shortId: string): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any>(API_BASE_URL + '/channels/' + shortId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChannelsByNodeId$(publicKey: string): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any>(API_BASE_URL + '/channels/' + publicKey);
 | 
			
		||||
    let params = new HttpParams()
 | 
			
		||||
      .set('public_key', publicKey)
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    return this.httpClient.get<any>(API_BASE_URL + '/channels', { params });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getLatestStatistics$(): Observable<any> {
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,16 @@ import { RouterModule } from '@angular/router';
 | 
			
		||||
import { NodeStatisticsComponent } from './node-statistics/node-statistics.component';
 | 
			
		||||
import { NodeComponent } from './node/node.component';
 | 
			
		||||
import { LightningRoutingModule } from './lightning.routing.module';
 | 
			
		||||
import { ChannelsListComponent } from './channels-list/channels-list.component';
 | 
			
		||||
import { ChannelComponent } from './channel/channel.component';
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    LightningDashboardComponent,
 | 
			
		||||
    NodesListComponent,
 | 
			
		||||
    NodeStatisticsComponent,
 | 
			
		||||
    NodeComponent,
 | 
			
		||||
    ChannelsListComponent,
 | 
			
		||||
    ChannelComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { LightningDashboardComponent } from './lightning-dashboard/lightning-dashboard.component';
 | 
			
		||||
import { NodeComponent } from './node/node.component';
 | 
			
		||||
import { ChannelComponent } from './channel/channel.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
@ -12,6 +13,10 @@ const routes: Routes = [
 | 
			
		||||
      path: 'node/:public_key',
 | 
			
		||||
      component: NodeComponent,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: 'channel/:short_id',
 | 
			
		||||
      component: ChannelComponent,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '**',
 | 
			
		||||
      redirectTo: ''
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@
 | 
			
		||||
      <h5 class="card-title" i18n="mining.average-fee">Capacity</h5>
 | 
			
		||||
      <div class="card-text" i18n-ngbTooltip="mining.average-fee"
 | 
			
		||||
        ngbTooltip="Fee paid on average for each transaction in the past 144 blocks" placement="bottom">
 | 
			
		||||
        <app-amount [satoshis]="statistics.latest.total_capacity" digitsInfo="1.2-3"></app-amount>
 | 
			
		||||
        <app-amount [satoshis]="statistics.latest.total_capacity" digitsInfo="1.2-2"></app-amount>
 | 
			
		||||
        <span class="fiat">
 | 
			
		||||
          <app-change [current]="statistics.latest.total_capacity" [previous]="statistics.previous.total_capacity"></app-change>
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
@ -1 +1,66 @@
 | 
			
		||||
<p>node works!</p>
 | 
			
		||||
<div class="container-xl" *ngIf="(node$ | async) as node">
 | 
			
		||||
  <div class="title-container mb-2">
 | 
			
		||||
    <h1 i18n="shared.address" class="mb-0">{{ node.alias }}</h1>
 | 
			
		||||
    <span class="tx-link">
 | 
			
		||||
      <a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]" >
 | 
			
		||||
        <span class="d-inline">{{ node.public_key | shortenString : 18 }}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <app-clipboard [text]="node.public_key"></app-clipboard>
 | 
			
		||||
    </span>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
    <div class="box">
 | 
			
		||||
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div class="col-md">
 | 
			
		||||
          <table class="table table-borderless table-striped">
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-received">First Seen</td>
 | 
			
		||||
                <td>{{ node.first_seen | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.total-sent">Updated At</td>
 | 
			
		||||
                <td>{{ node.updated_at | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <td i18n="address.balance">Color</td>
 | 
			
		||||
                <td><div [ngStyle]="{'color': node.color}">{{ node.color }}</div></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-100 d-block d-md-none"></div>
 | 
			
		||||
        <div class="col-md qrcode-col">
 | 
			
		||||
          <div class="qr-wrapper">
 | 
			
		||||
            <app-qrcode [data]="node.public_key"></app-qrcode>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <br>
 | 
			
		||||
    <h2>Channels</h2>
 | 
			
		||||
 | 
			
		||||
    <app-channels-list [publicKey]="node.public_key"></app-channels-list>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
    <div class="title-tx">
 | 
			
		||||
      <h2 class="text-left">
 | 
			
		||||
        <ng-template [ngIf]="!transactions?.length"> </ng-template>
 | 
			
		||||
        <ng-template i18n="X of X Address Transaction" [ngIf]="transactions?.length === 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction</ng-template>
 | 
			
		||||
        <ng-template i18n="X of X Address Transactions (Plural)" [ngIf]="transactions?.length > 1">{{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions</ng-template>
 | 
			
		||||
      </h2>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <app-transactions-list [transactions]="transactions" [showConfirmations]="true" [address]="address.address" (loadMore)="loadMore()"></app-transactions-list>
 | 
			
		||||
    -->
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
.qr-wrapper {
 | 
			
		||||
  background-color: #FFF;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  padding-bottom: 5px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.qrcode-col {
 | 
			
		||||
  margin: 20px auto 10px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  @media (min-width: 992px){
 | 
			
		||||
    margin: 0px auto 0px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tx-link {
 | 
			
		||||
  display: flex;
 | 
			
		||||
	flex-grow: 1;
 | 
			
		||||
	@media (min-width: 650px) {
 | 
			
		||||
    align-self: end;
 | 
			
		||||
    margin-left: 15px;
 | 
			
		||||
    margin-top: 0px;
 | 
			
		||||
    margin-bottom: -3px;
 | 
			
		||||
	}
 | 
			
		||||
	@media (min-width: 768px) {
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
    top: 1px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
	}
 | 
			
		||||
	@media (max-width: 768px) {
 | 
			
		||||
	  order: 3;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { switchMap } from 'rxjs/operators';
 | 
			
		||||
@ -7,10 +7,12 @@ import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-node',
 | 
			
		||||
  templateUrl: './node.component.html',
 | 
			
		||||
  styleUrls: ['./node.component.scss']
 | 
			
		||||
  styleUrls: ['./node.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class NodeComponent implements OnInit {
 | 
			
		||||
  node$: Observable<any>;
 | 
			
		||||
  publicKey$: Observable<string>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private lightningApiService: LightningApiService,
 | 
			
		||||
@ -21,7 +23,7 @@ export class NodeComponent implements OnInit {
 | 
			
		||||
    this.node$ = this.activatedRoute.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((params: ParamMap) => {
 | 
			
		||||
          return this.lightningApiService.getNode$(params.get('id'));
 | 
			
		||||
          return this.lightningApiService.getNode$(params.get('public_key'));
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number }}
 | 
			
		||||
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
 | 
			
		||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
 | 
			
		||||
<ng-template [ngIf]="network === 'testnet'">t-</ng-template>
 | 
			
		||||
<ng-template [ngIf]="network === 'signet'">s-</ng-template>sats</span>
 | 
			
		||||
							
								
								
									
										32
									
								
								frontend/src/app/shared/components/sats/sats.component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								frontend/src/app/shared/components/sats/sats.component.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { StateService } from '../../../services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-sats',
 | 
			
		||||
  templateUrl: './sats.component.html',
 | 
			
		||||
  styleUrls: ['./sats.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class SatsComponent implements OnInit {
 | 
			
		||||
  @Input() satoshis: number;
 | 
			
		||||
  @Input() digitsInfo = 0;
 | 
			
		||||
  @Input() addPlus = false;
 | 
			
		||||
 | 
			
		||||
  network = '';
 | 
			
		||||
  stateSubscription: Subscription;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.stateSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if (this.stateSubscription) {
 | 
			
		||||
      this.stateSubscription.unsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -74,6 +74,7 @@ import { LoadingIndicatorComponent } from '../components/loading-indicator/loadi
 | 
			
		||||
import { IndexingProgressComponent } from '../components/indexing-progress/indexing-progress.component';
 | 
			
		||||
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
 | 
			
		||||
import { ChangeComponent } from '../components/change/change.component';
 | 
			
		||||
import { SatsComponent } from './components/sats/sats.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -142,6 +143,7 @@ import { ChangeComponent } from '../components/change/change.component';
 | 
			
		||||
    IndexingProgressComponent,
 | 
			
		||||
    SvgImagesComponent,
 | 
			
		||||
    ChangeComponent,
 | 
			
		||||
    SatsComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
@ -238,6 +240,7 @@ import { ChangeComponent } from '../components/change/change.component';
 | 
			
		||||
    IndexingProgressComponent,
 | 
			
		||||
    SvgImagesComponent,
 | 
			
		||||
    ChangeComponent,
 | 
			
		||||
    SatsComponent
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class SharedModule {
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,20 @@ import logger from '../../logger';
 | 
			
		||||
import DB from '../../database';
 | 
			
		||||
 | 
			
		||||
class ChannelsApi {
 | 
			
		||||
  public async $getChannel(shortId: string): Promise<any> {
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* 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 WHERE channels.id = ?`;
 | 
			
		||||
      const [rows]: any = await DB.query(query, [shortId]);
 | 
			
		||||
      return rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$getChannel error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getChannelsForNode(public_key: string): Promise<any> {
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `SELECT * FROM channels WHERE node1_public_key = ? OR node2_public_key = ?`;
 | 
			
		||||
      const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* 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 WHERE node1_public_key = ? OR node2_public_key = ?`;
 | 
			
		||||
      const [rows]: any = await DB.query(query, [public_key, public_key]);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
@ -3,16 +3,36 @@ import { Express, Request, Response } from 'express';
 | 
			
		||||
import channelsApi from './channels.api';
 | 
			
		||||
 | 
			
		||||
class ChannelsRoutes {
 | 
			
		||||
  constructor(app: Express) {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public initRoutes(app: Express) {
 | 
			
		||||
    app
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'channels/:public_key', this.$getChannels)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'channels/:short_id', this.$getChannel)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'channels', this.$getChannels)
 | 
			
		||||
    ;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getChannel(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const channel = await channelsApi.$getChannel(req.params.short_id);
 | 
			
		||||
      if (!channel) {
 | 
			
		||||
        res.status(404).send('Channel not found');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      res.json(channel);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getChannels(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const channels = await channelsApi.$getChannelsForNode(req.params.public_key);
 | 
			
		||||
      res.json(channels);
 | 
			
		||||
      if (typeof req.query.public_key !== 'string') {
 | 
			
		||||
        res.status(501).send('Missing parameter: public_key');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
        const channels = await channelsApi.$getChannelsForNode(req.query.public_key);
 | 
			
		||||
        res.json(channels);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
@ -20,4 +40,4 @@ class ChannelsRoutes {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ChannelsRoutes;
 | 
			
		||||
export default new ChannelsRoutes();
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,15 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { Express, Request, Response } from 'express';
 | 
			
		||||
import nodesApi from './nodes.api';
 | 
			
		||||
import channelsApi from './channels.api';
 | 
			
		||||
class NodesRoutes {
 | 
			
		||||
  constructor(app: Express) {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public initRoutes(app: Express) {
 | 
			
		||||
    app
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode)
 | 
			
		||||
    ;
 | 
			
		||||
    .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats)
 | 
			
		||||
    .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes)
 | 
			
		||||
    .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode)
 | 
			
		||||
  ;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getNode(req: Request, res: Response) {
 | 
			
		||||
@ -47,4 +48,4 @@ class NodesRoutes {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default NodesRoutes;
 | 
			
		||||
export default new NodesRoutes();
 | 
			
		||||
 | 
			
		||||
@ -1,21 +1,14 @@
 | 
			
		||||
import config from './config';
 | 
			
		||||
import * as express from 'express';
 | 
			
		||||
import * as http from 'http';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
import DB from './database';
 | 
			
		||||
import { Express, Request, Response, NextFunction } from 'express';
 | 
			
		||||
import databaseMigration from './database-migration';
 | 
			
		||||
import statsUpdater from './tasks/stats-updater.service';
 | 
			
		||||
import nodeSyncService from './tasks/node-sync.service';
 | 
			
		||||
import NodesRoutes from './api/nodes/nodes.routes';
 | 
			
		||||
import ChannelsRoutes from './api/nodes/channels.routes';
 | 
			
		||||
import server from './server';
 | 
			
		||||
 | 
			
		||||
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
 | 
			
		||||
 | 
			
		||||
class LightningServer {
 | 
			
		||||
  private server: http.Server | undefined;
 | 
			
		||||
  private app: Express = express();
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.init();
 | 
			
		||||
  }
 | 
			
		||||
@ -27,27 +20,7 @@ class LightningServer {
 | 
			
		||||
    statsUpdater.startService();
 | 
			
		||||
    nodeSyncService.startService();
 | 
			
		||||
 | 
			
		||||
    this.startServer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startServer() {
 | 
			
		||||
    this.app
 | 
			
		||||
      .use((req: Request, res: Response, next: NextFunction) => {
 | 
			
		||||
        res.setHeader('Access-Control-Allow-Origin', '*');
 | 
			
		||||
        next();
 | 
			
		||||
      })
 | 
			
		||||
      .use(express.urlencoded({ extended: true }))
 | 
			
		||||
      .use(express.text())
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    this.server = http.createServer(this.app);
 | 
			
		||||
 | 
			
		||||
    this.server.listen(config.MEMPOOL.HTTP_PORT, () => {
 | 
			
		||||
      logger.notice(`Mempool Lightning is running on port ${config.MEMPOOL.HTTP_PORT}`);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const nodeRoutes = new NodesRoutes(this.app);
 | 
			
		||||
    const channelsRoutes = new ChannelsRoutes(this.app);
 | 
			
		||||
    server.startServer();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								lightning-backend/src/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lightning-backend/src/server.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
import { Express, Request, Response, NextFunction } from 'express';
 | 
			
		||||
import * as express from 'express';
 | 
			
		||||
import * as http from 'http';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import nodesRoutes from './api/nodes/nodes.routes';
 | 
			
		||||
import channelsRoutes from './api/nodes/channels.routes';
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
  private server: http.Server | undefined;
 | 
			
		||||
  private app: Express = express();
 | 
			
		||||
 | 
			
		||||
  public startServer() {
 | 
			
		||||
    this.app
 | 
			
		||||
      .use((req: Request, res: Response, next: NextFunction) => {
 | 
			
		||||
        res.setHeader('Access-Control-Allow-Origin', '*');
 | 
			
		||||
        next();
 | 
			
		||||
      })
 | 
			
		||||
      .use(express.urlencoded({ extended: true }))
 | 
			
		||||
      .use(express.text())
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
    this.server = http.createServer(this.app);
 | 
			
		||||
 | 
			
		||||
    this.server.listen(config.MEMPOOL.HTTP_PORT, () => {
 | 
			
		||||
      logger.notice(`Mempool Lightning is running on port ${config.MEMPOOL.HTTP_PORT}`);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.initRoutes();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private initRoutes() {
 | 
			
		||||
    nodesRoutes.initRoutes(this.app);
 | 
			
		||||
    channelsRoutes.initRoutes(this.app);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Server();
 | 
			
		||||
@ -15,12 +15,13 @@ class LightningStatsUpdater {
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.$logLightningStats();
 | 
			
		||||
      this.$logNodeStatsDaily();
 | 
			
		||||
      setInterval(() => {
 | 
			
		||||
        this.$logLightningStats();
 | 
			
		||||
        this.$logNodeStatsDaily();
 | 
			
		||||
      }, 1000 * 60 * 60);
 | 
			
		||||
    }, difference);
 | 
			
		||||
 | 
			
		||||
    this.$logNodeStatsDaily();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $logNodeStatsDaily() {
 | 
			
		||||
@ -28,7 +29,7 @@ class LightningStatsUpdater {
 | 
			
		||||
    try {
 | 
			
		||||
      const [state]: any = await DB.query(`SELECT string FROM state WHERE name = 'last_node_stats'`);
 | 
			
		||||
      // Only store once per day
 | 
			
		||||
      if (state[0] === currentDate) {
 | 
			
		||||
      if (state[0].string === currentDate) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -42,6 +43,7 @@ class LightningStatsUpdater {
 | 
			
		||||
            node.channels_count_left, node.channels_count_right]);
 | 
			
		||||
      }
 | 
			
		||||
      await DB.query(`UPDATE state SET string = ? WHERE name = 'last_node_stats'`, [currentDate]);
 | 
			
		||||
      logger.debug('Daily node stats has updated.');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$logNodeStatsDaily() error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
@ -49,22 +51,26 @@ class LightningStatsUpdater {
 | 
			
		||||
 | 
			
		||||
  private async $logLightningStats() {
 | 
			
		||||
    try {
 | 
			
		||||
      const networkInfo = await lightningApi.$getNetworkInfo();
 | 
			
		||||
      const networkGraph = await lightningApi.$getNetworkGraph();
 | 
			
		||||
      let total_capacity = 0;
 | 
			
		||||
      for (const channel of networkGraph.channels) {
 | 
			
		||||
        if (channel.capacity) {
 | 
			
		||||
          total_capacity += channel.capacity;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const query = `INSERT INTO statistics(
 | 
			
		||||
          added,
 | 
			
		||||
          channel_count,
 | 
			
		||||
          node_count,
 | 
			
		||||
          total_capacity,
 | 
			
		||||
          average_channel_size
 | 
			
		||||
          total_capacity
 | 
			
		||||
        )
 | 
			
		||||
        VALUES (NOW(), ?, ?, ?, ?)`;
 | 
			
		||||
        VALUES (NOW(), ?, ?, ?)`;
 | 
			
		||||
 | 
			
		||||
      await DB.query(query, [
 | 
			
		||||
        networkInfo.channel_count,
 | 
			
		||||
        networkInfo.node_count,
 | 
			
		||||
        networkInfo.total_capacity,
 | 
			
		||||
        networkInfo.average_channel_size
 | 
			
		||||
        networkGraph.channels.length,
 | 
			
		||||
        networkGraph.nodes.length,
 | 
			
		||||
        total_capacity,
 | 
			
		||||
      ]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$logLightningStats() error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user