Add basic lightning justice page
This commit is contained in:
		
							parent
							
								
									8ad4b952ea
								
							
						
					
					
						commit
						4ba552fe1b
					
				@ -117,6 +117,25 @@ class ChannelsApi {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getPenaltyClosedChannels(): 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.status = 2 AND channels.closing_reason = 3
 | 
			
		||||
      `;
 | 
			
		||||
      const [rows]: any = await DB.query(query);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$getPenaltyClosedChannels error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getUnresolvedClosedChannels(): Promise<any[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `SELECT * FROM channels WHERE status = 2 AND closing_reason = 2 AND closing_resolved = 0 AND closing_transaction_id != ''`;
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ class ChannelsRoutes {
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/search/:search', this.$searchChannelsById)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels/:short_id', this.$getChannel)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels', this.$getChannelsForNode)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/penalties', this.$getPenaltyClosedChannels)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels-geo', this.$getAllChannelsGeo)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/channels-geo/:publicKey', this.$getAllChannelsGeo)
 | 
			
		||||
    ;
 | 
			
		||||
@ -108,6 +109,18 @@ class ChannelsRoutes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getPenaltyClosedChannels(req: Request, res: Response): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const channels = await channelsApi.$getPenaltyClosedChannels();
 | 
			
		||||
      res.header('Pragma', 'public');
 | 
			
		||||
      res.header('Cache-control', 'public');
 | 
			
		||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
			
		||||
      res.json(channels);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $getAllChannelsGeo(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const style: string = typeof req.query.style === 'string' ? req.query.style : '';
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,7 @@
 | 
			
		||||
                          {{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
 | 
			
		||||
                        </ng-template>
 | 
			
		||||
                        <div>
 | 
			
		||||
                          <app-address-labels [vin]="vin" [channel]="tx._channels && tx._channels.inputs[vindex] || null"></app-address-labels>
 | 
			
		||||
                          <app-address-labels [vin]="vin" [channel]="tx._channels && tx._channels.inputs[vindex] ? tx._channels.inputs[vindex] : null"></app-address-labels>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </ng-template>
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
 | 
			
		||||
@ -266,6 +266,7 @@ export interface IChannel {
 | 
			
		||||
  closing_transaction_id: string;
 | 
			
		||||
  closing_reason: string;
 | 
			
		||||
  updated_at: string;
 | 
			
		||||
  closing_date?: string;
 | 
			
		||||
  created: string;
 | 
			
		||||
  status: number;
 | 
			
		||||
  node_left: INode,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,79 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div class="container-xl full-height" style="min-height: 335px">
 | 
			
		||||
  <h1 class="float-left" i18n="lightning.liquidity-ranking">Penalties</h1>
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
  <div style="min-height: 295px">
 | 
			
		||||
    <table class="table table-borderless">
 | 
			
		||||
      <thead>
 | 
			
		||||
        <th class="timestamp" i18n="lightning.closed-at">Closed</th>
 | 
			
		||||
        <th class="channels text-right" i18n="lightning.capacity">Capacity</th>
 | 
			
		||||
        <th class="node text-right"></th>
 | 
			
		||||
        <th class="node text-right" i18n="lightning.node">Nodes</th>
 | 
			
		||||
        <th class="channelid text-right" i18n="channels.id">Channel ID</th>
 | 
			
		||||
        <th></th>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody *ngIf="justiceChannels$ | async as channels">
 | 
			
		||||
        <ng-container *ngFor="let channel of channels;">
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td class="timestamp">
 | 
			
		||||
              ‎{{ channel.closing_date | date:'yyyy-MM-dd HH:mm' }}
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="capacity text-right">
 | 
			
		||||
              <app-amount *ngIf="channel.capacity > 100000000; else smallnode" [satoshis]="channel.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
 | 
			
		||||
              <ng-template #smallnode>
 | 
			
		||||
                {{ channel.capacity | amountShortener: 1 }}
 | 
			
		||||
                <span class="sats" i18n="shared.sats">sats</span>
 | 
			
		||||
              </ng-template>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="alias text-right">
 | 
			
		||||
              <app-truncate [text]="channel.alias_left || '?'" [maxWidth]="200" [lastChars]="6" textAlign="end" [inline]="true"></app-truncate>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="alias text-right">
 | 
			
		||||
              <app-truncate [text]="channel.alias_right || '?'" [maxWidth]="200" [lastChars]="6" textAlign="end" [inline]="true"></app-truncate>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="channelid text-right">
 | 
			
		||||
              <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">{{ channel.short_id }}</a>
 | 
			
		||||
             </td>
 | 
			
		||||
             <td class="text-right">
 | 
			
		||||
              <button type="button" class="btn btn-outline-info details-button btn-sm" (click)="toggleDetails(channel)"
 | 
			
		||||
                i18n="transaction.details|Transaction Details">Details</button>
 | 
			
		||||
             </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr *ngIf="channel.short_id === expanded">
 | 
			
		||||
            <ng-container *ngTemplateOutlet="channelTransactions"></ng-container>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <div class="clearfix"></div>
 | 
			
		||||
    <br>
 | 
			
		||||
  </div>
 | 
			
		||||
  
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #channelTransactions>
 | 
			
		||||
  <td colspan="6" *ngIf="transactions && !loadingTransactions else loadingTemplate;">
 | 
			
		||||
    <ng-template [ngIf]="transactions[0]">
 | 
			
		||||
      <div class="d-flex">
 | 
			
		||||
        <h5 i18n="lightning.opening-transaction">Opening transaction</h5>
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-transactions-list #txList1 [transactions]="[transactions[0]]" [showConfirmations]="true" [rowLimit]="5">
 | 
			
		||||
      </app-transactions-list>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
    <ng-template [ngIf]="transactions[1]">
 | 
			
		||||
      <div class="closing-header d-flex">
 | 
			
		||||
        <h5 style="margin: 0;" i18n="lightning.closing-transaction">Closing transaction</h5>  <app-closing-type [type]="3"></app-closing-type>
 | 
			
		||||
      </div>
 | 
			
		||||
      <app-transactions-list #txList2 [transactions]="[transactions[1]]" [showConfirmations]="true" [rowLimit]="5">
 | 
			
		||||
      </app-transactions-list>
 | 
			
		||||
    </ng-template>
 | 
			
		||||
  </td>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #loadingTemplate>
 | 
			
		||||
  <span>loading...</span>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
.container-xl {
 | 
			
		||||
  max-width: 1400px;
 | 
			
		||||
}
 | 
			
		||||
.container-xl.widget {
 | 
			
		||||
  padding-right: 0px;
 | 
			
		||||
  padding-left: 0px;
 | 
			
		||||
  padding-bottom: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tr, td, th {
 | 
			
		||||
  border: 0px;
 | 
			
		||||
  padding-top: 0.65rem !important;
 | 
			
		||||
  padding-bottom: 0.7rem !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clear-link {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pool {
 | 
			
		||||
  width: 15%;
 | 
			
		||||
  @media (max-width: 575px) {
 | 
			
		||||
    width: 75%;
 | 
			
		||||
  }
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  max-width: 160px;
 | 
			
		||||
}
 | 
			
		||||
.pool-name {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  vertical-align: text-top;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.liquidity {
 | 
			
		||||
  width: 10%;
 | 
			
		||||
  @media (max-width: 575px) {
 | 
			
		||||
    width: 25%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fiat {
 | 
			
		||||
  width: 15%;
 | 
			
		||||
  @media (min-width: 768px) and (max-width: 991px) {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
  @media (max-width: 575px) {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,60 @@
 | 
			
		||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { map, Observable, of, Subject, Subscription, switchMap, tap, zip } from 'rxjs';
 | 
			
		||||
import { IChannel } from '../../interfaces/node-api.interface';
 | 
			
		||||
import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { ElectrsApiService } from '../../services/electrs-api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-justice-list',
 | 
			
		||||
  templateUrl: './justice-list.component.html',
 | 
			
		||||
  styleUrls: ['./justice-list.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class JusticeList implements OnInit, OnDestroy {
 | 
			
		||||
  justiceChannels$: Observable<any[]>;
 | 
			
		||||
  fetchTransactions$: Subject<IChannel> = new Subject();
 | 
			
		||||
  transactionsSubscription: Subscription;
 | 
			
		||||
  transactions: Transaction[];
 | 
			
		||||
  expanded: string = null;
 | 
			
		||||
  loadingTransactions: boolean = true;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private apiService: LightningApiService,
 | 
			
		||||
    private electrsApiService: ElectrsApiService,
 | 
			
		||||
    private cd: ChangeDetectorRef,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.justiceChannels$ = this.apiService.getPenaltyClosedChannels$();
 | 
			
		||||
 | 
			
		||||
    this.transactionsSubscription = this.fetchTransactions$.pipe(
 | 
			
		||||
      tap(() => {
 | 
			
		||||
        this.loadingTransactions = true;
 | 
			
		||||
      }),
 | 
			
		||||
      switchMap((channel: IChannel) => {
 | 
			
		||||
        return zip([
 | 
			
		||||
          channel.transaction_id ? this.electrsApiService.getTransaction$(channel.transaction_id) : of(null),
 | 
			
		||||
          channel.closing_transaction_id ? this.electrsApiService.getTransaction$(channel.closing_transaction_id) : of(null),
 | 
			
		||||
        ]);
 | 
			
		||||
      }),
 | 
			
		||||
    ).subscribe((transactions) => {
 | 
			
		||||
      this.transactions = transactions;
 | 
			
		||||
      this.loadingTransactions = false;
 | 
			
		||||
      this.cd.markForCheck();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleDetails(channel: any): void {
 | 
			
		||||
    if (this.expanded === channel.short_id) {
 | 
			
		||||
      this.expanded = null;
 | 
			
		||||
    } else {
 | 
			
		||||
      this.expanded = channel.short_id;
 | 
			
		||||
      this.fetchTransactions$.next(channel);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy(): void {
 | 
			
		||||
    this.transactionsSubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
import { HttpClient, HttpParams } from '@angular/common/http';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
import { INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface';
 | 
			
		||||
import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
@ -84,6 +84,12 @@ export class LightningApiService {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getPenaltyClosedChannels$(): Observable<IChannel[]> {
 | 
			
		||||
    return this.httpClient.get<IChannel[]>(
 | 
			
		||||
      this.apiBasePath + '/api/v1/lightning/penalties'
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getOldestNodes$(): Observable<IOldestNodes[]> {
 | 
			
		||||
    return this.httpClient.get<IOldestNodes[]>(
 | 
			
		||||
      this.apiBasePath + '/api/v1/lightning/nodes/rankings/age'
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import { NodesChannelsMap } from '../lightning/nodes-channels-map/nodes-channels
 | 
			
		||||
import { NodesRanking } from '../lightning/nodes-ranking/nodes-ranking.component';
 | 
			
		||||
import { TopNodesPerChannels } from '../lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component';
 | 
			
		||||
import { TopNodesPerCapacity } from '../lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component';
 | 
			
		||||
import { JusticeList } from '../lightning/justice-list/justice-list.component';
 | 
			
		||||
import { OldestNodes } from '../lightning/nodes-ranking/oldest-nodes/oldest-nodes.component';
 | 
			
		||||
import { NodesRankingsDashboard } from '../lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component';
 | 
			
		||||
import { NodeChannels } from '../lightning/nodes-channels/node-channels.component';
 | 
			
		||||
@ -60,6 +61,7 @@ import { GroupComponent } from './group/group.component';
 | 
			
		||||
    NodesRanking,
 | 
			
		||||
    TopNodesPerChannels,
 | 
			
		||||
    TopNodesPerCapacity,
 | 
			
		||||
    JusticeList,
 | 
			
		||||
    OldestNodes,
 | 
			
		||||
    NodesRankingsDashboard,
 | 
			
		||||
    NodeChannels,
 | 
			
		||||
@ -97,6 +99,7 @@ import { GroupComponent } from './group/group.component';
 | 
			
		||||
    NodesRanking,
 | 
			
		||||
    TopNodesPerChannels,
 | 
			
		||||
    TopNodesPerCapacity,
 | 
			
		||||
    JusticeList,
 | 
			
		||||
    OldestNodes,
 | 
			
		||||
    NodesRankingsDashboard,
 | 
			
		||||
    NodeChannels,
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { NodesPerISP } from './nodes-per-isp/nodes-per-isp.component';
 | 
			
		||||
import { NodesRanking } from './nodes-ranking/nodes-ranking.component';
 | 
			
		||||
import { NodesRankingsDashboard } from './nodes-rankings-dashboard/nodes-rankings-dashboard.component';
 | 
			
		||||
import { GroupComponent } from './group/group.component';
 | 
			
		||||
import { JusticeList } from './justice-list/justice-list.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
@ -66,6 +67,10 @@ const routes: Routes = [
 | 
			
		||||
            type: 'oldest'
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: 'penalties',
 | 
			
		||||
          component: JusticeList,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          path: '**',
 | 
			
		||||
          redirectTo: ''
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null">
 | 
			
		||||
<span class="truncate" [style.max-width]="maxWidth ? maxWidth + 'px' : null" [style.justify-content]="textAlign" [class.inline]="inline">
 | 
			
		||||
    <ng-container *ngIf="link">
 | 
			
		||||
      <a [routerLink]="link" class="truncate-link">
 | 
			
		||||
        <ng-container *ngIf="rtl; then rtlTruncated; else ltrTruncated;"></ng-container>
 | 
			
		||||
 | 
			
		||||
@ -23,4 +23,8 @@
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
    flex-grow: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.inline {
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -11,6 +11,8 @@ export class TruncateComponent {
 | 
			
		||||
  @Input() link: any = null;
 | 
			
		||||
  @Input() lastChars: number = 4;
 | 
			
		||||
  @Input() maxWidth: number = null;
 | 
			
		||||
  @Input() inline: boolean = false;
 | 
			
		||||
  @Input() textAlign: 'start' | 'end' = 'start';
 | 
			
		||||
  rtl: boolean;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user