Merge branch 'master' into hunicus/move-on-in-it

This commit is contained in:
hunicus
2024-04-02 15:26:11 +09:00
committed by GitHub
519 changed files with 22961 additions and 18042 deletions

View File

@@ -66,9 +66,7 @@
</div>
<ng-template [ngIf]="error">
<div class="text-center">
<app-http-error [error]="error">
<span i18n="error.general-loading-data">Error loading data.</span>
<br><br>
<i>{{ error.status }}: {{ error.error }}</i>
</div>
</app-http-error>
</ng-template>

View File

@@ -8,6 +8,7 @@ import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesP
providedIn: 'root'
})
export class LightningApiService {
private apiBaseUrl: string; // base URL is protocol, hostname, and port
private apiBasePath = ''; // network path is /testnet, etc. or '' for mainnet
private requestCache = new Map<string, { subject: BehaviorSubject<any>, expiry: number }>;
@@ -16,11 +17,12 @@ export class LightningApiService {
private httpClient: HttpClient,
private stateService: StateService,
) {
this.apiBaseUrl = ''; // use relative URL by default
if (!stateService.isBrowser) { // except when inside AU SSR process
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
if (network === 'bisq' && !this.stateService.env.BISQ_SEPARATE_BACKEND) {
network = '';
}
this.apiBasePath = network ? '/' + network : '';
});
}
@@ -66,15 +68,15 @@ export class LightningApiService {
}
getNode$(publicKey: string): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey);
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey);
}
getNodeGroup$(name: string): Observable<any[]> {
return this.httpClient.get<any[]>(this.apiBasePath + '/api/v1/lightning/nodes/group/' + name);
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/group/' + name);
}
getChannel$(shortId: string): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels/' + shortId);
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels/' + shortId);
}
getChannelsByNodeId$(publicKey: string, index: number = 0, status = 'open'): Observable<any> {
@@ -84,57 +86,57 @@ export class LightningApiService {
.set('status', status)
;
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/channels', { params, observe: 'response' });
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels', { params, observe: 'response' });
}
getLatestStatistics$(): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/statistics/latest');
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/statistics/latest');
}
listNodeStats$(publicKey: string): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics');
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics');
}
getNodeFeeHistogram$(publicKey: string): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/fees/histogram');
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/fees/histogram');
}
getNodesRanking$(): Observable<INodesRanking> {
return this.httpClient.get<INodesRanking>(this.apiBasePath + '/api/v1/lightning/nodes/rankings');
return this.httpClient.get<INodesRanking>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings');
}
listChannelStats$(publicKey: string): Observable<any> {
return this.httpClient.get<any>(this.apiBasePath + '/channels/' + publicKey + '/statistics');
return this.httpClient.get<any>(this.apiBaseUrl + this.apiBasePath + '/channels/' + publicKey + '/statistics');
}
listStatistics$(interval: string | undefined): Observable<any> {
return this.httpClient.get<any>(
this.apiBasePath + '/api/v1/lightning/statistics' +
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/statistics' +
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
);
}
getTopNodesByCapacity$(): Observable<ITopNodesPerCapacity[]> {
return this.httpClient.get<ITopNodesPerCapacity[]>(
this.apiBasePath + '/api/v1/lightning/nodes/rankings/liquidity'
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/liquidity'
);
}
getTopNodesByChannels$(): Observable<ITopNodesPerChannels[]> {
return this.httpClient.get<ITopNodesPerChannels[]>(
this.apiBasePath + '/api/v1/lightning/nodes/rankings/connectivity'
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/connectivity'
);
}
getPenaltyClosedChannels$(): Observable<IChannel[]> {
return this.httpClient.get<IChannel[]>(
this.apiBasePath + '/api/v1/lightning/penalties'
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/penalties'
);
}
getOldestNodes$(): Observable<IOldestNodes[]> {
return this.httpClient.get<IOldestNodes[]>(
this.apiBasePath + '/api/v1/lightning/nodes/rankings/age'
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/rankings/age'
);
}
}

View File

