mirror of
https://github.com/mempool/mempool.git
synced 2024-11-20 02:11:49 +01:00
Index daily channel stats and show in dashboard widget
This commit is contained in:
parent
4009a066e0
commit
9000b6b18e
@ -4,7 +4,7 @@ import logger from '../logger';
|
|||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 26;
|
private static currentVersion = 27;
|
||||||
private queryTimeout = 120000;
|
private queryTimeout = 120000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -174,7 +174,7 @@ class DatabaseMigration {
|
|||||||
this.uniqueLog(logger.notice, this.blocksTruncatedMessage);
|
this.uniqueLog(logger.notice, this.blocksTruncatedMessage);
|
||||||
await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
|
await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
|
||||||
await this.$executeQuery(`ALTER TABLE blocks
|
await this.$executeQuery(`ALTER TABLE blocks
|
||||||
ADD avg_fee INT UNSIGNED NULL,
|
ADD med_fee INT UNSIGNED NULL,
|
||||||
ADD avg_fee_rate INT UNSIGNED NULL
|
ADD avg_fee_rate INT UNSIGNED NULL
|
||||||
`);
|
`);
|
||||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
@ -265,6 +265,15 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"');
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 27 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,69 @@ class ChannelsApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getChannelsStats(): Promise<any> {
|
||||||
|
try {
|
||||||
|
// Feedback from zerofeerouting:
|
||||||
|
// "I would argue > 5000ppm can be ignored. Channels charging more than .5% fee are ignored by CLN for example."
|
||||||
|
const ignoredFeeRateThreshold = 5000;
|
||||||
|
const ignoredBaseFeeThreshold = 5000;
|
||||||
|
|
||||||
|
// Capacity
|
||||||
|
let query = `SELECT AVG(capacity) AS avgCapacity FROM channels WHERE status = 1 ORDER BY capacity`;
|
||||||
|
const [avgCapacity]: any = await DB.query(query);
|
||||||
|
|
||||||
|
query = `SELECT capacity FROM channels WHERE status = 1 ORDER BY capacity`;
|
||||||
|
let [capacity]: any = await DB.query(query);
|
||||||
|
capacity = capacity.map(capacity => capacity.capacity);
|
||||||
|
const medianCapacity = capacity[Math.floor(capacity.length / 2)];
|
||||||
|
|
||||||
|
// Fee rates
|
||||||
|
query = `SELECT node1_fee_rate FROM channels WHERE node1_fee_rate < ${ignoredFeeRateThreshold} AND status = 1`;
|
||||||
|
let [feeRates1]: any = await DB.query(query);
|
||||||
|
feeRates1 = feeRates1.map(rate => rate.node1_fee_rate);
|
||||||
|
query = `SELECT node2_fee_rate FROM channels WHERE node2_fee_rate < ${ignoredFeeRateThreshold} AND status = 1`;
|
||||||
|
let [feeRates2]: any = await DB.query(query);
|
||||||
|
feeRates2 = feeRates2.map(rate => rate.node2_fee_rate);
|
||||||
|
|
||||||
|
let feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b);
|
||||||
|
let avgFeeRate = 0;
|
||||||
|
for (const rate of feeRates) {
|
||||||
|
avgFeeRate += rate;
|
||||||
|
}
|
||||||
|
avgFeeRate /= feeRates.length;
|
||||||
|
const medianFeeRate = feeRates[Math.floor(feeRates.length / 2)];
|
||||||
|
|
||||||
|
// Base fees
|
||||||
|
query = `SELECT node1_base_fee_mtokens FROM channels WHERE node1_base_fee_mtokens < ${ignoredBaseFeeThreshold} AND status = 1`;
|
||||||
|
let [baseFees1]: any = await DB.query(query);
|
||||||
|
baseFees1 = baseFees1.map(rate => rate.node1_base_fee_mtokens);
|
||||||
|
query = `SELECT node2_base_fee_mtokens FROM channels WHERE node2_base_fee_mtokens < ${ignoredBaseFeeThreshold} AND status = 1`;
|
||||||
|
let [baseFees2]: any = await DB.query(query);
|
||||||
|
baseFees2 = baseFees2.map(rate => rate.node2_base_fee_mtokens);
|
||||||
|
|
||||||
|
let baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b);
|
||||||
|
let avgBaseFee = 0;
|
||||||
|
for (const fee of baseFees) {
|
||||||
|
avgBaseFee += fee;
|
||||||
|
}
|
||||||
|
avgBaseFee /= baseFees.length;
|
||||||
|
const medianBaseFee = feeRates[Math.floor(baseFees.length / 2)];
|
||||||
|
|
||||||
|
return {
|
||||||
|
avgCapacity: parseInt(avgCapacity[0].avgCapacity, 10),
|
||||||
|
avgFeeRate: avgFeeRate,
|
||||||
|
avgBaseFee: avgBaseFee,
|
||||||
|
medianCapacity: medianCapacity,
|
||||||
|
medianFeeRate: medianFeeRate,
|
||||||
|
medianBaseFee: medianBaseFee,
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot calculate channels statistics. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async $getChannelsByTransactionId(transactionIds: string[]): Promise<any[]> {
|
public async $getChannelsByTransactionId(transactionIds: string[]): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
transactionIds = transactionIds.map((id) => '\'' + id + '\'');
|
transactionIds = transactionIds.map((id) => '\'' + id + '\'');
|
||||||
|
@ -16,7 +16,7 @@ class StatisticsApi {
|
|||||||
public async $getLatestStatistics(): Promise<any> {
|
public async $getLatestStatistics(): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY id DESC LIMIT 1`);
|
const [rows]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY id DESC LIMIT 1`);
|
||||||
const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY id DESC LIMIT 1 OFFSET 72`);
|
const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY id DESC LIMIT 1 OFFSET 7`);
|
||||||
return {
|
return {
|
||||||
latest: rows[0],
|
latest: rows[0],
|
||||||
previous: rows2[0],
|
previous: rows2[0],
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import DB from '../../database';
|
import DB from '../../database';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import lightningApi from '../../api/lightning/lightning-api-factory';
|
import lightningApi from '../../api/lightning/lightning-api-factory';
|
||||||
|
import channelsApi from '../../api/explorer/channels.api';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
|
|
||||||
class LightningStatsUpdater {
|
class LightningStatsUpdater {
|
||||||
@ -124,15 +125,15 @@ class LightningStatsUpdater {
|
|||||||
)
|
)
|
||||||
VALUES (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)`;
|
VALUES (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)`;
|
||||||
|
|
||||||
await DB.query(query, [
|
await DB.query(query, [
|
||||||
date.getTime() / 1000,
|
date.getTime() / 1000,
|
||||||
channelsCount,
|
channelsCount,
|
||||||
0,
|
0,
|
||||||
totalCapacity,
|
totalCapacity,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add one day and continue
|
// Add one day and continue
|
||||||
date.setDate(date.getDate() + 1);
|
date.setDate(date.getDate() + 1);
|
||||||
@ -232,6 +233,8 @@ class LightningStatsUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const channelStats = await channelsApi.$getChannelsStats();
|
||||||
|
|
||||||
const query = `INSERT INTO lightning_stats(
|
const query = `INSERT INTO lightning_stats(
|
||||||
added,
|
added,
|
||||||
channel_count,
|
channel_count,
|
||||||
@ -239,7 +242,13 @@ class LightningStatsUpdater {
|
|||||||
total_capacity,
|
total_capacity,
|
||||||
tor_nodes,
|
tor_nodes,
|
||||||
clearnet_nodes,
|
clearnet_nodes,
|
||||||
unannounced_nodes
|
unannounced_nodes,
|
||||||
|
avg_capacity,
|
||||||
|
avg_fee_rate,
|
||||||
|
avg_base_fee_mtokens,
|
||||||
|
med_capacity,
|
||||||
|
med_fee_rate,
|
||||||
|
med_base_fee_mtokens
|
||||||
)
|
)
|
||||||
VALUES (NOW(), ?, ?, ?, ?, ?, ?)`;
|
VALUES (NOW(), ?, ?, ?, ?, ?, ?)`;
|
||||||
|
|
||||||
@ -249,7 +258,13 @@ class LightningStatsUpdater {
|
|||||||
total_capacity,
|
total_capacity,
|
||||||
torNodes,
|
torNodes,
|
||||||
clearnetNodes,
|
clearnetNodes,
|
||||||
unannouncedNodes
|
unannouncedNodes,
|
||||||
|
channelStats.avgCapacity,
|
||||||
|
channelStats.avgFeeRate,
|
||||||
|
channelStats.avgBaseFee,
|
||||||
|
channelStats.medianCapacity,
|
||||||
|
channelStats.medianFeeRate,
|
||||||
|
channelStats.medianBaseFee,
|
||||||
]);
|
]);
|
||||||
logger.info(`Lightning daily stats done.`);
|
logger.info(`Lightning daily stats done.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -15,7 +15,11 @@ export class ChangeComponent implements OnChanges {
|
|||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
this.change = (this.current - this.previous) / this.previous * 100;
|
if (!this.previous) {
|
||||||
|
this.change = 0;
|
||||||
|
} else {
|
||||||
|
this.change = (this.current - this.previous) / this.previous * 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
<div class="widget-toggler">
|
||||||
|
<a href="javascript:;" (click)="switchMode('avg')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': mode !== 'avg'}"><small>avg</small></a>
|
||||||
|
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||||
|
<a href="javascript:;" (click)="switchMode('med')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': mode !== 'med'}"><small>med</small></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fee-estimation-wrapper" *ngIf="statistics$ | async as statistics; else loadingReward">
|
||||||
|
|
||||||
|
<div class="fee-estimation-container" *ngIf="mode === 'avg'">
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.average-capacity">Avg Capacity</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.avg_capacity || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.avg_capacity" [previous]="statistics.previous?.avg_capacity"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.average-feerate">Avg Fee Rate</h5>
|
||||||
|
<div class="card-text" i18n-ngbTooltip="ln.average-feerate-desc"
|
||||||
|
ngbTooltip="The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
||||||
|
placement="bottom">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.avg_fee_rate || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.avg_fee_rate" [previous]="statistics.previous?.avg_fee_rate"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.average-basefee">Avg Base Fee</h5>
|
||||||
|
<div class="card-text" i18n-ngbTooltip="ln.average-basefee-desc"
|
||||||
|
ngbTooltip="The average base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.avg_base_fee_mtokens || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.avg_base_fee_mtokens" [previous]="statistics.previous?.avg_base_fee_mtokens"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fee-estimation-container" *ngIf="mode === 'med'">
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.median-capacity">Med Capacity</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.med_capacity || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.med_capacity" [previous]="statistics.previous?.med_capacity"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.average-feerate">Med Fee Rate</h5>
|
||||||
|
<div class="card-text" i18n-ngbTooltip="ln.median-feerate-desc"
|
||||||
|
ngbTooltip="The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
||||||
|
placement="bottom">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.med_fee_rate || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.med_fee_rate" [previous]="statistics.previous?.med_fee_rate"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="ln.median-basefee">Med Base Fee</h5>
|
||||||
|
<div class="card-text" i18n-ngbTooltip="ln.median-basefee-desc"
|
||||||
|
ngbTooltip="The median base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="fee-text">
|
||||||
|
{{ statistics.latest?.med_base_fee_mtokens || 0 | number: '1.0-0' }}
|
||||||
|
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="fiat">
|
||||||
|
<app-change [current]="statistics.latest?.med_base_fee_mtokens" [previous]="statistics.previous?.med_base_fee_mtokens"></app-change>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loadingReward>
|
||||||
|
<div class="fee-estimation-container loading-container">
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="mining.rewards">Nodes</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="mining.rewards-per-tx">Channels</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<h5 class="card-title" i18n="mining.average-fee">Average Channel</h5>
|
||||||
|
<div class="card-text">
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
<div class="skeleton-loader"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
@ -0,0 +1,101 @@
|
|||||||
|
.card-title {
|
||||||
|
color: #4a68b9;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
font-size: 22px;
|
||||||
|
span {
|
||||||
|
font-size: 11px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.green-color {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee-estimation-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
@media (min-width: 376px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
max-width: 150px;
|
||||||
|
margin: 0;
|
||||||
|
width: -webkit-fill-available;
|
||||||
|
@media (min-width: 376px) {
|
||||||
|
margin: 0 auto 0px;
|
||||||
|
}
|
||||||
|
&:first-child{
|
||||||
|
display: none;
|
||||||
|
@media (min-width: 485px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.card-text span {
|
||||||
|
color: #ffffff66;
|
||||||
|
font-size: 12px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
.fee-text{
|
||||||
|
border-bottom: 1px solid #ffffff1c;
|
||||||
|
width: fit-content;
|
||||||
|
margin: auto;
|
||||||
|
line-height: 1.45;
|
||||||
|
padding: 0px 2px;
|
||||||
|
}
|
||||||
|
.fiat {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
min-height: 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
.skeleton-loader {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
&:first-child {
|
||||||
|
max-width: 90px;
|
||||||
|
margin: 15px auto 3px;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin: 10px auto 3px;
|
||||||
|
max-width: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-toggler {
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 3px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler-option {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
color: #ffffff66;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-channels-statistics',
|
||||||
|
templateUrl: './channels-statistics.component.html',
|
||||||
|
styleUrls: ['./channels-statistics.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ChannelsStatisticsComponent implements OnInit {
|
||||||
|
@Input() statistics$: Observable<any>;
|
||||||
|
mode: string = 'avg';
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
switchMode(mode: 'avg' | 'med') {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,10 @@ export class LightningApiService {
|
|||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/top');
|
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/top');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listChannelStats$(publicKey: string): Observable<any> {
|
||||||
|
return this.httpClient.get<any>(this.apiBasePath + '/channels/' + publicKey + '/statistics');
|
||||||
|
}
|
||||||
|
|
||||||
listStatistics$(): Observable<any> {
|
listStatistics$(): Observable<any> {
|
||||||
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/statistics');
|
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/statistics');
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,16 @@
|
|||||||
<div class="main-title">
|
<div class="main-title">
|
||||||
<span i18n="lightning.statistics-title">Channels Statistics</span>
|
<span i18n="lightning.statistics-title">Channels Statistics</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-wrapper">
|
||||||
|
<div class="card" style="height: 123px">
|
||||||
|
<div class="card-body more-padding">
|
||||||
|
<app-channels-statistics [statistics$]="statistics$"></app-channels-statistics>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,7 +35,7 @@ export class LightningDashboardComponent implements OnInit {
|
|||||||
map((object) => object.topByChannels),
|
map((object) => object.topByChannels),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.statistics$ = this.lightningApiService.getLatestStatistics$();
|
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import { LightningStatisticsChartComponent } from './statistics-chart/lightning-
|
|||||||
import { NodeStatisticsChartComponent } from './node-statistics-chart/node-statistics-chart.component';
|
import { NodeStatisticsChartComponent } from './node-statistics-chart/node-statistics-chart.component';
|
||||||
import { GraphsModule } from '../graphs/graphs.module';
|
import { GraphsModule } from '../graphs/graphs.module';
|
||||||
import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networks-chart.component';
|
import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networks-chart.component';
|
||||||
|
import { ChannelsStatisticsComponent } from './channels-statistics/channels-statistics.component';
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
LightningDashboardComponent,
|
LightningDashboardComponent,
|
||||||
@ -31,6 +32,7 @@ import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networ
|
|||||||
ClosingTypeComponent,
|
ClosingTypeComponent,
|
||||||
LightningStatisticsChartComponent,
|
LightningStatisticsChartComponent,
|
||||||
NodesNetworksChartComponent,
|
NodesNetworksChartComponent,
|
||||||
|
ChannelsStatisticsComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -2,18 +2,21 @@
|
|||||||
<div class="fee-estimation-container">
|
<div class="fee-estimation-container">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.average-fee">Capacity</h5>
|
<h5 class="card-title" i18n="mining.average-fee">Capacity</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee"
|
<div class="card-text" i18n-ngbTooltip="mining.average-fee" ngbTooltip="Percentage change past week"
|
||||||
ngbTooltip="Percentage change past week" placement="bottom">
|
placement="bottom">
|
||||||
<app-amount [satoshis]="statistics.latest?.total_capacity" digitsInfo="1.2-2"></app-amount>
|
<div class="fee-text">
|
||||||
|
<app-amount [satoshis]="statistics.latest?.total_capacity" digitsInfo="1.2-2"></app-amount>
|
||||||
|
</div>
|
||||||
<span class="fiat" *ngIf="statistics.previous">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest.total_capacity" [previous]="statistics.previous.total_capacity"></app-change>
|
<app-change [current]="statistics.latest.total_capacity" [previous]="statistics.previous.total_capacity">
|
||||||
|
</app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.rewards">Nodes</h5>
|
<h5 class="card-title" i18n="mining.rewards">Nodes</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-desc"
|
<div class="card-text" i18n-ngbTooltip="mining.rewards-desc" ngbTooltip="Percentage change past week"
|
||||||
ngbTooltip="Percentage change past week" placement="bottom">
|
placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text">
|
||||||
{{ statistics.latest?.node_count || 0 | number }}
|
{{ statistics.latest?.node_count || 0 | number }}
|
||||||
</div>
|
</div>
|
||||||
@ -24,13 +27,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.rewards-per-tx">Channels</h5>
|
<h5 class="card-title" i18n="mining.rewards-per-tx">Channels</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-per-tx-desc"
|
<div class="card-text" i18n-ngbTooltip="mining.rewards-per-tx-desc" ngbTooltip="Percentage change past week"
|
||||||
ngbTooltip="Percentage change past week" placement="bottom">
|
placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text">
|
||||||
{{ statistics.latest?.channel_count || 0 | number }}
|
{{ statistics.latest?.channel_count || 0 | number }}
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat" *ngIf="statistics.previous">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest.channel_count" [previous]="statistics.previous.channel_count"></app-change>
|
<app-change [current]="statistics.latest.channel_count" [previous]="statistics.previous.channel_count">
|
||||||
|
</app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -73,4 +77,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
Loading…
Reference in New Issue
Block a user