Merge branch 'master' into nymkappa/unify-blocks-apis
This commit is contained in:
commit
6c271ab6ee
@ -237,14 +237,21 @@ export class Common {
|
||||
].join('x');
|
||||
}
|
||||
|
||||
static utcDateToMysql(date?: number): string {
|
||||
static utcDateToMysql(date?: number | null): string | null {
|
||||
if (date === null) {
|
||||
return null;
|
||||
}
|
||||
const d = new Date((date || 0) * 1000);
|
||||
return d.toISOString().split('T')[0] + ' ' + d.toTimeString().split(' ')[0];
|
||||
}
|
||||
|
||||
static findSocketNetwork(addr: string): {network: string | null, url: string} {
|
||||
let network: string | null = null;
|
||||
let url = addr.split('://')[1];
|
||||
let url: string = addr;
|
||||
|
||||
if (config.LIGHTNING.BACKEND === 'cln') {
|
||||
url = addr.split('://')[1];
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
@ -261,7 +268,7 @@ export class Common {
|
||||
}
|
||||
} else if (addr.indexOf('i2p') !== -1) {
|
||||
network = 'i2p';
|
||||
} else if (addr.indexOf('ipv4') !== -1) {
|
||||
} else if (addr.indexOf('ipv4') !== -1 || (config.LIGHTNING.BACKEND === 'lnd' && isIP(url.split(':')[0]) === 4)) {
|
||||
const ipv = isIP(url.split(':')[0]);
|
||||
if (ipv === 4) {
|
||||
network = 'ipv4';
|
||||
@ -271,7 +278,7 @@ export class Common {
|
||||
url: addr,
|
||||
};
|
||||
}
|
||||
} else if (addr.indexOf('ipv6') !== -1) {
|
||||
} else if (addr.indexOf('ipv6') !== -1 || (config.LIGHTNING.BACKEND === 'lnd' && url.indexOf(']:'))) {
|
||||
url = url.split('[')[1].split(']')[0];
|
||||
const ipv = isIP(url);
|
||||
if (ipv === 6) {
|
||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 56;
|
||||
private static currentVersion = 57;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@ -500,6 +500,11 @@ class DatabaseMigration {
|
||||
this.uniqueLog(logger.notice, '`pools` table has been truncated`');
|
||||
await this.updateToSchemaVersion(56);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 57) {
|
||||
await this.$executeQuery(`ALTER TABLE nodes MODIFY updated_at datetime NULL`);
|
||||
await this.updateToSchemaVersion(57);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -559,6 +559,17 @@ class ChannelsApi {
|
||||
const policy1: Partial<ILightningApi.RoutingPolicy> = channel.node1_policy || {};
|
||||
const policy2: Partial<ILightningApi.RoutingPolicy> = channel.node2_policy || {};
|
||||
|
||||
// https://github.com/mempool/mempool/issues/3006
|
||||
if ((channel.last_update ?? 0) < 1514736061) { // January 1st 2018
|
||||
channel.last_update = null;
|
||||
}
|
||||
if ((policy1.last_update ?? 0) < 1514736061) { // January 1st 2018
|
||||
policy1.last_update = null;
|
||||
}
|
||||
if ((policy2.last_update ?? 0) < 1514736061) { // January 1st 2018
|
||||
policy2.last_update = null;
|
||||
}
|
||||
|
||||
const query = `INSERT INTO channels
|
||||
(
|
||||
id,
|
||||
|
@ -228,7 +228,7 @@ class NodesApi {
|
||||
nodes.capacity
|
||||
FROM nodes
|
||||
ORDER BY capacity DESC
|
||||
LIMIT 100
|
||||
LIMIT 6
|
||||
`;
|
||||
|
||||
[rows] = await DB.query(query);
|
||||
@ -269,14 +269,26 @@ class NodesApi {
|
||||
let query: string;
|
||||
if (full === false) {
|
||||
query = `
|
||||
SELECT nodes.public_key as publicKey, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias,
|
||||
nodes.channels
|
||||
SELECT
|
||||
nodes.public_key as publicKey,
|
||||
IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias,
|
||||
nodes.channels,
|
||||
geo_names_city.names as city, geo_names_country.names as country,
|
||||
geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision
|
||||
FROM nodes
|
||||
LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country'
|
||||
LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city'
|
||||
LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code'
|
||||
LEFT JOIN geo_names geo_names_subdivision on geo_names_subdivision.id = nodes.subdivision_id AND geo_names_subdivision.type = 'division'
|
||||
ORDER BY channels DESC
|
||||
LIMIT 100;
|
||||
LIMIT 6;
|
||||
`;
|
||||
|
||||
[rows] = await DB.query(query);
|
||||
for (let i = 0; i < rows.length; ++i) {
|
||||
rows[i].country = JSON.parse(rows[i].country);
|
||||
rows[i].city = JSON.parse(rows[i].city);
|
||||
}
|
||||
} else {
|
||||
query = `
|
||||
SELECT nodes.public_key AS publicKey, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias,
|
||||
@ -630,6 +642,11 @@ class NodesApi {
|
||||
*/
|
||||
public async $saveNode(node: ILightningApi.Node): Promise<void> {
|
||||
try {
|
||||
// https://github.com/mempool/mempool/issues/3006
|
||||
if ((node.last_update ?? 0) < 1514736061) { // January 1st 2018
|
||||
node.last_update = null;
|
||||
}
|
||||
|
||||
const sockets = (node.addresses?.map(a => a.addr).join(',')) ?? '';
|
||||
const query = `INSERT INTO nodes(
|
||||
public_key,
|
||||
|
@ -21,7 +21,7 @@ export namespace ILightningApi {
|
||||
export interface Channel {
|
||||
channel_id: string;
|
||||
chan_point: string;
|
||||
last_update: number;
|
||||
last_update: number | null;
|
||||
node1_pub: string;
|
||||
node2_pub: string;
|
||||
capacity: string;
|
||||
@ -36,11 +36,11 @@ export namespace ILightningApi {
|
||||
fee_rate_milli_msat: string;
|
||||
disabled: boolean;
|
||||
max_htlc_msat: string;
|
||||
last_update: number;
|
||||
last_update: number | null;
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
last_update: number;
|
||||
last_update: number | null;
|
||||
pub_key: string;
|
||||
alias: string;
|
||||
addresses: {
|
||||
|
@ -72,7 +72,7 @@ class NetworkSyncService {
|
||||
const graphNodesPubkeys: string[] = [];
|
||||
for (const node of nodes) {
|
||||
const latestUpdated = await channelsApi.$getLatestChannelUpdateForNode(node.pub_key);
|
||||
node.last_update = Math.max(node.last_update, latestUpdated);
|
||||
node.last_update = Math.max(node.last_update ?? 0, latestUpdated);
|
||||
|
||||
await nodesApi.$saveNode(node);
|
||||
graphNodesPubkeys.push(node.pub_key);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, OnDestroy, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Price } from 'src/app/services/price.service';
|
||||
import { Price } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-amount',
|
||||
|
@ -5,7 +5,7 @@ import BlockScene from './block-scene';
|
||||
import TxSprite from './tx-sprite';
|
||||
import TxView from './tx-view';
|
||||
import { Position } from './sprite-types';
|
||||
import { Price } from 'src/app/services/price.service';
|
||||
import { Price } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-overview-graph',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { Position } from '../../components/block-overview-graph/sprite-types.js';
|
||||
import { Price } from 'src/app/services/price.service';
|
||||
import { Price } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-overview-tooltip',
|
||||
|
@ -13,7 +13,7 @@ import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||
import { detectWebGL } from '../../shared/graphs.utils';
|
||||
import { PriceService, Price } from 'src/app/services/price.service';
|
||||
import { PriceService, Price } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block',
|
||||
|
@ -267,6 +267,7 @@ export class StartComponent implements OnInit, OnDestroy {
|
||||
|
||||
resetScroll(): void {
|
||||
this.scrollToBlock(this.chainTip);
|
||||
this.blockchainContainer.nativeElement.scrollLeft = 0;
|
||||
}
|
||||
|
||||
getPageIndexOf(height: number): number {
|
||||
|
@ -22,7 +22,7 @@ import { SeoService } from '../../services/seo.service';
|
||||
import { BlockExtended, CpfpInfo } from '../../interfaces/node-api.interface';
|
||||
import { LiquidUnblinding } from './liquid-ublinding';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Price, PriceService } from 'src/app/services/price.service';
|
||||
import { Price, PriceService } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction',
|
||||
|
@ -9,7 +9,7 @@ import { AssetsService } from '../../services/assets.service';
|
||||
import { filter, map, tap, switchMap, shareReplay } from 'rxjs/operators';
|
||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { PriceService } from 'src/app/services/price.service';
|
||||
import { PriceService } from '../../services/price.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions-list',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, ElementRef, ViewChild, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { tap } from 'rxjs';
|
||||
import { Price, PriceService } from 'src/app/services/price.service';
|
||||
import { Price, PriceService } from '../../services/price.service';
|
||||
|
||||
interface Xput {
|
||||
type: 'input' | 'output' | 'fee';
|
||||
|
@ -199,8 +199,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
|
||||
|
||||
this.middle = {
|
||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.25} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.25}`,
|
||||
style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
|
||||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
||||
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
||||
};
|
||||
|
||||
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
||||
@ -257,7 +257,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
const lineParams = weights.map((w, i) => {
|
||||
return {
|
||||
weight: w,
|
||||
thickness: xputs[i].value === 0 ? this.zeroValueThickness : Math.min(this.combinedWeight + 0.5, Math.max(this.minWeight - 1, w) + 1),
|
||||
thickness: xputs[i].value === 0 ? this.zeroValueThickness : Math.max(this.minWeight - 1, w) + 1,
|
||||
offset: 0,
|
||||
innerY: 0,
|
||||
outerY: 0,
|
||||
@ -269,7 +269,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
|
||||
// bounds of the middle segment
|
||||
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
||||
const innerBottom = innerTop + this.combinedWeight + 0.5;
|
||||
const innerBottom = innerTop + this.combinedWeight;
|
||||
// tracks the visual bottom of the endpoints of the previous line
|
||||
let lastOuter = 0;
|
||||
let lastInner = innerTop;
|
||||
@ -294,7 +294,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
|
||||
// set the vertical position of the (center of the) outer side of the line
|
||||
line.outerY = lastOuter + (line.thickness / 2);
|
||||
line.innerY = Math.min(innerBottom - (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
|
||||
line.innerY = Math.min(innerBottom + (line.thickness / 2), Math.max(innerTop + (line.thickness / 2), lastInner + (line.weight / 2)));
|
||||
|
||||
// special case to center single input/outputs
|
||||
if (xputs.length === 1) {
|
||||
|
@ -55,7 +55,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Top nodes per capacity -->
|
||||
<div class="col">
|
||||
<div class="col" style="max-height: 410px">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/liquidity' | relativeUrl]">
|
||||
@ -69,7 +69,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Top nodes per channels -->
|
||||
<div class="col">
|
||||
<div class="col" style="max-height: 410px">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/lightning/nodes/rankings/connectivity' | relativeUrl]">
|
||||
|
@ -1,71 +1,56 @@
|
||||
<div [class]="!widget ? 'container-xl full-height' : ''">
|
||||
<h1 *ngIf="!widget" class="float-left">
|
||||
<span i18n="lightning.top-100-liquidity">Top 100 nodes liquidity ranking</span>
|
||||
</h1>
|
||||
|
||||
<div [class]="widget ? 'widget' : 'full'">
|
||||
<table class="table table-borderless table-fixed">
|
||||
|
||||
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget}">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="lightning.liquidity-ranking">Liquidity Ranking</h1>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<th class="rank"></th>
|
||||
<th class="alias text-left" i18n="nodes.alias">Alias</th>
|
||||
<th class="capacity text-right" i18n="node.liquidity">Liquidity</th>
|
||||
<th *ngIf="!widget" class="channels text-right" i18n="lightning.channels">Channels</th>
|
||||
<th *ngIf="!widget" class="timestamp-first text-left" i18n="transaction.first-seen|Transaction first seen">First seen</th>
|
||||
<th *ngIf="!widget" class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
||||
<th *ngIf="!widget" class="location text-right" i18n="lightning.location">Location</th>
|
||||
<th class="text-left" i18n="nodes.alias">Alias</th>
|
||||
<th class="liquidity text-right" i18n="node.liquidity">Liquidity</th>
|
||||
<th class="d-table-cell fiat text-right" [class]="{'widget': widget}">{{ currency$ | async }}</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell channels text-right" i18n="lightning.channels">Channels</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell timestamp text-right" i18n="transaction.first-seen|Transaction first seen">First seen</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell timestamp text-right" i18n="lightning.last_update">Last update</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell text-right" i18n="lightning.location">Location</th>
|
||||
</thead>
|
||||
<tbody *ngIf="topNodesPerCapacity$ | async as nodes; else skeleton">
|
||||
<tr *ngFor="let node of nodes; let i = index;">
|
||||
<td class="rank text-left">
|
||||
{{ i + 1 }}
|
||||
<tbody *ngIf="topNodesPerCapacity$ | async as nodes">
|
||||
<tr *ngFor="let node of nodes;">
|
||||
<td class="pool text-left">
|
||||
<div class="tooltip-custom d-block w-100">
|
||||
<a class="link d-block w-100" [routerLink]="['/lightning/node' | relativeUrl, node.publicKey]">
|
||||
<span class="pool-name w-100">{{ node.alias }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.publicKey]">{{ node.alias }}</a>
|
||||
</td>
|
||||
<td class="capacity text-right">
|
||||
<td class="text-right">
|
||||
<app-amount [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="channels text-right">
|
||||
<td class="d-table-cell fiat text-right" [ngClass]="{'widget': widget}">
|
||||
<app-fiat [value]="node.capacity"></app-fiat>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right">
|
||||
{{ node.channels | number }}
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right text-truncate">
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonRows">
|
||||
<td class="rank text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="capacity text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="channels text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -1,91 +1,52 @@
|
||||
.container-xl {
|
||||
max-width: 1400px;
|
||||
padding-bottom: 100px;
|
||||
@media (min-width: 960px) {
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
.container-xl.widget {
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.table td, .table th {
|
||||
padding: 0.5rem;
|
||||
tr, td, th {
|
||||
border: 0px;
|
||||
padding-top: 0.65rem !important;
|
||||
padding-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
.full .rank {
|
||||
width: 5%;
|
||||
}
|
||||
.widget .rank {
|
||||
@media (min-width: 960px) {
|
||||
width: 13%;
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.clear-link {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.full .alias {
|
||||
width: 20%;
|
||||
.pool {
|
||||
width: 15%;
|
||||
@media (max-width: 575px) {
|
||||
width: 75%;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 350px;
|
||||
@media (max-width: 960px) {
|
||||
width: 40%;
|
||||
max-width: 500px;
|
||||
}
|
||||
white-space: nowrap;
|
||||
max-width: 160px;
|
||||
}
|
||||
.widget .alias {
|
||||
width: 60%;
|
||||
overflow: hidden;
|
||||
.pool-name {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 350px;
|
||||
@media (max-width: 960px) {
|
||||
max-width: 175px;
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.full .capacity {
|
||||
.liquidity {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
.widget .capacity {
|
||||
width: 32%;
|
||||
@media (max-width: 960px) {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
@media (max-width: 575px) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.full .channels {
|
||||
.fiat {
|
||||
width: 15%;
|
||||
padding-right: 50px;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
display: none !important;
|
||||
}
|
||||
@media (max-width: 575px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.full .timestamp-first {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.full .timestamp-update {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.full .location {
|
||||
width: 15%;
|
||||
@media (max-width: 960px) {
|
||||
width: 30%;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { INodesRanking, ITopNodesPerCapacity } from '../../../interfaces/node-api.interface';
|
||||
import { SeoService } from '../../../services/seo.service';
|
||||
import { isMobile } from '../../../shared/common.utils';
|
||||
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
||||
import { LightningApiService } from '../../lightning-api.service';
|
||||
|
||||
@ -18,18 +18,22 @@ export class TopNodesPerCapacity implements OnInit {
|
||||
|
||||
topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>;
|
||||
skeletonRows: number[] = [];
|
||||
currency$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private apiService: LightningApiService,
|
||||
private seoService: SeoService
|
||||
private seoService: SeoService,
|
||||
private stateService: StateService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currency$ = this.stateService.fiatCurrency$;
|
||||
|
||||
if (!this.widget) {
|
||||
this.seoService.setTitle($localize`:@@2d9883d230a47fbbb2ec969e32a186597ea27405:Liquidity Ranking`);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||
for (let i = 1; i <= (this.widget ? 6 : 100); ++i) {
|
||||
this.skeletonRows.push(i);
|
||||
}
|
||||
|
||||
@ -50,7 +54,7 @@ export class TopNodesPerCapacity implements OnInit {
|
||||
} else {
|
||||
this.topNodesPerCapacity$ = this.nodes$.pipe(
|
||||
map((ranking) => {
|
||||
return ranking.topByCapacity.slice(0, isMobile() ? 8 : 7);
|
||||
return ranking.topByCapacity.slice(0, 6);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,71 +1,56 @@
|
||||
<div [class]="!widget ? 'container-xl full-height' : ''">
|
||||
<h1 *ngIf="!widget" class="float-left">
|
||||
<span i18n="lightning.top-100-connectivity">Top 100 nodes connectivity ranking</span>
|
||||
</h1>
|
||||
|
||||
<div [class]="widget ? 'widget' : 'full'">
|
||||
|
||||
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget}">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="lightning.liquidity-ranking">Liquidity Ranking</h1>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless">
|
||||
<thead>
|
||||
<th class="rank"></th>
|
||||
<th class="alias text-left" i18n="nodes.alias">Alias</th>
|
||||
<th class="channels text-right" i18n="node.channels">Channels</th>
|
||||
<th *ngIf="!widget" class="capacity text-right" i18n="lightning.liquidity">Liquidity</th>
|
||||
<th *ngIf="!widget" class="timestamp-first text-left" i18n="transaction.first-seen|Transaction first seen">First seen</th>
|
||||
<th *ngIf="!widget" class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
||||
<th *ngIf="!widget" class="location text-right" i18n="lightning.location">Location</th>
|
||||
<th class="pool text-left" i18n="nodes.alias" [ngClass]="{'widget': widget}">Alias</th>
|
||||
<th class="liquidity text-right" i18n="node.channels">Channels</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell channels text-right" i18n="lightning.channels">Capacity</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell text-right" i18n="node.liquidity">{{ currency$ | async }}</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell timestamp text-right" i18n="transaction.first-seen|Transaction first seen">First seen</th>
|
||||
<th *ngIf="!widget" class="d-none d-md-table-cell timestamp text-right" i18n="lightning.last_update">Last update</th>
|
||||
<th class="geolocation d-table-cell text-right" i18n="lightning.location">Location</th>
|
||||
</thead>
|
||||
<tbody *ngIf="topNodesPerChannels$ | async as nodes; else skeleton">
|
||||
<tr *ngFor="let node of nodes; let i = index;">
|
||||
<td class="rank text-left">
|
||||
{{ i + 1 }}
|
||||
<tbody *ngIf="topNodesPerChannels$ | async as nodes">
|
||||
<tr *ngFor="let node of nodes;">
|
||||
<td class="pool text-left">
|
||||
<div class="tooltip-custom d-block w-100">
|
||||
<a class="link d-block w-100" [routerLink]="['/lightning/node' | relativeUrl, node.publicKey]">
|
||||
<span class="pool-name w-100">{{ node.alias }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.publicKey]">{{ node.alias }}</a>
|
||||
<td class="text-right">
|
||||
{{ node.channels ? (node.channels | number) : '~' }}
|
||||
</td>
|
||||
<td class="channels text-right">
|
||||
{{ node.channels | number }}
|
||||
</td>
|
||||
<td *ngIf="!widget" class="capacity text-right">
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell capacity text-right">
|
||||
<app-amount [satoshis]="node.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||
<td *ngIf="!widget" class="fiat d-none d-md-table-cell text-right">
|
||||
<app-fiat [value]="node.capacity"></app-fiat>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.firstSeen" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||
<td *ngIf="!widget" class="d-none d-md-table-cell text-right">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
<td class="geolocation d-table-cell text-right text-truncate">
|
||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of skeletonRows">
|
||||
<td class="rank text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="alias text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td class="channels text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="capacity text-right">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-first text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="timestamp-update text-left">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||
<span class="skeleton-loader"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -1,91 +1,54 @@
|
||||
.container-xl {
|
||||
max-width: 1400px;
|
||||
padding-bottom: 100px;
|
||||
@media (min-width: 960px) {
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
.container-xl.widget {
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.table td, .table th {
|
||||
padding: 0.5rem;
|
||||
tr, td, th {
|
||||
border: 0px;
|
||||
padding-top: 0.65rem !important;
|
||||
padding-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
.full .rank {
|
||||
width: 5%;
|
||||
}
|
||||
.widget .rank {
|
||||
@media (min-width: 960px) {
|
||||
width: 13%;
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.clear-link {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.full .alias {
|
||||
width: 20%;
|
||||
.pool {
|
||||
width: 15%;
|
||||
@media (max-width: 576px) {
|
||||
width: 75%;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 350px;
|
||||
@media (max-width: 960px) {
|
||||
width: 40%;
|
||||
max-width: 500px;
|
||||
}
|
||||
white-space: nowrap;
|
||||
max-width: 160px;
|
||||
}
|
||||
.widget .alias {
|
||||
width: 60%;
|
||||
overflow: hidden;
|
||||
.pool-name {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 350px;
|
||||
@media (max-width: 960px) {
|
||||
max-width: 175px;
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
.pool.widget {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.full .capacity {
|
||||
.liquidity {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
.widget .capacity {
|
||||
width: 32%;
|
||||
@media (max-width: 960px) {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
@media (max-width: 576px) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.full .channels {
|
||||
width: 15%;
|
||||
padding-right: 50px;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
.geolocation {
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.full .timestamp-first {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.full .timestamp-update {
|
||||
width: 10%;
|
||||
@media (max-width: 960px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.full .location {
|
||||
width: 15%;
|
||||
@media (max-width: 960px) {
|
||||
width: 30%;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
@media (max-width: 575px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { INodesRanking, ITopNodesPerChannels } from '../../../interfaces/node-api.interface';
|
||||
import { isMobile } from '../../../shared/common.utils';
|
||||
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
||||
import { LightningApiService } from '../../lightning-api.service';
|
||||
|
||||
@ -17,13 +17,17 @@ export class TopNodesPerChannels implements OnInit {
|
||||
|
||||
topNodesPerChannels$: Observable<ITopNodesPerChannels[]>;
|
||||
skeletonRows: number[] = [];
|
||||
currency$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private apiService: LightningApiService,
|
||||
private stateService: StateService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||
this.currency$ = this.stateService.fiatCurrency$;
|
||||
|
||||
for (let i = 1; i <= (this.widget ? 6 : 100); ++i) {
|
||||
this.skeletonRows.push(i);
|
||||
}
|
||||
|
||||
@ -44,7 +48,15 @@ export class TopNodesPerChannels implements OnInit {
|
||||
} else {
|
||||
this.topNodesPerChannels$ = this.nodes$.pipe(
|
||||
map((ranking) => {
|
||||
return ranking.topByChannels.slice(0, isMobile() ? 8 : 7);
|
||||
for (const i in ranking.topByChannels) {
|
||||
ranking.topByChannels[i].geolocation = <GeolocationData>{
|
||||
country: ranking.topByChannels[i].country?.en,
|
||||
city: ranking.topByChannels[i].city?.en,
|
||||
subdivision: ranking.topByChannels[i].subdivision?.en,
|
||||
iso: ranking.topByChannels[i].iso_code,
|
||||
};
|
||||
}
|
||||
return ranking.topByChannels.slice(0, 6);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ export class CacheService {
|
||||
|
||||
txCache: { [txid: string]: Transaction } = {};
|
||||
|
||||
network: string;
|
||||
blockCache: { [height: number]: BlockExtended } = {};
|
||||
blockLoading: { [height: number]: boolean } = {};
|
||||
copiesInBlockQueue: { [height: number]: number } = {};
|
||||
@ -33,6 +34,10 @@ export class CacheService {
|
||||
this.stateService.chainTip$.subscribe((height) => {
|
||||
this.tip = height;
|
||||
});
|
||||
this.stateService.networkChanged$.subscribe((network) => {
|
||||
this.network = network;
|
||||
this.resetBlockCache();
|
||||
});
|
||||
}
|
||||
|
||||
setTxCache(transactions) {
|
||||
@ -68,15 +73,17 @@ export class CacheService {
|
||||
} catch (e) {
|
||||
console.log("failed to load blocks: ", e.message);
|
||||
}
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
delete this.blockLoading[maxHeight - i];
|
||||
}
|
||||
if (result && result.length) {
|
||||
result.forEach(block => {
|
||||
this.addBlockToCache(block);
|
||||
this.loadedBlocks$.next(block);
|
||||
if (this.blockLoading[block.height]) {
|
||||
this.addBlockToCache(block);
|
||||
this.loadedBlocks$.next(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
delete this.blockLoading[maxHeight - i];
|
||||
}
|
||||
this.clearBlocks();
|
||||
} else {
|
||||
this.bumpBlockPriority(height);
|
||||
@ -104,6 +111,14 @@ export class CacheService {
|
||||
}
|
||||
}
|
||||
|
||||
// remove all blocks from the cache
|
||||
resetBlockCache() {
|
||||
this.blockCache = {};
|
||||
this.blockLoading = {};
|
||||
this.copiesInBlockQueue = {};
|
||||
this.blockPriorities = [];
|
||||
}
|
||||
|
||||
getCachedBlock(height) {
|
||||
return this.blockCache[height];
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user