@@ -34,9 +34,11 @@
<!-- ISP pie chart -->
<div class="col" style="margin-bottom: 1.47rem">
<div class="card graph-card">
<div class="card">
<div class="card-body pl-2 pr-2">
<app-nodes-per-isp-chart [widget]="true"></app-nodes-per-isp-chart>
<div class="mempool-graph">
<app-nodes-per-isp-chart [height]="graphHeight" [widget]="true"></app-nodes-per-isp-chart>
</div>
<div style="margin-top: 5px"><a [attr.data-cy]="'pool-distribution-view-more'" [routerLink]="['/graphs/lightning/nodes-per-isp' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
@@ -44,11 +46,13 @@
<!-- Network history -->
<div class="col">
<div class="card graph-card">
<div class="card">
<div class="card-body pl-2 pr-2 pt-1">
<h5 class="card-title mt-3" i18n="lightning.network-history">Lightning Network History</h5>
<app-lightning-statistics-chart [widget]=true></app-lightning-statistics-chart>
<app-nodes-networks-chart [widget]=true></app-nodes-networks-chart>
<div class="mempool-graph">
<h5 class="card-title mt-3" i18n="lightning.network-history">Lightning Network History</h5>
<app-lightning-statistics-chart [height]="(graphHeight / 1.7)" [widget]=true></app-lightning-statistics-chart>
<app-nodes-networks-chart [height]="(graphHeight / 1.7)" [widget]=true></app-nodes-networks-chart>
</div>
<div><a [routerLink]="['/graphs/lightning/nodes-networks' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>

View File

@@ -20,6 +20,19 @@
}
}
.fixed-mempool-graph {
height: 330px;
}
.mempool-graph, .fixed-mempool-graph {
@media (min-width: 768px) {
height: 345px;
}
@media (min-width: 992px) {
height: 439px;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;

View File

@@ -1,8 +1,9 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit } from '@angular/core';
import { Observable, merge } from 'rxjs';
import { share } from 'rxjs/operators';
import { INodesRanking, INodesStatistics } from '../../interfaces/node-api.interface';
import { SeoService } from '../../services/seo.service';
import { OpenGraphService } from '../../services/opengraph.service';
import { StateService } from '../../services/state.service';
import { LightningApiService } from '../lightning-api.service';
@@ -16,22 +17,45 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit {
statistics$: Observable<INodesStatistics>;
nodesRanking$: Observable<INodesRanking>;
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
graphHeight: number = 300;
constructor(
private lightningApiService: LightningApiService,
private seoService: SeoService,
private ogService: OpenGraphService,
private stateService: StateService,
private cd: ChangeDetectorRef,
) { }
ngOnInit(): void {
this.onResize();
this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`);
this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`);
this.ogService.setManualOgImage('lightning.jpg');
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());
if (!this.stateService.isBrowser) {
merge(this.nodesRanking$, this.statistics$).subscribe(() => {
this.cd.markForCheck();
});
}
}
ngAfterViewInit(): void {
this.stateService.focusSearchInputDesktop();
}
@HostListener('window:resize', ['$event'])
onResize(): void {
if (window.innerWidth >= 992) {
this.graphHeight = 340;
} else if (window.innerWidth >= 768) {
this.graphHeight = 245;
} else {
this.graphHeight = 210;
}
}
}

View File

