Add ISP chart in the dashboard - Fix mobile layout - Start polishing
This commit is contained in:
@@ -1,6 +1,29 @@
|
||||
<div class="full-container h-100">
|
||||
<div [class]="widget === false ? 'full-container' : ''">
|
||||
|
||||
<div class="card-header">
|
||||
<div *ngIf="widget">
|
||||
<div class="pool-distribution" *ngIf="(nodesPerAsObservable$ | async) as stats; else loadingReward">
|
||||
<div class="item">
|
||||
<h5 class="card-title d-inline-block" i18n="lightning.tagged-isp">Tagged ISPs</h5>
|
||||
<p class="card-text">
|
||||
{{ stats.taggedISP }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title d-inline-block" i18n="lightning.tagged-capacity">Tagged capacity</h5>
|
||||
<p class="card-text" i18n-ngbTooltip="mining.blocks-count-desc">
|
||||
<app-amount [satoshis]="stats.taggedCapacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title d-inline-block" i18n="lightning.tagged-nodes">Tagged nodes</h5>
|
||||
<p class="card-text" i18n-ngbTooltip="mining.pools-count-desc">
|
||||
{{ stats.taggedNodeCount }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-header" *ngIf="!widget">
|
||||
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
||||
<span i18n="lightning.nodes-per-isp">Lightning nodes per ISP</span>
|
||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
||||
@@ -12,23 +35,21 @@
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="container pb-lg-0 bottom-padding">
|
||||
<div class="pb-lg-5" *ngIf="nodesPerAsObservable$ | async">
|
||||
<div class="chart w-100" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
|
||||
(chartInit)="onChartInit($event)">
|
||||
</div>
|
||||
<div [class]="!widget ? 'bottom-padding' : 'pb-0'" class="container pb-lg-0">
|
||||
<div [class]="widget ? 'chart-widget' : 'chart'" 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 toggle">
|
||||
<div class="d-flex toggle" *ngIf="!widget">
|
||||
<app-toggle [textLeft]="'Show Tor'" [textRight]="" (toggleStatusChanged)="onTorToggleStatusChanged($event)"></app-toggle>
|
||||
<app-toggle [textLeft]="'Nodes'" [textRight]="'Capacity'" (toggleStatusChanged)="onGroupToggleStatusChanged($event)"></app-toggle>
|
||||
</div>
|
||||
|
||||
<table class="table table-borderless text-center m-auto" style="max-width: 900px">
|
||||
<table class="table table-borderless text-center m-auto" style="max-width: 900px" *ngIf="!widget">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="rank text-left pl-0" i18n="mining.rank">Rank</th>
|
||||
@@ -39,7 +60,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody [attr.data-cy]="'pools-table'" *ngIf="(nodesPerAsObservable$ | async) as asList">
|
||||
<tr *ngFor="let asEntry of asList">
|
||||
<tr *ngFor="let asEntry of asList.data">
|
||||
<td class="rank text-left pl-0">{{ asEntry.rank }}</td>
|
||||
<td class="name text-left text-truncate">
|
||||
<a *ngIf="asEntry.ispId" [routerLink]="[('/lightning/nodes/isp/' + asEntry.ispId) | relativeUrl]">{{ asEntry.name }}</a>
|
||||
@@ -54,3 +75,26 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ng-template #loadingReward>
|
||||
<div class="pool-distribution">
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="lightning.tagged-isp">Tagged ISPs</h5>
|
||||
<p class="card-text">
|
||||
<span class="skeleton-loader skeleton-loader-big"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="lightning.tagged-capacity">Tagged capacity</h5>
|
||||
<p class="card-text">
|
||||
<span class="skeleton-loader skeleton-loader-big"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="lightning.tagged-nodes">Tagged nodes</h5>
|
||||
<p class="card-text">
|
||||
<span class="skeleton-loader skeleton-loader-big"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -22,7 +22,40 @@
|
||||
max-height: 400px;
|
||||
@media (max-width: 767.98px) {
|
||||
max-height: 230px;
|
||||
margin-top: -35px;
|
||||
margin-top: -40px;
|
||||
}
|
||||
}
|
||||
.chart-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 240px;
|
||||
@media (max-width: 485px) {
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.formRadioGroup {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@media (min-width: 991px) {
|
||||
position: relative;
|
||||
top: -65px;
|
||||
}
|
||||
@media (min-width: 830px) and (max-width: 991px) {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
}
|
||||
@media (min-width: 830px) {
|
||||
flex-direction: row;
|
||||
float: right;
|
||||
margin-top: 0px;
|
||||
}
|
||||
.btn-sm {
|
||||
font-size: 9px;
|
||||
@media (min-width: 830px) {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +68,79 @@
|
||||
};
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.pools-table th,
|
||||
.pools-table td {
|
||||
padding: .3em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loadingGraphs {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: calc(50% - 15px);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.pool-distribution {
|
||||
min-height: 56px;
|
||||
display: block;
|
||||
@media (min-width: 485px) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
h5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.item {
|
||||
max-width: 160px;
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
margin: 0px auto 20px;
|
||||
&:nth-child(2) {
|
||||
order: 2;
|
||||
@media (min-width: 485px) {
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
&:nth-child(3) {
|
||||
width: 50%;
|
||||
order: 3;
|
||||
@media (min-width: 485px) {
|
||||
order: 2;
|
||||
display: block;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.card-title {
|
||||
font-size: 1rem;
|
||||
color: #4a68b9;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.card-text {
|
||||
font-size: 18px;
|
||||
span {
|
||||
color: #ffffff66;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-loader {
|
||||
width: 100%;
|
||||
display: block;
|
||||
max-width: 80px;
|
||||
margin: 15px auto 3px;
|
||||
}
|
||||
|
||||
.rank {
|
||||
width: 15%;
|
||||
@media (max-width: 576px) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||
import { combineLatest, map, Observable, share, Subject, switchMap, tap } from 'rxjs';
|
||||
import { combineLatest, map, Observable, share, startWith, Subject, switchMap, tap } from 'rxjs';
|
||||
import { chartColors } from 'src/app/app.constants';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { isMobile } from 'src/app/shared/common.utils';
|
||||
import { download } from 'src/app/shared/graphs.utils';
|
||||
import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe';
|
||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||
@@ -17,6 +18,8 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NodesPerISPChartComponent implements OnInit {
|
||||
@Input() widget: boolean = false;
|
||||
|
||||
isLoading = true;
|
||||
chartOptions: EChartsOption = {};
|
||||
chartInitOptions = {
|
||||
@@ -46,7 +49,11 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
this.seoService.setTitle($localize`Lightning nodes per ISP`);
|
||||
|
||||
this.showTorObservable$ = this.showTorSubject.asObservable();
|
||||
this.nodesPerAsObservable$ = combineLatest([this.groupBySubject, this.showTorSubject])
|
||||
|
||||
this.nodesPerAsObservable$ = combineLatest([
|
||||
this.groupBySubject.pipe(startWith(false)),
|
||||
this.showTorSubject.pipe(startWith(false)),
|
||||
])
|
||||
.pipe(
|
||||
switchMap((selectedFilters) => {
|
||||
return this.apiService.getNodesPerAs(
|
||||
@@ -62,23 +69,41 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
data[i].rank = i + 1;
|
||||
}
|
||||
return data.slice(0, 100);
|
||||
return {
|
||||
taggedISP: data.length,
|
||||
taggedCapacity: data.reduce((partialSum, isp) => partialSum + isp.capacity, 0),
|
||||
taggedNodeCount: data.reduce((partialSum, isp) => partialSum + isp.count, 0),
|
||||
data: data.slice(0, 100),
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
share()
|
||||
);
|
||||
|
||||
if (this.widget) {
|
||||
this.showTorSubject.next(false);
|
||||
this.groupBySubject.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
generateChartSerieData(as): PieSeriesOption[] {
|
||||
const shareThreshold = this.isMobile() ? 2 : 0.5;
|
||||
let shareThreshold = 0.5;
|
||||
if (this.widget && isMobile() || isMobile()) {
|
||||
shareThreshold = 1;
|
||||
} else if (this.widget) {
|
||||
shareThreshold = 0.75;
|
||||
}
|
||||
|
||||
const data: object[] = [];
|
||||
let totalShareOther = 0;
|
||||
let totalNodeOther = 0;
|
||||
|
||||
let edgeDistance: string | number = '10%';
|
||||
if (this.isMobile()) {
|
||||
if (isMobile() && this.widget) {
|
||||
edgeDistance = 0;
|
||||
} else if (isMobile() && !this.widget || this.widget) {
|
||||
edgeDistance = 10;
|
||||
}
|
||||
|
||||
as.forEach((as) => {
|
||||
@@ -92,15 +117,16 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
color: as.ispId === null ? '#7D4698' : undefined,
|
||||
},
|
||||
value: as.share,
|
||||
name: as.name + (this.isMobile() ? `` : ` (${as.share}%)`),
|
||||
name: as.name + (isMobile() || this.widget ? `` : ` (${as.share}%)`),
|
||||
label: {
|
||||
overflow: 'truncate',
|
||||
width: isMobile() ? 75 : this.widget ? 125 : 250,
|
||||
color: '#b1b1b1',
|
||||
alignTo: 'edge',
|
||||
edgeDistance: edgeDistance,
|
||||
},
|
||||
tooltip: {
|
||||
show: !this.isMobile(),
|
||||
show: !isMobile(),
|
||||
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||
borderRadius: 4,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
@@ -125,7 +151,7 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
color: 'grey',
|
||||
},
|
||||
value: totalShareOther,
|
||||
name: 'Other' + (this.isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||
name: 'Other' + (isMobile() || this.widget ? `` : ` (${totalShareOther.toFixed(2)}%)`),
|
||||
label: {
|
||||
overflow: 'truncate',
|
||||
color: '#b1b1b1',
|
||||
@@ -153,7 +179,7 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
|
||||
prepareChartOptions(as): void {
|
||||
let pieSize = ['20%', '80%']; // Desktop
|
||||
if (this.isMobile()) {
|
||||
if (isMobile() && !this.widget) {
|
||||
pieSize = ['15%', '60%'];
|
||||
}
|
||||
|
||||
@@ -177,8 +203,8 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
},
|
||||
length: this.isMobile() ? 1 : 20,
|
||||
length2: this.isMobile() ? 1 : undefined,
|
||||
length: isMobile() ? 1 : 20,
|
||||
length2: isMobile() ? 1 : undefined,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
@@ -204,10 +230,6 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
isMobile(): boolean {
|
||||
return (window.innerWidth <= 767.98);
|
||||
}
|
||||
|
||||
onChartInit(ec): void {
|
||||
if (this.chartInstance !== undefined) {
|
||||
return;
|
||||
@@ -244,5 +266,9 @@ export class NodesPerISPChartComponent implements OnInit {
|
||||
onGroupToggleStatusChanged(e): void {
|
||||
this.groupBySubject.next(e);
|
||||
}
|
||||
|
||||
isEllipsisActive(e) {
|
||||
return (e.offsetWidth < e.scrollWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user