More status page polish
This commit is contained in:
		
							parent
							
								
									31e320b2e2
								
							
						
					
					
						commit
						be52fd4e46
					
				@ -49,4 +49,5 @@ export interface HealthCheckHost {
 | 
			
		||||
  outOfSync: boolean;
 | 
			
		||||
  unreachable: boolean;
 | 
			
		||||
  checked: boolean;
 | 
			
		||||
  lastChecked: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 [];
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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 '🇩🇪';
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -132,6 +132,7 @@ export interface HealthCheckHost {
 | 
			
		||||
  outOfSync: boolean;
 | 
			
		||||
  unreachable: boolean;
 | 
			
		||||
  checked: boolean;
 | 
			
		||||
  lastChecked: number;
 | 
			
		||||
  link?: string;
 | 
			
		||||
  statusPage?: SafeResourceUrl;
 | 
			
		||||
  flag?: string;
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user