@@ -34,7 +34,6 @@ import { OldestNodes } from '../lightning/nodes-ranking/oldest-nodes/oldest-node
import { NodesRankingsDashboard } from '../lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component';
import { NodeChannels } from '../lightning/nodes-channels/node-channels.component';
import { GroupComponent } from './group/group.component';
import { NodeOwnerComponent } from './node-owner/node-owner.component';
@NgModule({
declarations: [
@@ -67,7 +66,6 @@ import { NodeOwnerComponent } from './node-owner/node-owner.component';
NodesRankingsDashboard,
NodeChannels,
GroupComponent,
NodeOwnerComponent,
],
imports: [
CommonModule,
@@ -105,7 +103,6 @@ import { NodeOwnerComponent } from './node-owner/node-owner.component';
OldestNodes,
NodesRankingsDashboard,
NodeChannels,
NodeOwnerComponent,
],
providers: [
LightningApiService,

View File

@@ -1,7 +1,7 @@
<div class="full-container">
<h2 i18n="lightning.node-fee-distribution">Fee distribution</h2>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="chart" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>
</div>

View File

@@ -1,5 +1,7 @@
.full-container {
position: relative;
margin-top: 25px;
margin-bottom: 25px;
min-height: 100%;
}
min-height: 450px;
}

View File

@@ -5,6 +5,7 @@ import { download } from '../../shared/graphs.utils';
import { LightningApiService } from '../lightning-api.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-node-fee-chart',
@@ -33,6 +34,7 @@ export class NodeFeeChartComponent implements OnInit {
constructor(
@Inject(LOCALE_ID) public locale: string,
private lightningApiService: LightningApiService,
public stateService: StateService,
private activatedRoute: ActivatedRoute,
private amountShortenerPipe: AmountShortenerPipe,
) {

View File

@@ -1,17 +0,0 @@
<div *ngIf="stateService.env.OFFICIAL_MEMPOOL_SPACE === true">
<div *ngIf="{ value: (nodeOwner$ | async) } as nodeOwner">
<div *ngIf="nodeOwner.value && nodeOwner.value.sns_id">
<a target="_blank" [href]="'https://twitter.com/' + nodeOwner.value.username">
<img class="profile-photo" [src]="'data:' + nodeOwner.value.image_mime + ';base64,' + nodeOwner.value.image">
</a>
</div>
<div *ngIf="nodeOwner.value === false">
<a [href]="'/login/lnnode?type=signup&pubkey=' + publicKey + '&alias=' + alias" class="btn btn-primary btn-sm">Claim</a>
<div>
</div>
</div>

View File

@@ -1,4 +0,0 @@
.profile-photo {
width: 31px;
height: 31px;
}

View File

@@ -1,20 +0,0 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-node-owner',
templateUrl: './node-owner.component.html',
styleUrls: ['./node-owner.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodeOwnerComponent{
@Input() publicKey: string = '';
@Input() alias: string = '';
@Input() nodeOwner$: Observable<any>;
constructor(
public stateService: StateService
) {
}
}

View File

@@ -1,7 +1,7 @@
<div class="full-container">
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -8,6 +8,7 @@ import { StorageService } from '../../services/storage.service';
import { download } from '../../shared/graphs.utils';
import { LightningApiService } from '../lightning-api.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-node-statistics-chart',
@@ -48,6 +49,7 @@ export class NodeStatisticsChartComponent implements OnInit {
@Inject(LOCALE_ID) public locale: string,
private lightningApiService: LightningApiService,
private storageService: StorageService,
public stateService: StateService,
private activatedRoute: ActivatedRoute,
) {
}

View File

@@ -5,7 +5,6 @@
<div class="title-container mb-2">
<div class="d-flex justify-content-between align-items-center">
<h1 class="mb-0 text-truncate">{{ node.alias }}</h1>
<!-- <app-node-owner [nodeOwner$]="nodeOwner$" [publicKey]="node.public_key" [alias]="node.alias" class="claim-btn"></app-node-owner> -->
</div>
<span class="tx-link justify-content-between align-items-center">
<span class="node-id">
@@ -13,7 +12,6 @@
<app-clipboard [text]="node.public_key"></app-clipboard>
</app-truncate>
</span>
<!-- <app-node-owner [nodeOwner$]="nodeOwner$" [publicKey]="node.public_key" [alias]="node.alias" class="claim-btn-mobile"></app-node-owner> -->
</span>
</div>
</ng-container>

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable, of, EMPTY } from 'rxjs';
import { Observable, of } from 'rxjs';
import { catchError, map, switchMap, tap, share } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { ApiService } from '../../services/api.service';
@@ -8,6 +8,7 @@ import { LightningApiService } from '../lightning-api.service';
import { GeolocationData } from '../../shared/components/geolocation/geolocation.component';
import { ILiquidityAd, parseLiquidityAdHex } from './liquidity-ad';
import { haversineDistance, kmToMiles } from '../../../app/shared/common.utils';
import { ServicesApiServices } from '../../services/services-api.service';
interface CustomRecord {
type: string;
@@ -38,11 +39,11 @@ export class NodeComponent implements OnInit {
tlvRecords: CustomRecord[];
avgChannelDistance$: Observable<number | null>;
showFeatures = false;
nodeOwner$: Observable<any>;
kmToMiles = kmToMiles;
constructor(
private apiService: ApiService,
private servicesApiService: ServicesApiServices,
private lightningApiService: LightningApiService,
private activatedRoute: ActivatedRoute,
private seoService: SeoService,
@@ -151,24 +152,6 @@ export class NodeComponent implements OnInit {
return null;
})
) as Observable<number | null>;
this.nodeOwner$ = this.activatedRoute.paramMap
.pipe(
switchMap((params: ParamMap) => {
return this.apiService.getNodeOwner$(params.get('public_key')).pipe(
switchMap((response) => {
if (response.status === 204) {
return of(false);
}
return of(response.body);
}),
catchError(() => {
return of(false);
})
)
}),
share(),
);
}
toggleShowDetails(): void {

View File

@@ -1,12 +1,13 @@
<div class="map-wrapper" [class]="style" *ngIf="style !== 'graph'">
<ng-container *ngIf="channelsObservable | async">
<div *ngIf="chartOptions" [class]="'full-container ' + style + (fitContainer ? ' fit-container' : '')">
<div class="chart" [class]="style" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart" [class]="style" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
<div *ngIf="!chartOptions && style === 'nodepage'" style="padding-top: 30px"></div>
</div>
<div class="text-center loading-spinner" [class]="style" *ngIf="isLoading && !disableSpinner">
<div class="text-center loading-spinner" [class]="style" *ngIf="(!stateService.isBrowser || isLoading) && !disableSpinner">
<div class="spinner-border text-light"></div>
</div>
</ng-container>
@@ -21,8 +22,10 @@
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
</div>
<div *ngIf="channelsObservable | async" class="chart-graph" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
<ng-container *ngIf="channelsObservable | async">
<div class="chart-graph" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
</ng-container>
</div>

View File

@@ -33,7 +33,6 @@
min-height: 400px;
margin-top: 25px;
margin-bottom: 25px;
min-height: 100%;
}
.full-container.widget {
height: 250px;

View File

@@ -1,13 +1,16 @@
import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core';
import { SeoService } from '../../services/seo.service';
import { ApiService } from '../../services/api.service';
import { delay, Observable, switchMap, tap, zip } from 'rxjs';
import { delay, Observable, of, switchMap, tap, zip } from 'rxjs';
import { AssetsService } from '../../services/assets.service';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { StateService } from '../../services/state.service';
import { EChartsOption, echarts } from '../../graphs/echarts';
import { isMobile } from '../../shared/common.utils';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { getFlagEmoji } from '../../shared/common.utils';
import { lerpColor } from '../../shared/graphs.utils';
@Component({
selector: 'app-nodes-channels-map',
@@ -45,11 +48,12 @@ export class NodesChannelsMap implements OnInit {
constructor(
private seoService: SeoService,
private apiService: ApiService,
private stateService: StateService,
public stateService: StateService,
private assetsService: AssetsService,
private router: Router,
private zone: NgZone,
private activatedRoute: ActivatedRoute,
private amountShortenerPipe: AmountShortenerPipe,
) {
}
@@ -86,10 +90,12 @@ export class NodesChannelsMap implements OnInit {
return zip(
this.assetsService.getWorldMapJson$,
this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''],
[params.get('public_key') ?? undefined]
[params.get('public_key') ?? undefined],
this.style === 'widget' ? of(undefined) : this.apiService.getWorldNodes$(),
).pipe(tap((data) => {
echarts.registerMap('world', data[0]);
let maxLiquidity = data[3]?.maxLiquidity;
const channelsLoc = [];
const nodes = [];
const nodesPubkeys = {};
@@ -197,13 +203,24 @@ export class NodesChannelsMap implements OnInit {
this.zoom = -0.05 * distance + 8;
}
this.prepareChartOptions(nodes, channelsLoc);
if (data[3]) {
for (const node of nodes) {
const foundNode = data[3].nodes.find((n) => n[2] === node[3]);
if (foundNode) {
node.push(foundNode[4], foundNode[5], foundNode[6]?.en, foundNode[7]);
maxLiquidity = Math.max(maxLiquidity ?? 0, foundNode[4]);
}
}
}
maxLiquidity = Math.max(1, maxLiquidity);
this.prepareChartOptions(nodes, channelsLoc, maxLiquidity);
}));
})
);
}
prepareChartOptions(nodes, channels) {
prepareChartOptions(nodes, channels, maxLiquidity) {
let title: object;
if (channels.length === 0) {
if (!this.placeholder) {
@@ -267,7 +284,12 @@ export class NodesChannelsMap implements OnInit {
data: nodes,
coordinateSystem: 'geo',
geoIndex: 0,
symbolSize: this.nodeSize,
symbolSize: (params) => {
if (maxLiquidity) {
return 10 * Math.pow(params[5] / maxLiquidity, 0.2) + 3;
}
return this.nodeSize;
},
tooltip: {
show: true,
backgroundColor: 'rgba(17, 19, 31, 1)',
@@ -281,11 +303,25 @@ export class NodesChannelsMap implements OnInit {
formatter: (value) => {
const data = value.data;
const alias = data[4].length > 0 ? data[4] : data[3].slice(0, 20);
return `<b style="color: white">${alias}</b>`;
}
const liquidity = data[5] >= 100000000 ?
`${this.amountShortenerPipe.transform(data[5] / 100000000)} BTC` :
`${this.amountShortenerPipe.transform(data[5], 2)} sats`;
return `
<b style="color: white">${alias}</b><br>
${liquidity}<br>` +
$localize`:@@205c1b86ac1cc419c4d0cca51fdde418c4ffdc20:${data[6]}:INTERPOLATION: channels` + `<br>
${getFlagEmoji(data[8])} ${data[7]}
`;
},
},
itemStyle: {
color: 'white',
color: (params) => {
if (!maxLiquidity) {
return 'white';
}
return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[5] / maxLiquidity, 0.2))}`;
},
opacity: 1,
borderColor: 'black',
borderWidth: 0,
@@ -361,8 +397,6 @@ export class NodesChannelsMap implements OnInit {
}
chartOptions.series[0].itemStyle.borderWidth = nodeBorder;
chartOptions.series[0].symbolSize += e.zoom > 1 ? speed * 15 : -speed * 15;
chartOptions.series[0].symbolSize = Math.max(4, Math.min(7, chartOptions.series[0].symbolSize));
chartOptions.series[1].lineStyle.opacity += e.zoom > 1 ? speed : -speed;
chartOptions.series[1].lineStyle.width += e.zoom > 1 ? speed : -speed;

View File

@@ -1,9 +1,11 @@
<div *ngIf="channelsObservable$ | async" style="min-height: 455px">
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
<div echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
<div class="node-channels-container">
<div *ngIf="channelsObservable$ | async" style="min-height: 455px">
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
<div *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
</div>
</div>
<div *ngIf="!stateService.isBrowser || isLoading" class="text-center loading-spinner">
<div class="spinner-border text-light"></div>
</div>
</div>
<div *ngIf="isLoading" class="text-center loading-spinner">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -1,5 +1,13 @@
.node-channels-container {
position: relative;
}
.loading-spinner {
min-height: 455px;
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 100;
}

View File

@@ -33,7 +33,7 @@ export class NodeChannels implements OnChanges {
private amountShortenerPipe: AmountShortenerPipe,
private zone: NgZone,
private router: Router,
private stateService: StateService,
public stateService: StateService,
) {}
ngOnChanges(): void {

View File

@@ -7,8 +7,13 @@
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
</div>
<div *ngIf="observable$ | async" class="chart" [class]="widget ? 'widget' : ''" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
<ng-container *ngIf="observable$ | async">
<div class="chart" [class]="widget ? 'widget' : ''" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
</div>
</ng-container>
<div class="text-center loading-spinner" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>
</div>

View File

@@ -63,3 +63,13 @@
.chart.widget {
padding: 0px;
}
.loading-spinner {
position: absolute;
top: 50%;
left: calc(50% - 15px);
z-index: 100;
@media (max-width: 767.98px) {
top: 550px;
}
}

View File

@@ -26,6 +26,7 @@ export class NodesMap implements OnInit, OnChanges {
inputNodes$: BehaviorSubject<any>;
nodes$: Observable<any>;
observable$: Observable<any>;
isLoading: boolean = true;
chartInstance = undefined;
chartOptions: EChartsOption = {};
@@ -37,7 +38,7 @@ export class NodesMap implements OnInit, OnChanges {
@Inject(LOCALE_ID) public locale: string,
private seoService: SeoService,
private apiService: ApiService,
private stateService: StateService,
public stateService: StateService,
private assetsService: AssetsService,
private router: Router,
private zone: NgZone,
@@ -88,7 +89,7 @@ export class NodesMap implements OnInit, OnChanges {
node.public_key,
node.alias,
node.capacity,
node.active_channel_count,
node.channels,
node.country,
node.iso_code,
]);
@@ -226,6 +227,7 @@ export class NodesMap implements OnInit, OnChanges {
},
]
};
this.isLoading = false;
}
onChartInit(ec) {
@@ -235,6 +237,10 @@ export class NodesMap implements OnInit, OnChanges {
this.chartInstance = ec;
this.chartInstance.on('finished', () => {
this.isLoading = false;
});
this.chartInstance.on('click', (e) => {
if (e.data) {
this.zone.run(() => {

View File

@@ -35,8 +35,8 @@
</form>
</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]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -56,7 +56,6 @@
}
.chart-widget {
width: 100%;
height: 145px;
}
.pool-distribution {

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding, OnChanges, SimpleChanges } from '@angular/core';
import { echarts, EChartsOption, LineSeriesOption } from '../../graphs/echarts';
import { Observable } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
@@ -11,6 +11,7 @@ import { SeoService } from '../../services/seo.service';
import { LightningApiService } from '../lightning-api.service';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { isMobile } from '../../shared/common.utils';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-nodes-networks-chart',
@@ -26,7 +27,8 @@ import { isMobile } from '../../shared/common.utils';
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesNetworksChartComponent implements OnInit {
export class NodesNetworksChartComponent implements OnInit, OnChanges {
@Input() height: number = 150;
@Input() right: number | string = 45;
@Input() left: number | string = 45;
@Input() widget = false;
@@ -47,6 +49,9 @@ export class NodesNetworksChartComponent implements OnInit {
timespan = '';
chartInstance: any = undefined;
chartData: any;
maxYAxis: number;
constructor(
@Inject(LOCALE_ID) public locale: string,
private seoService: SeoService,
@@ -54,6 +59,7 @@ export class NodesNetworksChartComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
public stateService: StateService,
private amountShortenerPipe: AmountShortenerPipe,
) {
}
@@ -71,44 +77,49 @@ export class NodesNetworksChartComponent implements OnInit {
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.nodesNetworkObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.miningWindowPreference),
switchMap((timespan) => {
this.timespan = timespan;
if (!this.widget && !firstRun) {
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan)
.pipe(
tap((response:any) => {
const data = response.body;
const chartData = {
tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]),
clearnet_nodes: data.map(val => [val.added * 1000, val.clearnet_nodes]),
unannounced_nodes: data.map(val => [val.added * 1000, val.unannounced_nodes]),
clearnet_tor_nodes: data.map(val => [val.added * 1000, val.clearnet_tor_nodes]),
};
let maxYAxis = 0;
for (const day of data) {
maxYAxis = Math.max(maxYAxis, day.tor_nodes + day.clearnet_nodes + day.unannounced_nodes + day.clearnet_tor_nodes);
}
maxYAxis = Math.ceil(maxYAxis / 3000) * 3000;
this.prepareChartOptions(chartData, maxYAxis);
this.isLoading = false;
}),
map((response) => {
return {
days: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
share()
);
this.nodesNetworkObservable$ = this.radioGroupForm.get('dateSpan').valueChanges.pipe(
startWith(this.miningWindowPreference),
switchMap((timespan) => {
this.timespan = timespan;
if (!this.widget && !firstRun) {
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan)
.pipe(
tap((response:any) => {
const data = response.body;
this.chartData = {
tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]),
clearnet_nodes: data.map(val => [val.added * 1000, val.clearnet_nodes]),
unannounced_nodes: data.map(val => [val.added * 1000, val.unannounced_nodes]),
clearnet_tor_nodes: data.map(val => [val.added * 1000, val.clearnet_tor_nodes]),
};
this.maxYAxis = 0;
for (const day of data) {
this.maxYAxis = Math.max(this.maxYAxis, day.tor_nodes + day.clearnet_nodes + day.unannounced_nodes + day.clearnet_tor_nodes);
}
this.maxYAxis = Math.ceil(this.maxYAxis / 3000) * 3000;
this.prepareChartOptions(this.chartData, this.maxYAxis);
this.isLoading = false;
}),
map((response) => {
return {
days: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
share()
);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.height && this.chartData && this.maxYAxis != null) {
this.prepareChartOptions(this.chartData, this.maxYAxis);
}
}
prepareChartOptions(data, maxYAxis): void {
@@ -228,7 +239,7 @@ export class NodesNetworksChartComponent implements OnInit {
title: title,
animation: false,
grid: {
height: this.widget ? 90 : undefined,
height: this.widget ? ((this.height || 120) - 60) : undefined,
top: this.widget ? 20 : 40,
bottom: this.widget ? 0 : 70,
right: (isMobile() && this.widget) ? 35 : this.right,

View File

@@ -12,12 +12,12 @@
<div class="container pb-lg-0">
<div class="pb-lg-5">
<div class="chart w-100" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div class="chart w-100" *browserOnly echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -37,7 +37,7 @@ export class NodesPerCountryChartComponent implements OnInit {
private seoService: SeoService,
private amountShortenerPipe: AmountShortenerPipe,
private zone: NgZone,
private stateService: StateService,
public stateService: StateService,
private router: Router,
) {
}

View File

@@ -64,8 +64,8 @@
<th class="channels text-right" i18n="lightning.channels">Channels</th>
<th class="city text-right" i18n="lightning.location">Location</th>
</thead>
<tbody *ngIf="nodes$ | async as countryNodes; else skeleton">
<tr *ngFor="let node of countryNodes.nodes; let i= index; trackBy: trackByPublicKey">
<tbody *ngIf="nodesPagination$ | async as countryNodes; else skeleton">
<tr *ngFor="let node of countryNodes; let i= index; trackBy: trackByPublicKey">
<td class="alias text-left text-truncate">
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
</td>
@@ -116,5 +116,10 @@
</ng-template>
</table>
<ngb-pagination *ngIf="nodes$ | async as countryNodes" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
[collectionSize]="countryNodes.nodes.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="pageSize" [(page)]="page"
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination>
</div>
</div>

View File

@@ -22,14 +22,14 @@
.timestamp-first {
width: 20%;
@media (max-width: 576px) {
@media (max-width: 1060px) {
display: none
}
}
.timestamp-update {
width: 16%;
@media (max-width: 576px) {
@media (max-width: 1060px) {
display: none
}
}
@@ -50,7 +50,7 @@
.city {
max-width: 150px;
@media (max-width: 576px) {
@media (max-width: 675px) {
display: none
}
}

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable, share } from 'rxjs';
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { getFlagEmoji } from '../../shared/common.utils';
@@ -15,15 +15,22 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
export class NodesPerCountry implements OnInit {
nodes$: Observable<any>;
country: {name: string, flag: string};
nodesPagination$: Observable<any>;
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
page = 1;
pageSize = 15;
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
isLoading = true;
skeletonLines: number[] = [];
constructor(
private apiService: ApiService,
private seoService: SeoService,
private cd: ChangeDetectorRef,
private route: ActivatedRoute,
) {
for (let i = 0; i < 20; ++i) {
for (let i = 0; i < this.pageSize; ++i) {
this.skeletonLines.push(i);
}
}
@@ -31,6 +38,7 @@ export class NodesPerCountry implements OnInit {
ngOnInit(): void {
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
.pipe(
tap(() => this.isLoading = true),
map(response => {
this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`);
this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country:Explore all the Lightning nodes hosted in ${response.country.en} and see an overview of each node's capacity, number of open channels, and more.`);
@@ -87,11 +95,24 @@ export class NodesPerCountry implements OnInit {
ispCount: Object.keys(isps).length
};
}),
tap(() => {
this.isLoading = false
this.cd.markForCheck();
}),
share()
);
this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
);
}
trackByPublicKey(index: number, node: any): string {
return node.public_key;
}
pageChange(page: number): void {
this.startingIndexSubject.next((page - 1) * this.pageSize);
this.page = page;
}
}

