Add Lightning charts in /graph

This commit is contained in:
nymkappa 2022-07-06 15:15:08 +02:00 committed by wiz
parent 37f731d21c
commit ed3aa7f516
No known key found for this signature in database
GPG Key ID: A394E332255A6173
10 changed files with 154 additions and 58 deletions

View File

@ -10,7 +10,7 @@ class GeneralLightningRoutes {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/search', this.$searchNodesAndChannels)
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/statistics/latest', this.$getGeneralStats)
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/statistics', this.$getStatistics)
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/statistics/:interval', this.$getStatistics)
;
}
@ -33,7 +33,12 @@ class GeneralLightningRoutes {
private async $getStatistics(req: Request, res: Response) {
try {
const statistics = await statisticsApi.$getStatistics();
const statistics = await statisticsApi.$getStatistics(req.params.interval);
const statisticsCount = await statisticsApi.$getStatisticsCount();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.header('X-total-count', statisticsCount.toString());
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(statistics);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);

View File

@ -1,10 +1,21 @@
import logger from '../../logger';
import DB from '../../database';
import { Common } from '../common';
class StatisticsApi {
public async $getStatistics(): Promise<any> {
public async $getStatistics(interval: string | null = null): Promise<any> {
interval = Common.getSqlInterval(interval);
let query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, node_count, total_capacity, tor_nodes, clearnet_nodes, unannounced_nodes
FROM lightning_stats`;
if (interval) {
query += ` WHERE added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
}
query += ` ORDER BY id DESC`;
try {
const query = `SELECT UNIX_TIMESTAMP(added) AS added, channel_count, node_count, total_capacity, tor_nodes, clearnet_nodes, unannounced_nodes FROM lightning_stats ORDER BY id DESC`;
const [rows]: any = await DB.query(query);
return rows;
} catch (e) {
@ -27,6 +38,15 @@ class StatisticsApi {
}
}
public async $getStatisticsCount(): Promise<number> {
try {
const [rows]: any = await DB.query(`SELECT count(*) as count FROM lightning_stats`);
return rows[0].count;
} catch (e) {
logger.err('$getLatestStatistics error: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
}
export default new StatisticsApi();

View File

@ -1,7 +1,10 @@
<div *ngIf="stateService.env.MINING_DASHBOARD" class="mb-3 d-inline-flex menu" style="padding: 0px 35px;">
<div *ngIf="stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING" class="mb-3 d-flex menu"
style="padding: 0px 35px;">
<a routerLinkActive="active" class="btn btn-primary w-50 mr-1"
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
<div ngbDropdown class="w-50">
<div ngbDropdown class="w-50" *ngIf="stateService.env.MINING_DASHBOARD">
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="mining">Mining</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/pools' | relativeUrl]"
@ -9,19 +12,30 @@
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]"
i18n="mining.pools-dominance">Pools Dominance</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="mining.hashrate-difficulty">Hashrate & Difficulty</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]" i18n="mining.block-fee-rates">Block Fee Rates</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-fees' | relativeUrl]" i18n="mining.block-fees">Block Fees</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" i18n="mining.block-rewards">Block Rewards</a>
[routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="mining.hashrate-difficulty">Hashrate &
Difficulty</a>
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]"
i18n="mining.block-fee-rates">Block Fee Rates</a>
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"
i18n="mining.block-fees">Block Fees</a>
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"
i18n="mining.block-rewards">Block Rewards</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</a>
</div>
</div>
<div ngbDropdown class="w-50" *ngIf="stateService.env.LIGHTNING">
<button class="btn btn-primary w-100" id="dropdownBasic1" ngbDropdownToggle i18n="lightning">Lightning</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"
i18n="lightning.nodes-networks">Nodes per network</a>
<a class="dropdown-item" routerLinkActive="active" [routerLink]="['/graphs/lightning/capacity' | relativeUrl]"
i18n="lightning.capacity">Network capacity</a>
</div>
</div>
</div>
<router-outlet></router-outlet>

View File

@ -18,6 +18,8 @@ import { StartComponent } from '../components/start/start.component';
import { StatisticsComponent } from '../components/statistics/statistics.component';
import { TelevisionComponent } from '../components/television/television.component';
import { DashboardComponent } from '../dashboard/dashboard.component';
import { NodesNetworksChartComponent } from '../lightning/nodes-networks-chart/nodes-networks-chart.component';
import { LightningStatisticsChartComponent } from '../lightning/statistics-chart/lightning-statistics-chart.component';
const browserWindow = window || {};
// @ts-ignore
@ -89,6 +91,14 @@ const routes: Routes = [
path: 'mining/block-sizes-weights',
component: BlockSizesWeightsGraphComponent,
},
{
path: 'lightning/nodes-networks',
component: NodesNetworksChartComponent,
},
{
path: 'lightning/capacity',
component: LightningStatisticsChartComponent,
},
{
path: '',
redirectTo: 'mempool',

View File

@ -56,7 +56,10 @@ export class LightningApiService {
return this.httpClient.get<any>(this.apiBasePath + '/channels/' + publicKey + '/statistics');
}
listStatistics$(): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/statistics');
listStatistics$(interval: string | undefined): Observable<any> {
return this.httpClient.get<any>(
this.apiBasePath + '/api/v1/lightning/statistics' +
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
);
}
}

View File

@ -31,7 +31,8 @@
<div class="col">
<div class="card">
<div class="card-body">
<app-lightning-statistics-chart [widget]=true></app-lightning-statistics-chart>
<app-nodes-networks-chart [widget]=true></app-nodes-networks-chart>
<div class="mt-1"><a [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>
@ -39,7 +40,8 @@
<div class="col">
<div class="card">
<div class="card-body">
<app-nodes-networks-chart [widget]=true></app-nodes-networks-chart>
<app-lightning-statistics-chart [widget]=true></app-lightning-statistics-chart>
<div class="mt-1"><a [routerLink]="['/graphs/lightning/capacity' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>

View File

@ -8,35 +8,26 @@
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(nodesNetworkObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 144">
<input ngbButton type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 24h
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 30">
<input ngbButton type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 432">
<input ngbButton type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 3D
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 90">
<input ngbButton type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 3M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 1008">
<input ngbButton type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 1W
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 180">
<input ngbButton type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 6M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 4320">
<input ngbButton type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 1M
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 365">
<input ngbButton type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 12960">
<input ngbButton type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 3M
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 730">
<input ngbButton type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 25920">
<input ngbButton type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 6M
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 1095">
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 52560">
<input ngbButton type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 105120">
<input ngbButton type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> ALL
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]"> ALL
</label>
</div>
</form>

View File

@ -1,12 +1,12 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
import { EChartsOption} from 'echarts';
import { Observable } from 'rxjs';
import { startWith, switchMap, tap } from 'rxjs/operators';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
import { download, formatterXAxis } from 'src/app/shared/graphs.utils';
import { download } from 'src/app/shared/graphs.utils';
import { SeoService } from 'src/app/services/seo.service';
import { LightningApiService } from '../lightning-api.service';
@ -62,7 +62,7 @@ export class NodesNetworksChartComponent implements OnInit {
this.miningWindowPreference = '1y';
} else {
this.seoService.setTitle($localize`Nodes per network`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('1m');
this.miningWindowPreference = this.miningService.getDefaultTimespan('all');
}
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
@ -73,14 +73,15 @@ export class NodesNetworksChartComponent implements OnInit {
switchMap((timespan) => {
this.timespan = timespan;
if (!this.widget && !firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.listStatistics$()
return this.lightningApiService.listStatistics$(timespan)
.pipe(
tap((data) => {
tap((response) => {
const data = response.body;
this.prepareChartOptions({
node_count: data.map(val => [val.added * 1000, val.node_count]),
tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]),
@ -89,9 +90,15 @@ export class NodesNetworksChartComponent implements OnInit {
});
this.isLoading = false;
}),
map((response) => {
return {
days: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
)
share()
);
}
prepareChartOptions(data) {

View File

@ -5,9 +5,43 @@
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(capacityObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 30">
<input ngbButton type="radio" [value]="'1m'" fragment="1m"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 90">
<input ngbButton type="radio" [value]="'3m'" fragment="3m"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 3M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 180">
<input ngbButton type="radio" [value]="'6m'" fragment="6m"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 6M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 365">
<input ngbButton type="radio" [value]="'1y'" fragment="1y"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 730">
<input ngbButton type="radio" [value]="'2y'" fragment="2y"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.days >= 1095">
<input ngbButton type="radio" [value]="'3y'" fragment="3y"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm">
<input ngbButton type="radio" [value]="'all'" fragment="all"
[routerLink]="['/graphs/lightning/capacity' | relativeUrl]"> ALL
</label>
</div>
</form>
</div>
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@ -1,7 +1,7 @@
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
import { EChartsOption, graphic } from 'echarts';
import { Observable } from 'rxjs';
import { startWith, switchMap, tap } from 'rxjs/operators';
import { map, startWith, switchMap, tap } from 'rxjs/operators';
import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
@ -38,7 +38,7 @@ export class LightningStatisticsChartComponent implements OnInit {
@HostBinding('attr.dir') dir = 'ltr';
blockSizesWeightsObservable$: Observable<any>;
capacityObservable$: Observable<any>;
isLoading = true;
formatNumber = formatNumber;
timespan = '';
@ -57,34 +57,44 @@ export class LightningStatisticsChartComponent implements OnInit {
ngOnInit(): void {
let firstRun = true;
this.miningWindowPreference = this.miningService.getDefaultTimespan('24h');
if (this.widget) {
this.miningWindowPreference = '1y';
} else {
this.seoService.setTitle($localize`Channels and Capacity`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('all');
}
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.radioGroupForm.get('dateSpan').valueChanges
this.capacityObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.miningWindowPreference),
switchMap((timespan) => {
this.timespan = timespan;
if (!firstRun) {
this.storageService.setValue('miningWindowPreference', timespan);
if (!this.widget && !firstRun) {
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.listStatistics$()
return this.lightningApiService.listStatistics$(timespan)
.pipe(
tap((data) => {
tap((response) => {
const data = response.body;
this.prepareChartOptions({
channel_count: data.map(val => [val.added * 1000, val.channel_count]),
capacity: data.map(val => [val.added * 1000, val.total_capacity]),
});
this.isLoading = false;
}),
map((response) => {
return {
days: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
).subscribe(() => {
});
)
}
prepareChartOptions(data) {