Merge pull request #4744 from mempool/mononaut/send-nodes

More status page polish
This commit is contained in:
wiz 2024-03-07 18:06:05 +09:00 committed by GitHub
commit 1d877a746f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 65 additions and 33 deletions

View file

@ -49,4 +49,5 @@ export interface HealthCheckHost {
outOfSync: boolean;
unreachable: boolean;
checked: boolean;
lastChecked: number;
}

View file

@ -18,6 +18,7 @@ interface FailoverHost {
unreachable?: boolean,
preferred?: boolean,
checked: boolean,
lastChecked?: number,
}
class FailoverRouter {
@ -122,7 +123,7 @@ class FailoverRouter {
}
}
host.checked = true;
host.lastChecked = Date.now();
// switch if the current host is out of sync or significantly slower than the next best alternative
const rankOrder = this.sortHosts();
@ -361,6 +362,7 @@ class ElectrsApi implements AbstractBitcoinApi {
outOfSync: !!host.outOfSync,
unreachable: !!host.unreachable,
checked: !!host.checked,
lastChecked: host.lastChecked || 0,
}));
} else {
return [];

View file

@ -1,4 +1,4 @@
<footer class="footer">
<footer class="footer" [class.inline-footer]="inline">
<div class="container-xl">
<div class="row text-center" *ngIf="mempoolInfoData$ | async as mempoolInfoData">
<div class="col d-none d-sm-block">

View file

@ -6,6 +6,12 @@
background-color: #1d1f31;
box-shadow: 15px 15px 15px 15px #000;
z-index: 10;
&.inline-footer {
position: relative;
bottom: unset;
top: -44px;
}
}
.sub-text {

View file

@ -1,4 +1,4 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { StateService } from '../../services/state.service';
import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
@ -23,6 +23,8 @@ interface MempoolInfoData {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent implements OnInit {
@Input() inline = false;
mempoolBlocksData$: Observable<MempoolBlocksData>;
mempoolInfoData$: Observable<MempoolInfoData>;
vBytesPerSecondLimit = 1667;

View file

@ -1,9 +1,11 @@
<div class="tomahawk container-xl dashboard-container">
<div class="tomahawk">
<div class="links">
<span>Status</span>
<a [routerLink]='"/network"'>Live</a>
<span>Monitoring</span>
<a [routerLink]='"/nodes"'>Nodes</a>
</div>
<h1 class="dashboard-title">Node Status</h1>
<app-start [showLoadingIndicator]="true"></app-start>
<app-footer [inline]="true"></app-footer>
<ng-container *ngIf="(hosts$ | async) as hosts">
<div class="status-panel">
@ -13,6 +15,7 @@
<th class="rank"></th>
<th class="flag"></th>
<th class="host">Host</th>
<th class="updated">Last checked</th>
<th class="rtt only-small">RTT</th>
<th class="rtt only-large">RTT</th>
<th class="height">Height</th>
@ -21,6 +24,7 @@
<td class="rank">{{ i + 1 }}</td>
<td class="flag">{{ host.active ? '⭐️' : host.flag }}</td>
<td class="host">{{ host.link }}</td>
<td class="updated">{{ getLastUpdateSeconds(host) }}</td>
<td class="rtt only-small">{{ (host.rtt / 1000) | number : '1.1-1' }} {{ host.rtt == null ? '' : 's'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
<td class="rtt only-large">{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
<td class="height">{{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < tip ? '🟧' : '')) }}</td>

View file

@ -1,22 +1,16 @@
.tomahawk {
.links {
float: right;
text-align: right;
margin-top: 1em;
margin-inline-end: 1em;
a, span {
margin-left: 1em;
}
}
.dashboard-title {
text-align: left;
}
.status-panel {
max-width: 720px;
margin: auto;
margin-top: 2em;
padding: 1em;
background: #24273e;
}
@ -31,6 +25,12 @@
width: 28px;
text-align: right;
}
&.updated {
display: none;
width: 130px;
text-align: right;
white-space: pre-wrap;
}
&.rtt, &.height {
width: 92px;
text-align: right;
@ -57,6 +57,9 @@
&.rank, &.flag {
width: 32px;
}
&.updated {
display: table-cell;
}
&.rtt, &.height {
width: 96px;
}

View file

@ -1,4 +1,4 @@
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext } from '@angular/core';
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, ChangeDetectorRef } from '@angular/core';
import { WebsocketService } from '../../services/websocket.service';
import { Observable, Subject, map } from 'rxjs';
import { StateService } from '../../services/state.service';
@ -14,17 +14,20 @@ import { DomSanitizer } from '@angular/platform-browser';
export class ServerHealthComponent implements OnInit {
hosts$: Observable<HealthCheckHost[]>;
tip$: Subject<number>;
interval: number;
now: number = Date.now();
constructor(
private websocketService: WebsocketService,
private stateService: StateService,
private cd: ChangeDetectorRef,
public sanitizer: DomSanitizer,
) {}
ngOnInit(): void {
this.hosts$ = this.stateService.serverHealth$.pipe(
map((hosts) => {
const subpath = window.location.pathname.slice(0, -6);
const subpath = window.location.pathname.slice(0, -11);
for (const host of hosts) {
let statusUrl = '';
let linkHost = '';
@ -44,13 +47,27 @@ export class ServerHealthComponent implements OnInit {
})
);
this.tip$ = this.stateService.chainTip$;
this.websocketService.want(['blocks', 'tomahawk']);
this.websocketService.want(['mempool-blocks', 'stats', 'blocks', 'tomahawk']);
this.interval = window.setInterval(() => {
this.now = Date.now();
this.cd.markForCheck();
}, 1000);
}
trackByFn(index: number, host: HealthCheckHost): string {
return host.host;
}
getLastUpdateSeconds(host: HealthCheckHost): string {
if (host.lastChecked) {
const seconds = Math.ceil((this.now - host.lastChecked) / 1000);
return `${seconds} second${seconds > 1 ? 's' : ' '} ago`;
} else {
return '~';
}
}
private parseFlag(host: string): string {
if (host.includes('.fra.')) {
return '🇩🇪';

View file

@ -1,12 +1,12 @@
<div class="tomahawk">
<div class="container-xl dashboard-container">
<div class="links">
<a [routerLink]='"/nodes"'>Status</a>
<span>Live</span>
</div>
<h1 class="dashboard-title">Live Network</h1>
<div class="links">
<a [routerLink]='"/monitoring"'>Monitoring</a>
<span>Nodes</span>
</div>
<app-start [showLoadingIndicator]="true"></app-start>
<app-footer [inline]="true"></app-footer>
<ng-container *ngFor="let host of hosts; trackBy: trackByFn">
<h5 [id]="host.host" class="hostLink">
<a [href]="'https://' + host.link">{{ host.link }}</a>

View file

@ -1,21 +1,17 @@
.tomahawk {
.links {
float: right;
text-align: right;
margin-top: 1em;
margin-inline-end: 1em;
a, span {
margin-left: 1em;
}
}
.dashboard-title {
text-align: left;
}
.mempoolStatus {
width: 100%;
height: 270px;
border: none;
}
.hostLink {

View file

@ -26,7 +26,7 @@ export class ServerStatusComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.hostSubscription = this.stateService.serverHealth$.pipe(
map((hosts) => {
const subpath = window.location.pathname.slice(0, -8);
const subpath = window.location.pathname.slice(0, -6);
for (const host of hosts) {
let statusUrl = '';
let linkHost = '';
@ -66,7 +66,7 @@ export class ServerStatusComponent implements OnInit, OnDestroy {
})
).subscribe();
this.tip$ = this.stateService.chainTip$;
this.websocketService.want(['blocks', 'tomahawk']);
this.websocketService.want(['mempool-blocks', 'stats', 'blocks', 'tomahawk']);
}
trackByFn(index: number, host: HealthCheckHost): string {

View file

@ -132,6 +132,7 @@ export interface HealthCheckHost {
outOfSync: boolean;
unreachable: boolean;
checked: boolean;
lastChecked: number;
link?: string;
statusPage?: SafeResourceUrl;
flag?: string;

View file

@ -101,12 +101,12 @@ const routes: Routes = [
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
routes[0].children.push({
path: 'nodes',
path: 'monitoring',
data: { networks: ['bitcoin', 'liquid'] },
component: ServerHealthComponent
});
routes[0].children.push({
path: 'network',
path: 'nodes',
data: { networks: ['bitcoin', 'liquid'] },
component: ServerStatusComponent
});