Merge branch 'master' into nymkappa/bugfix/remove-duplicated-node-map
This commit is contained in:
commit
441a5fa2b4
@ -9,7 +9,10 @@ class NodesApi {
|
|||||||
geo_names_country.names as country, geo_names_subdivision.names as subdivision,
|
geo_names_country.names as country, geo_names_subdivision.names as subdivision,
|
||||||
(SELECT Count(*)
|
(SELECT Count(*)
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channels.status < 2 AND ( channels.node1_public_key = ? OR channels.node2_public_key = ? )) AS channel_count,
|
WHERE channels.status = 2 AND ( channels.node1_public_key = ? OR channels.node2_public_key = ? )) AS channel_closed_count,
|
||||||
|
(SELECT Count(*)
|
||||||
|
FROM channels
|
||||||
|
WHERE channels.status < 2 AND ( channels.node1_public_key = ? OR channels.node2_public_key = ? )) AS channel_active_count,
|
||||||
(SELECT Sum(capacity)
|
(SELECT Sum(capacity)
|
||||||
FROM channels
|
FROM channels
|
||||||
WHERE channels.status < 2 AND ( channels.node1_public_key = ? OR channels.node2_public_key = ? )) AS capacity,
|
WHERE channels.status < 2 AND ( channels.node1_public_key = ? OR channels.node2_public_key = ? )) AS capacity,
|
||||||
@ -23,7 +26,7 @@ class NodesApi {
|
|||||||
LEFT JOIN geo_names geo_names_country on geo_names_country.id = country_id
|
LEFT JOIN geo_names geo_names_country on geo_names_country.id = country_id
|
||||||
WHERE public_key = ?
|
WHERE public_key = ?
|
||||||
`;
|
`;
|
||||||
const [rows]: any = await DB.query(query, [public_key, public_key, public_key, public_key, public_key, public_key, public_key]);
|
const [rows]: any = await DB.query(query, [public_key, public_key, public_key, public_key, public_key, public_key, public_key, public_key, public_key]);
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
rows[0].as_organization = JSON.parse(rows[0].as_organization);
|
rows[0].as_organization = JSON.parse(rows[0].as_organization);
|
||||||
rows[0].subdivision = JSON.parse(rows[0].subdivision);
|
rows[0].subdivision = JSON.parse(rows[0].subdivision);
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless" *ngIf="response.channels.length > 1">
|
||||||
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let channel of response.channels; let i = index;">
|
<tr *ngFor="let channel of response.channels; let i = index;">
|
||||||
@ -19,7 +19,11 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
<ngb-pagination *ngIf="response.channels.length > 1" 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>
|
||||||
|
|
||||||
|
<table class="table table-borderless" *ngIf="response.channels.length === 0">
|
||||||
|
<div class="d-flex justify-content-center" i18n="lightning.empty-channels-list">No channels to display</div>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #tableHeader>
|
<ng-template #tableHeader>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { BehaviorSubject, combineLatest, merge, Observable, of } from 'rxjs';
|
import { BehaviorSubject, combineLatest, merge, Observable, of } from 'rxjs';
|
||||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||||
@ -12,6 +12,7 @@ import { LightningApiService } from '../lightning-api.service';
|
|||||||
})
|
})
|
||||||
export class ChannelsListComponent implements OnInit, OnChanges {
|
export class ChannelsListComponent implements OnInit, OnChanges {
|
||||||
@Input() publicKey: string;
|
@Input() publicKey: string;
|
||||||
|
@Output() channelsStatusChangedEvent = new EventEmitter<string>();
|
||||||
channels$: Observable<any>;
|
channels$: Observable<any>;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -41,13 +42,17 @@ export class ChannelsListComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.channelStatusForm.get('status').setValue(this.defaultStatus, { emitEvent: false })
|
this.channelStatusForm.get('status').setValue(this.defaultStatus, { emitEvent: false })
|
||||||
|
this.channelsStatusChangedEvent.emit(this.defaultStatus);
|
||||||
|
|
||||||
this.channels$ = combineLatest([
|
this.channels$ = combineLatest([
|
||||||
this.channelsPage$,
|
this.channelsPage$,
|
||||||
this.channelStatusForm.get('status').valueChanges.pipe(startWith(this.defaultStatus))
|
this.channelStatusForm.get('status').valueChanges.pipe(startWith(this.defaultStatus))
|
||||||
])
|
])
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(([page, status]) =>this.lightningApiService.getChannelsByNodeId$(this.publicKey, (page -1) * this.itemsPerPage, status)),
|
switchMap(([page, status]) => {
|
||||||
|
this.channelsStatusChangedEvent.emit(status);
|
||||||
|
return this.lightningApiService.getChannelsByNodeId$(this.publicKey, (page -1) * this.itemsPerPage, status);
|
||||||
|
}),
|
||||||
map((response) => {
|
map((response) => {
|
||||||
return {
|
return {
|
||||||
channels: response.body,
|
channels: response.body,
|
||||||
|
@ -1,126 +1,145 @@
|
|||||||
<div class="container-xl" *ngIf="(node$ | async) as node">
|
<div class="container-xl" *ngIf="(node$ | async) as node">
|
||||||
<div class="title-container mb-2">
|
<div class="title-container mb-2" *ngIf="!error">
|
||||||
<h1 class="mb-0">{{ node.alias }}</h1>
|
<h1 class="mb-0">{{ node.alias }}</h1>
|
||||||
<span class="tx-link">
|
<span class="tx-link">
|
||||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.public_key | shortenString : 12 }}</a>
|
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.public_key | shortenString : 12
|
||||||
|
}}</a>
|
||||||
<app-clipboard [text]="node.public_key"></app-clipboard>
|
<app-clipboard [text]="node.public_key"></app-clipboard>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
<div class="box">
|
<div *ngIf="error" class="d-flex flex-column justify-content-around align-items-center mt-5 w-100" style="min-height: 100px">
|
||||||
|
<span i18n="lightning.node-not-found">No node found for public key "{{ node.public_key | shortenString : 12}}"</span>
|
||||||
|
<a [routerLink]="['/lightning' | relativeUrl]" i18n="lightning.back-to-lightning-dashboard">Back to the lightning dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="box" *ngIf="!error">
|
||||||
<div class="col-md">
|
|
||||||
<table class="table table-borderless table-striped">
|
<div class="row">
|
||||||
<tbody>
|
<div class="col-md">
|
||||||
<tr>
|
<table class="table table-borderless table-striped">
|
||||||
<td i18n="address.total-received">Total capacity</td>
|
<tbody>
|
||||||
<td>
|
<tr>
|
||||||
<app-sats [satoshis]="node.capacity"></app-sats><app-fiat [value]="node.capacity" digitsInfo="1.0-0"></app-fiat>
|
<td i18n="address.total-received">Total capacity</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<app-sats [satoshis]="node.capacity"></app-sats>
|
||||||
<tr>
|
<app-fiat [value]="node.capacity" digitsInfo="1.0-0"></app-fiat>
|
||||||
<td i18n="address.total-sent">Total channels</td>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
{{ node.channel_count }}
|
<tr>
|
||||||
</td>
|
<td i18n="address.total-sent">Total channels</td>
|
||||||
</tr>
|
<td>
|
||||||
<tr>
|
{{ node.channel_active_count }}
|
||||||
<td i18n="address.total-received">Average channel size</td>
|
</td>
|
||||||
<td>
|
</tr>
|
||||||
<app-sats [satoshis]="node.channels_capacity_avg"></app-sats><app-fiat [value]="node.channels_capacity_avg" digitsInfo="1.0-0"></app-fiat>
|
<tr>
|
||||||
</td>
|
<td i18n="address.total-received">Average channel size</td>
|
||||||
</tr>
|
<td>
|
||||||
<tr *ngIf="node.country && node.city && node.subdivision">
|
<app-sats [satoshis]="node.channels_capacity_avg"></app-sats>
|
||||||
<td i18n="location">Location</td>
|
<app-fiat [value]="node.channels_capacity_avg" digitsInfo="1.0-0"></app-fiat>
|
||||||
<td>{{ node.city.en }}, {{ node.subdivision.en }}<br>{{ node.country.en }}</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="node.country && !node.city">
|
<tr *ngIf="node.country && node.city && node.subdivision">
|
||||||
<td i18n="location">Location</td>
|
<td i18n="location">Location</td>
|
||||||
<td>{{ node.country.en }}</td>
|
<td>{{ node.city.en }}, {{ node.subdivision.en }}<br>{{ node.country.en }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
<tr *ngIf="node.country && !node.city">
|
||||||
</table>
|
<td i18n="location">Location</td>
|
||||||
</div>
|
<td>{{ node.country.en }}</td>
|
||||||
<div class="w-100 d-block d-md-none"></div>
|
</tr>
|
||||||
<div class="col-md">
|
</tbody>
|
||||||
<table class="table table-borderless table-striped">
|
</table>
|
||||||
<tbody>
|
</div>
|
||||||
<tr>
|
<div class="w-100 d-block d-md-none"></div>
|
||||||
<td i18n="address.total-received">First seen</td>
|
<div class="col-md">
|
||||||
<td>
|
<table class="table table-borderless table-striped">
|
||||||
<app-timestamp [dateString]="node.first_seen"></app-timestamp>
|
<tbody>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td i18n="address.total-received">First seen</td>
|
||||||
<tr>
|
<td>
|
||||||
<td i18n="address.total-sent">Last update</td>
|
<app-timestamp [dateString]="node.first_seen"></app-timestamp>
|
||||||
<td>
|
</td>
|
||||||
<app-timestamp [dateString]="node.updated_at"></app-timestamp>
|
</tr>
|
||||||
</td>
|
<tr>
|
||||||
</tr>
|
<td i18n="address.total-sent">Last update</td>
|
||||||
<tr>
|
<td>
|
||||||
<td i18n="address.balance">Color</td>
|
<app-timestamp [dateString]="node.updated_at"></app-timestamp>
|
||||||
<td><div [ngStyle]="{'color': node.color}">{{ node.color }}</div></td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="node.country">
|
<tr>
|
||||||
<td i18n="isp">ISP</td>
|
<td i18n="address.balance">Color</td>
|
||||||
<td>
|
<td>
|
||||||
{{ node.as_organization }} [ASN {{node.as_number}}]
|
<div [ngStyle]="{'color': node.color}">{{ node.color }}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
<tr *ngIf="node.country">
|
||||||
</table>
|
<td i18n="isp">ISP</td>
|
||||||
</div>
|
<td>
|
||||||
|
{{ node.as_organization }} [ASN {{node.as_number}}]
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
</div>
|
||||||
|
|
||||||
<div class="input-group mb-3" *ngIf="node.socketsObject.length">
|
<br>
|
||||||
<div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown" *ngIf="node.socketsObject.length > 1; else noDropdown">
|
|
||||||
<button class="btn btn-secondary dropdown-toggle" type="button" aria-expanded="false" ngbDropdownAnchor (focus)="myDrop.open()"><div class="dropdownLabel">{{ node.socketsObject[selectedSocketIndex].label }}</div></button>
|
<div class="input-group mb-3" *ngIf="!error && node.socketsObject.length">
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownManual">
|
<div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown"
|
||||||
<button *ngFor="let socket of node.socketsObject; let i = index;" ngbDropdownItem (click)="changeSocket(i)">{{ socket.label }}</button>
|
*ngIf="node.socketsObject.length > 1; else noDropdown">
|
||||||
</div>
|
<button class="btn btn-secondary dropdown-toggle" type="button" aria-expanded="false" ngbDropdownAnchor
|
||||||
</div>
|
(focus)="myDrop.open()">
|
||||||
<ng-template #noDropdown>
|
<div class="dropdownLabel">{{ node.socketsObject[selectedSocketIndex].label }}</div>
|
||||||
<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 = true" (mouseout)="qrCodeVisible = false">
|
|
||||||
<fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon>
|
|
||||||
<div class="qr-wrapper" [hidden]="!qrCodeVisible">
|
|
||||||
<app-qrcode [size]="200" [data]="node.socketsObject[selectedSocketIndex].socket"></app-qrcode>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary ml-1" type="button" id="inputGroupFileAddon04">
|
<div ngbDropdownMenu aria-labelledby="dropdownManual">
|
||||||
<app-clipboard [text]="node.socketsObject[selectedSocketIndex].socket"></app-clipboard>
|
<button *ngFor="let socket of node.socketsObject; let i = index;" ngbDropdownItem (click)="changeSocket(i)">{{
|
||||||
</button>
|
socket.label }}</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<app-node-statistics-chart [publicKey]="node.public_key"></app-node-statistics-chart>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<h2>Channels ({{ node.channel_count }})</h2>
|
|
||||||
<div class="d-flex align-items-center justify-content-end">
|
|
||||||
<span style="margin-bottom: 0.5rem">List</span>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" (change)="channelsListModeChange($event)">
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<span style="margin-bottom: 0.5rem">Map</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<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 = true"
|
||||||
|
(mouseout)="qrCodeVisible = false">
|
||||||
|
<fa-icon [icon]="['fas', 'qrcode']" [fixedWidth]="true"></fa-icon>
|
||||||
|
<div class="qr-wrapper" [hidden]="!qrCodeVisible">
|
||||||
|
<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"></app-clipboard>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<app-nodes-channels-map *ngIf="channelsListMode === 'map'" [style]="'nodepage'" [publicKey]="node.public_key"></app-nodes-channels-map>
|
<br>
|
||||||
<app-channels-list *ngIf="channelsListMode === 'list'" [publicKey]="node.public_key"></app-channels-list>
|
|
||||||
|
<app-node-statistics-chart [publicKey]="node.public_key" *ngIf="!error"></app-node-statistics-chart>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between" *ngIf="!error">
|
||||||
|
<h2>Channels ({{ channelsListStatus === 'open' ? node.channel_active_count : node.channel_closed_count }})</h2>
|
||||||
|
<div class="d-flex align-items-center justify-content-end">
|
||||||
|
<span style="margin-bottom: 0.5rem">List</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" (change)="channelsListModeChange($event)">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<span style="margin-bottom: 0.5rem">Map</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-nodes-channels-map *ngIf="channelsListMode === 'map' && !error" [style]="'nodepage'" [publicKey]="node.public_key">
|
||||||
|
</app-nodes-channels-map>
|
||||||
|
<app-channels-list *ngIf="channelsListMode === 'list' && !error" [publicKey]="node.public_key"
|
||||||
|
(channelsStatusChangedEvent)="onChannelsListStatusChanged($event)"></app-channels-list>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { catchError, map, switchMap } from 'rxjs/operators';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { LightningApiService } from '../lightning-api.service';
|
import { LightningApiService } from '../lightning-api.service';
|
||||||
|
|
||||||
@ -18,6 +18,9 @@ export class NodeComponent implements OnInit {
|
|||||||
selectedSocketIndex = 0;
|
selectedSocketIndex = 0;
|
||||||
qrCodeVisible = false;
|
qrCodeVisible = false;
|
||||||
channelsListMode = 'list';
|
channelsListMode = 'list';
|
||||||
|
channelsListStatus: string;
|
||||||
|
error: Error;
|
||||||
|
publicKey: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private lightningApiService: LightningApiService,
|
private lightningApiService: LightningApiService,
|
||||||
@ -29,6 +32,7 @@ export class NodeComponent implements OnInit {
|
|||||||
this.node$ = this.activatedRoute.paramMap
|
this.node$ = this.activatedRoute.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
|
this.publicKey = params.get('public_key');
|
||||||
return this.lightningApiService.getNode$(params.get('public_key'));
|
return this.lightningApiService.getNode$(params.get('public_key'));
|
||||||
}),
|
}),
|
||||||
map((node) => {
|
map((node) => {
|
||||||
@ -55,6 +59,13 @@ export class NodeComponent implements OnInit {
|
|||||||
node.socketsObject = socketsObject;
|
node.socketsObject = socketsObject;
|
||||||
return node;
|
return node;
|
||||||
}),
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
this.error = err;
|
||||||
|
return [{
|
||||||
|
alias: this.publicKey,
|
||||||
|
public_key: this.publicKey,
|
||||||
|
}];
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,4 +80,8 @@ export class NodeComponent implements OnInit {
|
|||||||
this.channelsListMode = 'list';
|
this.channelsListMode = 'list';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChannelsListStatusChanged(e) {
|
||||||
|
this.channelsListStatus = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user