Channel pagination
This commit is contained in:
parent
11a7babbc4
commit
473cb55dc4
@ -34,7 +34,7 @@ export class AddressLabelsComponent implements OnChanges {
|
||||
}
|
||||
|
||||
handleChannel() {
|
||||
this.label = `Channel open: ${this.channel.alias_left} <> ${this.channel.alias_right}`;
|
||||
this.label = `Channel open: ${this.channel.node_left.alias} <> ${this.channel.node_right.alias}`;
|
||||
}
|
||||
|
||||
handleVin() {
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelBoxComponent } from './channel-box.component';
|
||||
|
||||
describe('ChannelBoxComponent', () => {
|
||||
let component: ChannelBoxComponent;
|
||||
let fixture: ComponentFixture<ChannelBoxComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ChannelBoxComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelBoxComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,44 +1,39 @@
|
||||
<div>
|
||||
<div *ngIf="channels$ | async as response; else skeleton">
|
||||
<h2 class="float-left">Channels ({{ response.totalItems }})</h2>
|
||||
|
||||
<form [formGroup]="channelStatusForm" class="formRadioGroup float-right">
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="status">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'open'" fragment="open"> Open
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'closed'" fragment="closed"> Closed
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<th class="alias text-left" i18n="nodes.alias">Node Alias</th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="channels.transaction"> </th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="nodes.alias">Status</th>
|
||||
<th class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
||||
<th class="capacity text-right d-none d-md-table-cell" i18n="nodes.capacity">Capacity</th>
|
||||
<th class="capacity text-left" i18n="channels.id">Channel ID</th>
|
||||
</thead>
|
||||
<tbody *ngIf="channels$ | async as channels; else skeleton">
|
||||
<tr *ngFor="let channel of channels; let i = index;">
|
||||
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
||||
<tbody>
|
||||
<tr *ngFor="let channel of response.channels; let i = index;">
|
||||
<ng-container *ngTemplateOutlet="tableTemplate; context: { $implicit: channel, node: channel.node_left.public_key === publicKey ? channel.node_right : channel.node_left }"></ng-container>
|
||||
</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" style="width: 370px;">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="capacity text-left d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-left d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-right d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination class="pagination-container float-right" [size]="paginationSize" [collectionSize]="response.totalItems" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="paginationMaxSize" [boundaryLinks]="true" [ellipses]="false"></ngb-pagination>
|
||||
</div>
|
||||
|
||||
<ng-template #tableHeader>
|
||||
<thead>
|
||||
<th class="alias text-left" i18n="nodes.alias">Node Alias</th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="channels.transaction"> </th>
|
||||
<th class="alias text-left d-none d-md-table-cell" i18n="nodes.alias">Status</th>
|
||||
<th class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
||||
<th class="capacity text-right d-none d-md-table-cell" i18n="nodes.capacity">Capacity</th>
|
||||
<th class="capacity text-right" i18n="channels.id">Channel ID</th>
|
||||
</thead>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tableTemplate let-channel let-node="node">
|
||||
<td class="alias text-left">
|
||||
@ -50,7 +45,7 @@
|
||||
<app-clipboard [text]="node.public_key" size="small"></app-clipboard>
|
||||
</div>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<td class="alias text-left d-none d-md-table-cell">
|
||||
<div class="second-line">{{ node.channels }} channels</div>
|
||||
<div class="second-line"><app-amount [satoshis]="node.capacity" digitsInfo="1.2-2"></app-amount></div>
|
||||
</td>
|
||||
@ -65,7 +60,37 @@
|
||||
<td class="capacity text-right d-none d-md-table-cell">
|
||||
<app-amount [satoshis]="channel.capacity" digitsInfo="1.2-2"></app-amount>
|
||||
</td>
|
||||
<td class="capacity text-left">
|
||||
<td class="capacity text-right">
|
||||
<a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]">{{ channel.short_id }}</a>
|
||||
</td>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #skeleton>
|
||||
<h2 class="float-left">Channels</h2>
|
||||
|
||||
<table class="table table-borderless">
|
||||
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||
<td class="alias text-left" style="width: 370px;">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="capacity text-left d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-left d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-right d-none d-md-table-cell">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-template>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { BehaviorSubject, combineLatest, merge, Observable, of } from 'rxjs';
|
||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { LightningApiService } from '../lightning-api.service';
|
||||
|
||||
@Component({
|
||||
@ -8,16 +10,53 @@ import { LightningApiService } from '../lightning-api.service';
|
||||
styleUrls: ['./channels-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ChannelsListComponent implements OnChanges {
|
||||
export class ChannelsListComponent implements OnInit, OnChanges {
|
||||
@Input() publicKey: string;
|
||||
channels$: Observable<any[]>;
|
||||
channels$: Observable<any>;
|
||||
|
||||
// @ts-ignore
|
||||
paginationSize: 'sm' | 'lg' = 'md';
|
||||
paginationMaxSize = 10;
|
||||
itemsPerPage = 25;
|
||||
page = 1;
|
||||
channelsPage$ = new BehaviorSubject<number>(1);
|
||||
channelStatusForm: FormGroup;
|
||||
defaultStatus = 'open';
|
||||
|
||||
constructor(
|
||||
private lightningApiService: LightningApiService,
|
||||
) { }
|
||||
private formBuilder: FormBuilder,
|
||||
) {
|
||||
this.channelStatusForm = this.formBuilder.group({
|
||||
status: [this.defaultStatus],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (document.body.clientWidth < 670) {
|
||||
this.paginationSize = 'sm';
|
||||
this.paginationMaxSize = 3;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.channels$ = this.lightningApiService.getChannelsByNodeId$(this.publicKey);
|
||||
this.channels$ = combineLatest([
|
||||
this.channelsPage$,
|
||||
this.channelStatusForm.get('status').valueChanges.pipe(startWith(this.defaultStatus))
|
||||
])
|
||||
.pipe(
|
||||
switchMap(([page, status]) =>this.lightningApiService.getChannelsByNodeId$(this.publicKey, (page -1) * this.itemsPerPage, status)),
|
||||
map((response) => {
|
||||
return {
|
||||
channels: response.body,
|
||||
totalItems: parseInt(response.headers.get('x-total-count'), 10)
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
pageChange(page: number) {
|
||||
this.channelsPage$.next(page);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ export class LightningApiService {
|
||||
return this.httpClient.get<any>(API_BASE_URL + '/channels/' + shortId);
|
||||
}
|
||||
|
||||
getChannelsByNodeId$(publicKey: string): Observable<any> {
|
||||
getChannelsByNodeId$(publicKey: string, index: number = 0, status = 'open'): Observable<any> {
|
||||
let params = new HttpParams()
|
||||
.set('public_key', publicKey)
|
||||
.set('index', index)
|
||||
.set('status', status)
|
||||
;
|
||||
|
||||
return this.httpClient.get<any>(API_BASE_URL + '/channels', { params });
|
||||
return this.httpClient.get<any>(API_BASE_URL + '/channels', { params, observe: 'response' });
|
||||
}
|
||||
|
||||
getLatestStatistics$(): Observable<any> {
|
||||
|
@ -88,7 +88,6 @@
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<h2>Channels</h2>
|
||||
|
||||
<app-channels-list [publicKey]="node.public_key"></app-channels-list>
|
||||
|
||||
|
@ -73,10 +73,16 @@ class ChannelsApi {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getChannelsForNode(public_key: string): Promise<any> {
|
||||
public async $getChannelsForNode(public_key: string, index: number, length: number, status: string): Promise<any[]> {
|
||||
try {
|
||||
const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.*, ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key WHERE (ns1.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node1_public_key) AND ns2.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node2_public_key)) AND (node1_public_key = ? OR node2_public_key = ?) ORDER BY channels.capacity DESC`;
|
||||
const [rows]: any = await DB.query(query, [public_key, public_key]);
|
||||
// Default active and inactive channels
|
||||
let statusQuery = '< 2';
|
||||
// Closed channels only
|
||||
if (status === 'closed') {
|
||||
statusQuery = '= 2';
|
||||
}
|
||||
const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.*, ns1.channels AS channels_left, ns1.capacity AS capacity_left, ns2.channels AS channels_right, ns2.capacity AS capacity_right FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key LEFT JOIN node_stats AS ns1 ON ns1.public_key = channels.node1_public_key LEFT JOIN node_stats AS ns2 ON ns2.public_key = channels.node2_public_key WHERE (ns1.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node1_public_key) AND ns2.id = (SELECT MAX(id) FROM node_stats WHERE public_key = channels.node2_public_key)) AND (node1_public_key = ? OR node2_public_key = ?) AND status ${statusQuery} ORDER BY channels.capacity DESC LIMIT ?, ?`;
|
||||
const [rows]: any = await DB.query(query, [public_key, public_key, index, length]);
|
||||
const channels = rows.map((row) => this.convertChannel(row));
|
||||
return channels;
|
||||
} catch (e) {
|
||||
@ -85,13 +91,30 @@ class ChannelsApi {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getChannelsCountForNode(public_key: string, status: string): Promise<any> {
|
||||
try {
|
||||
// Default active and inactive channels
|
||||
let statusQuery = '< 2';
|
||||
// Closed channels only
|
||||
if (status === 'closed') {
|
||||
statusQuery = '= 2';
|
||||
}
|
||||
const query = `SELECT COUNT(*) AS count FROM channels WHERE (node1_public_key = ? OR node2_public_key = ?) AND status ${statusQuery}`;
|
||||
const [rows]: any = await DB.query(query, [public_key, public_key]);
|
||||
return rows[0]['count'];
|
||||
} catch (e) {
|
||||
logger.err('$getChannelsForNode error: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private convertChannel(channel: any): any {
|
||||
return {
|
||||
'id': channel.id,
|
||||
'short_id': channel.short_id,
|
||||
'capacity': channel.capacity,
|
||||
'transaction_id': channel.transaction_id,
|
||||
'transaction_vout': channel.void,
|
||||
'transaction_vout': channel.transaction_vout,
|
||||
'updated_at': channel.updated_at,
|
||||
'created': channel.created,
|
||||
'status': channel.status,
|
||||
|
@ -10,7 +10,7 @@ class ChannelsRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'channels/txids', this.$getChannelsByTransactionIds)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'channels/search/:search', this.$searchChannelsById)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'channels/:short_id', this.$getChannel)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'channels', this.$getChannels)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'channels', this.$getChannelsForNode)
|
||||
;
|
||||
}
|
||||
|
||||
@ -36,13 +36,18 @@ class ChannelsRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
private async $getChannels(req: Request, res: Response) {
|
||||
private async $getChannelsForNode(req: Request, res: Response) {
|
||||
try {
|
||||
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);
|
||||
const index = parseInt(typeof req.query.index === 'string' ? req.query.index : '0', 10) || 0;
|
||||
const status: string = typeof req.query.status === 'string' ? req.query.status : '';
|
||||
const length = 25;
|
||||
const channels = await channelsApi.$getChannelsForNode(req.query.public_key, index, length, status);
|
||||
const channelsCount = await channelsApi.$getChannelsCountForNode(req.query.public_key, status);
|
||||
res.header('X-Total-Count', channelsCount.toString());
|
||||
res.json(channels);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user