View File

@@ -39,14 +39,10 @@
</div>
<div *ngIf="!indexingInProgress else indexing" [class]="!widget ? '' : 'pb-0'" class="container pb-lg-0">
<div [class]="widget ? 'chart-widget' : 'chart'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class]="widget ? 'chart-widget' : 'chart'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
<div class="d-flex justify-content-md-end toggle" *ngIf="!widget">
<app-toggle [textLeft]="'Sort by nodes'" [textRight]="'capacity'" [checked]="true" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
</div>
@@ -74,6 +70,9 @@
</tbody>
</table>
</div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>
</div>
<ng-template #loadingReward>

View File

@@ -18,6 +18,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NodesPerISPChartComponent implements OnInit {
@Input() height: number = 300;
@Input() widget: boolean = false;
isLoading = true;
@@ -43,7 +44,7 @@ export class NodesPerISPChartComponent implements OnInit {
private amountShortenerPipe: AmountShortenerPipe,
private router: Router,
private zone: NgZone,
private stateService: StateService,
public stateService: StateService,
) {
}

View File

@@ -61,8 +61,8 @@
<th class="channels text-right" i18n="lightning.channels">Channels</th>
<th class="city text-right" i18n="lightning.location">Location</th>
</thead>
<tbody *ngIf="nodes$ | async as ispNodes; else skeleton">
<tr *ngFor="let node of ispNodes.nodes; let i= index; trackBy: trackByPublicKey">
<tbody *ngIf="nodesPagination$ | async as ispNodes; else skeleton">
<tr *ngFor="let node of ispNodes; let i= index; trackBy: trackByPublicKey">
<td class="alias text-left text-truncate">
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
</td>
@@ -113,5 +113,10 @@
</ng-template>
</table>
<ngb-pagination *ngIf="nodes$ | async as ispNodes" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
[collectionSize]="ispNodes.nodes.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="pageSize" [(page)]="page"
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination>
</div>
</div>

View File

@@ -24,7 +24,7 @@
.timestamp-first {
width: 20%;
@media (max-width: 576px) {
@media (max-width: 1060px) {
display: none
}
}
@@ -32,7 +32,7 @@
.timestamp-update {
width: 16%;
@media (max-width: 576px) {
@media (max-width: 1060px) {
display: none
}
}
@@ -56,7 +56,7 @@
.city {
max-width: 150px;
@media (max-width: 576px) {
@media (max-width: 675px) {
display: none
}
}

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable, share } from 'rxjs';
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
import { ApiService } from '../../services/api.service';
import { SeoService } from '../../services/seo.service';
import { getFlagEmoji } from '../../shared/common.utils';
@@ -15,6 +15,12 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
export class NodesPerISP implements OnInit {
nodes$: Observable<any>;
isp: {name: string, id: number};
nodesPagination$: Observable<any>;
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
page = 1;
pageSize = 15;
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
isLoading = true;
skeletonLines: number[] = [];
@@ -23,7 +29,7 @@ export class NodesPerISP implements OnInit {
private seoService: SeoService,
private route: ActivatedRoute,
) {
for (let i = 0; i < 20; ++i) {
for (let i = 0; i < this.pageSize; ++i) {
this.skeletonLines.push(i);
}
}
@@ -31,6 +37,7 @@ export class NodesPerISP implements OnInit {
ngOnInit(): void {
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
.pipe(
tap(() => this.isLoading = true),
map(response => {
this.isp = {
name: response.isp,
@@ -77,11 +84,21 @@ export class NodesPerISP implements OnInit {
topCountry: topCountry,
};
}),
tap(() => this.isLoading = false),
share()
);
this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
);
}
trackByPublicKey(index: number, node: any): string {
return node.public_key;
}
pageChange(page: number): void {
this.startingIndexSubject.next((page - 1) * this.pageSize);
this.page = page;
}
}

View File

@@ -42,9 +42,9 @@
</form>
</div>
<div [class]="!widget ? 'chart' : 'chart-widget'" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
<div [class]="!widget ? 'chart' : 'chart-widget'" *browserOnly [style]="{ height: widget ? (height + 'px') : null}" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)"></div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="spinner-border text-light"></div>
</div>

View File

@@ -56,7 +56,6 @@
}
.chart-widget {
width: 100%;
height: 145px;
}
.pool-distribution {

View File

@@ -1,6 +1,6 @@
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core';
import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding, OnChanges, SimpleChanges } from '@angular/core';
import { echarts, EChartsOption } from '../../graphs/echarts';
import { Observable } from 'rxjs';
import { Observable, combineLatest, fromEvent } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { formatNumber } from '@angular/common';
@@ -11,6 +11,7 @@ import { download } from '../../shared/graphs.utils';
import { LightningApiService } from '../lightning-api.service';
import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe';
import { isMobile } from '../../shared/common.utils';
import { StateService } from '../../services/state.service';
@Component({
selector: 'app-lightning-statistics-chart',
@@ -25,7 +26,8 @@ import { isMobile } from '../../shared/common.utils';
}
`],
})
export class LightningStatisticsChartComponent implements OnInit {
export class LightningStatisticsChartComponent implements OnInit, OnChanges {
@Input() height: number = 150;
@Input() right: number | string = 45;
@Input() left: number | string = 45;
@Input() widget = false;
@@ -37,6 +39,7 @@ export class LightningStatisticsChartComponent implements OnInit {
chartInitOptions = {
renderer: 'svg',
};
chartData: any;
@HostBinding('attr.dir') dir = 'ltr';
@@ -53,6 +56,7 @@ export class LightningStatisticsChartComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
private storageService: StorageService,
private miningService: MiningService,
public stateService: StateService,
private amountShortenerPipe: AmountShortenerPipe,
) {
}
@@ -70,36 +74,42 @@ export class LightningStatisticsChartComponent implements OnInit {
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.capacityObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.miningWindowPreference),
switchMap((timespan) => {
this.timespan = timespan;
if (!this.widget && !firstRun) {
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan)
.pipe(
tap((response:any) => {
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),
};
}),
);
}),
share(),
);
this.capacityObservable$ = this.radioGroupForm.get('dateSpan').valueChanges.pipe(
startWith(this.miningWindowPreference),
switchMap((timespan) => {
this.timespan = timespan;
if (!this.widget && !firstRun) {
this.storageService.setValue('lightningWindowPreference', timespan);
}
firstRun = false;
this.miningWindowPreference = timespan;
this.isLoading = true;
return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan)
.pipe(
tap((response:any) => {
const data = response.body;
this.chartData = {
channel_count: data.map(val => [val.added * 1000, val.channel_count]),
capacity: data.map(val => [val.added * 1000, val.total_capacity]),
};
this.prepareChartOptions(this.chartData);
this.isLoading = false;
}),
map((response) => {
return {
days: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
share(),
);
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.height && this.chartData) {
this.prepareChartOptions(this.chartData);
}
}
prepareChartOptions(data): void {
@@ -138,7 +148,7 @@ export class LightningStatisticsChartComponent implements OnInit {
]),
],
grid: {
height: this.widget ? 90 : undefined,
height: this.widget ? ((this.height || 120) - 60) : undefined,
top: this.widget ? 20 : 40,
bottom: this.widget ? 0 : 70,
right: (isMobile() && this.widget) ? 35 : this.right,