mirror of
https://github.com/mempool/mempool.git
synced 2025-04-22 06:27:02 +02:00
Merge pull request #4734 from mempool/mononaut/tomahawk-status-page
Tomahawk status page
This commit is contained in:
commit
62e0cb2e57
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": false,
|
||||
"NETWORK": "mainnet",
|
||||
"BACKEND": "electrum",
|
||||
"ENABLED": true,
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"ENABLED": true,
|
||||
"OFFICIAL": false,
|
||||
"NETWORK": "__MEMPOOL_NETWORK__",
|
||||
"BACKEND": "__MEMPOOL_BACKEND__",
|
||||
"BLOCKS_SUMMARIES_INDEXING": true,
|
||||
|
@ -14,6 +14,7 @@ describe('Mempool Backend Config', () => {
|
||||
|
||||
expect(config.MEMPOOL).toStrictEqual({
|
||||
ENABLED: true,
|
||||
OFFICIAL: false,
|
||||
NETWORK: 'mainnet',
|
||||
BACKEND: 'none',
|
||||
BLOCKS_SUMMARIES_INDEXING: false,
|
||||
|
@ -29,6 +29,7 @@ export interface AbstractBitcoinApi {
|
||||
$getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]>;
|
||||
|
||||
startHealthChecks(): void;
|
||||
getHealthStatus(): HealthCheckHost[];
|
||||
}
|
||||
export interface BitcoinRpcCredentials {
|
||||
host: string;
|
||||
@ -38,3 +39,14 @@ export interface BitcoinRpcCredentials {
|
||||
timeout: number;
|
||||
cookie?: string;
|
||||
}
|
||||
|
||||
export interface HealthCheckHost {
|
||||
host: string;
|
||||
active: boolean;
|
||||
rtt: number;
|
||||
latestHeight: number;
|
||||
socket: boolean;
|
||||
outOfSync: boolean;
|
||||
unreachable: boolean;
|
||||
checked: boolean;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import blocks from '../blocks';
|
||||
@ -382,6 +382,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
public startHealthChecks(): void {};
|
||||
|
||||
public getHealthStatus() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default BitcoinApi;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import config from '../../config';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import axios from 'axios';
|
||||
import http from 'http';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import logger from '../../logger';
|
||||
import { Common } from '../common';
|
||||
@ -157,7 +157,7 @@ class FailoverRouter {
|
||||
}
|
||||
|
||||
// sort hosts by connection quality, and update default fallback
|
||||
private sortHosts(): FailoverHost[] {
|
||||
public sortHosts(): FailoverHost[] {
|
||||
// sort by connection quality
|
||||
return this.hosts.slice().sort((a, b) => {
|
||||
if ((a.unreachable || a.outOfSync) === (b.unreachable || b.outOfSync)) {
|
||||
@ -342,6 +342,23 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
public startHealthChecks(): void {
|
||||
this.failoverRouter.startHealthChecks();
|
||||
}
|
||||
|
||||
public getHealthStatus(): HealthCheckHost[] {
|
||||
if (config.MEMPOOL.OFFICIAL) {
|
||||
return this.failoverRouter.sortHosts().map(host => ({
|
||||
host: host.host,
|
||||
active: host === this.failoverRouter.activeHost,
|
||||
rtt: host.rtt,
|
||||
latestHeight: host.latestHeight || 0,
|
||||
socket: !!host.socket,
|
||||
outOfSync: !!host.outOfSync,
|
||||
unreachable: !!host.unreachable,
|
||||
checked: !!host.checked,
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ElectrsApi;
|
||||
|
@ -26,6 +26,7 @@ import mempool from './mempool';
|
||||
import statistics from './statistics/statistics';
|
||||
import accelerationCosts from './acceleration';
|
||||
import accelerationRepository from '../repositories/AccelerationRepository';
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
|
||||
interface AddressTransactions {
|
||||
mempool: MempoolTransactionExtended[],
|
||||
@ -39,6 +40,7 @@ const wantable = [
|
||||
'mempool-blocks',
|
||||
'live-2h-chart',
|
||||
'stats',
|
||||
'tomahawk',
|
||||
];
|
||||
|
||||
class WebsocketHandler {
|
||||
@ -123,7 +125,7 @@ class WebsocketHandler {
|
||||
for (const sub of wantable) {
|
||||
const key = `want-${sub}`;
|
||||
const wants = parsedMessage.data.includes(sub);
|
||||
if (wants && client['wants'] && !client[key]) {
|
||||
if (wants && !client[key]) {
|
||||
wantNow[key] = true;
|
||||
}
|
||||
client[key] = wants;
|
||||
@ -147,6 +149,10 @@ class WebsocketHandler {
|
||||
response['da'] = this.socketData['da'];
|
||||
}
|
||||
|
||||
if (wantNow['want-tomahawk']) {
|
||||
response['tomahawk'] = JSON.stringify(bitcoinApi.getHealthStatus());
|
||||
}
|
||||
|
||||
if (parsedMessage && parsedMessage['track-tx']) {
|
||||
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
||||
client['track-tx'] = parsedMessage['track-tx'];
|
||||
@ -546,6 +552,10 @@ class WebsocketHandler {
|
||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||
}
|
||||
|
||||
if (client['want-tomahawk']) {
|
||||
response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus());
|
||||
}
|
||||
|
||||
if (client['track-mempool-tx']) {
|
||||
const tx = newTransactions.find((t) => t.txid === client['track-mempool-tx']);
|
||||
if (tx) {
|
||||
@ -909,6 +919,10 @@ class WebsocketHandler {
|
||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||
}
|
||||
|
||||
if (client['want-tomahawk']) {
|
||||
response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus());
|
||||
}
|
||||
|
||||
if (client['track-tx']) {
|
||||
const trackTxid = client['track-tx'];
|
||||
if (trackTxid && confirmedTxids[trackTxid]) {
|
||||
|
@ -5,6 +5,7 @@ const configFromFile = require(
|
||||
interface IConfig {
|
||||
MEMPOOL: {
|
||||
ENABLED: boolean;
|
||||
OFFICIAL: boolean;
|
||||
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
||||
BACKEND: 'esplora' | 'electrum' | 'none';
|
||||
HTTP_PORT: number;
|
||||
@ -161,6 +162,7 @@ interface IConfig {
|
||||
const defaults: IConfig = {
|
||||
'MEMPOOL': {
|
||||
'ENABLED': true,
|
||||
'OFFICIAL': false,
|
||||
'NETWORK': 'mainnet',
|
||||
'BACKEND': 'none',
|
||||
'HTTP_PORT': 8999,
|
||||
|
@ -3,6 +3,7 @@
|
||||
"NETWORK": "__MEMPOOL_NETWORK__",
|
||||
"BACKEND": "__MEMPOOL_BACKEND__",
|
||||
"ENABLED": __MEMPOOL_ENABLED__,
|
||||
"OFFICIAL": __MEMPOOL_OFFICIAL__,
|
||||
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
|
||||
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
|
||||
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
||||
|
@ -4,6 +4,7 @@
|
||||
__MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet}
|
||||
__MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum}
|
||||
__MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
|
||||
__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false}
|
||||
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
|
||||
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
|
||||
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
|
||||
@ -158,6 +159,7 @@ mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_BACKEND__!${__MEMPOOL_BACKEND__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
|
||||
|
@ -0,0 +1,28 @@
|
||||
<div class="tomahawk container-xl dashboard-container">
|
||||
<div class="links">
|
||||
<span>Status</span>
|
||||
<a [routerLink]='"/network"'>Live</a>
|
||||
</div>
|
||||
<h1 class="dashboard-title">Node Status</h1>
|
||||
|
||||
<ng-container *ngIf="(hosts$ | async) as hosts">
|
||||
<div class="status-panel">
|
||||
<table class="status-table table table-borderless table-striped" *ngIf="(tip$ | async) as tip">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="active"></th>
|
||||
<th class="host">Host</th>
|
||||
<th class="rtt">RTT</th>
|
||||
<th class="height">Height</th>
|
||||
</tr>
|
||||
<tr *ngFor="let host of hosts;">
|
||||
<td class="active"><span *ngIf="host.active">⭐️</span></td>
|
||||
<td class="host">{{ host.host }}</td>
|
||||
<td class="rtt">{{ 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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
@ -0,0 +1,34 @@
|
||||
.tomahawk {
|
||||
.links {
|
||||
float: right;
|
||||
text-align: right;
|
||||
margin-top: 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;
|
||||
}
|
||||
|
||||
.status-table {
|
||||
width: 100%;
|
||||
|
||||
td, th {
|
||||
padding: 0.25em;
|
||||
&.rtt, &.height {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext } from '@angular/core';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { Observable, Subject, map } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { HealthCheckHost } from '../../interfaces/websocket.interface';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-health',
|
||||
templateUrl: './server-health.component.html',
|
||||
styleUrls: ['./server-health.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ServerHealthComponent implements OnInit {
|
||||
hosts$: Observable<HealthCheckHost[]>;
|
||||
tip$: Subject<number>;
|
||||
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private stateService: StateService,
|
||||
public sanitizer: DomSanitizer,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.hosts$ = this.stateService.serverHealth$.pipe(
|
||||
map((hosts) => {
|
||||
const subpath = window.location.pathname.slice(0, -6);
|
||||
for (const host of hosts) {
|
||||
let statusUrl = '';
|
||||
let linkHost = '';
|
||||
if (host.socket) {
|
||||
statusUrl = window.location.host + subpath + '/status';
|
||||
linkHost = window.location.host + subpath;
|
||||
} else {
|
||||
const hostUrl = new URL(host.host);
|
||||
statusUrl = 'https://' + hostUrl.hostname + subpath + '/status';
|
||||
linkHost = hostUrl.hostname + subpath;
|
||||
}
|
||||
host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl));
|
||||
host.link = linkHost;
|
||||
}
|
||||
return hosts;
|
||||
})
|
||||
);
|
||||
this.tip$ = this.stateService.chainTip$;
|
||||
this.websocketService.want(['blocks', 'tomahawk']);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<div class="tomahawk container-xl dashboard-container">
|
||||
<div class="links">
|
||||
<a [routerLink]='"/nodes"'>Status</a>
|
||||
<span>Live</span>
|
||||
</div>
|
||||
<h1 class="dashboard-title">Live Network</h1>
|
||||
|
||||
<ng-container *ngFor="let host of hosts; trackBy: trackByFn">
|
||||
<h5 [id]="host.host" class="hostLink">
|
||||
<a [href]="'https://' + host.link">{{ host.link }}</a>
|
||||
</h5>
|
||||
<iframe class="mempoolStatus" [src]="host.statusPage"></iframe>
|
||||
</ng-container>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
.tomahawk {
|
||||
.links {
|
||||
float: right;
|
||||
text-align: right;
|
||||
margin-top: 1em;
|
||||
|
||||
a, span {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mempoolStatus {
|
||||
width: 100%;
|
||||
height: 270px;
|
||||
}
|
||||
|
||||
.hostLink {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { Observable, Subject, Subscription, map, tap } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { HealthCheckHost } from '../../interfaces/websocket.interface';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-status',
|
||||
templateUrl: './server-status.component.html',
|
||||
styleUrls: ['./server-status.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ServerStatusComponent implements OnInit, OnDestroy {
|
||||
tip$: Subject<number>;
|
||||
hosts: HealthCheckHost[] = [];
|
||||
hostSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private websocketService: WebsocketService,
|
||||
private stateService: StateService,
|
||||
private cd: ChangeDetectorRef,
|
||||
public sanitizer: DomSanitizer,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.hostSubscription = this.stateService.serverHealth$.pipe(
|
||||
map((hosts) => {
|
||||
const subpath = window.location.pathname.slice(0, -8);
|
||||
for (const host of hosts) {
|
||||
let statusUrl = '';
|
||||
let linkHost = '';
|
||||
if (host.socket) {
|
||||
statusUrl = window.location.host + subpath + '/status';
|
||||
linkHost = window.location.host + subpath;
|
||||
} else {
|
||||
const hostUrl = new URL(host.host);
|
||||
statusUrl = 'https://' + hostUrl.hostname + subpath + '/status';
|
||||
linkHost = hostUrl.hostname + subpath;
|
||||
}
|
||||
host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl));
|
||||
host.link = linkHost;
|
||||
}
|
||||
return hosts;
|
||||
}),
|
||||
tap((hosts) => {
|
||||
if (this.hosts.length !== hosts.length) {
|
||||
this.hosts = hosts.sort((a,b) => {
|
||||
const aParts = (a.host?.split('.') || []).reverse();
|
||||
const bParts = (b.host?.split('.') || []).reverse();
|
||||
let i = 0;
|
||||
while (i < Math.max(aParts.length, bParts.length)) {
|
||||
if (aParts[i] && !bParts[i]) {
|
||||
return 1;
|
||||
} else if (bParts[i] && !aParts[i]) {
|
||||
return -1;
|
||||
} else if (aParts[i] !== bParts[i]) {
|
||||
return aParts[i].localeCompare(bParts[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
})
|
||||
).subscribe();
|
||||
this.tip$ = this.stateService.chainTip$;
|
||||
this.websocketService.want(['blocks', 'tomahawk']);
|
||||
}
|
||||
|
||||
trackByFn(index: number, host: HealthCheckHost): string {
|
||||
return host.host;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.hosts = [];
|
||||
this.hostSubscription.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { ILoadingIndicators } from '../services/state.service';
|
||||
import { Transaction } from './electrs.interface';
|
||||
import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface';
|
||||
@ -120,4 +121,17 @@ export interface Recommendedfees {
|
||||
hourFee: number;
|
||||
minimumFee: number;
|
||||
economyFee: number;
|
||||
}
|
||||
|
||||
export interface HealthCheckHost {
|
||||
host: string;
|
||||
active: boolean;
|
||||
rtt: number;
|
||||
latestHeight: number;
|
||||
socket: boolean;
|
||||
outOfSync: boolean;
|
||||
unreachable: boolean;
|
||||
checked: boolean;
|
||||
link?: string;
|
||||
statusPage?: SafeResourceUrl;
|
||||
}
|
@ -19,6 +19,8 @@ import { RecentPegsListComponent } from '../components/liquid-reserves-audit/rec
|
||||
import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component';
|
||||
import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component';
|
||||
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
|
||||
import { ServerHealthComponent } from '../components/server-health/server-health.component';
|
||||
import { ServerStatusComponent } from '../components/server-health/server-status.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -140,6 +142,19 @@ const routes: Routes = [
|
||||
},
|
||||
];
|
||||
|
||||
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||
routes[0].children.push({
|
||||
path: 'nodes',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerHealthComponent
|
||||
});
|
||||
routes[0].children.push({
|
||||
path: 'network',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerStatusComponent
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
|
@ -10,6 +10,8 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
|
||||
import { CalculatorComponent } from './components/calculator/calculator.component';
|
||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||
import { RbfList } from './components/rbf-list/rbf-list.component';
|
||||
import { ServerHealthComponent } from './components/server-health/server-health.component';
|
||||
import { ServerStatusComponent } from './components/server-health/server-status.component';
|
||||
|
||||
const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
@ -96,6 +98,19 @@ const routes: Routes = [
|
||||
}
|
||||
];
|
||||
|
||||
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||
routes[0].children.push({
|
||||
path: 'nodes',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerHealthComponent
|
||||
});
|
||||
routes[0].children.push({
|
||||
path: 'network',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerStatusComponent
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionCompressed, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||
import { StorageService } from './storage.service';
|
||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
||||
import { ApiService } from './api.service';
|
||||
import { ActiveFilter } from '../shared/filters.utils';
|
||||
|
||||
export interface MarkBlockState {
|
||||
@ -129,6 +128,7 @@ export class StateService {
|
||||
loadingIndicators$ = new ReplaySubject<ILoadingIndicators>(1);
|
||||
recommendedFees$ = new ReplaySubject<Recommendedfees>(1);
|
||||
chainTip$ = new ReplaySubject<number>(-1);
|
||||
serverHealth$ = new Subject<HealthCheckHost[]>();
|
||||
|
||||
live2Chart$ = new Subject<OptimizedMempoolStats>();
|
||||
|
||||
|
@ -415,6 +415,10 @@ export class WebsocketService {
|
||||
this.stateService.previousRetarget$.next(response.previousRetarget);
|
||||
}
|
||||
|
||||
if (response['tomahawk']) {
|
||||
this.stateService.serverHealth$.next(response['tomahawk']);
|
||||
}
|
||||
|
||||
if (response['git-commit']) {
|
||||
this.stateService.backendInfo$.next(response['git-commit']);
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ import { AssetComponent } from '../components/asset/asset.component';
|
||||
import { AssetsComponent } from '../components/assets/assets.component';
|
||||
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
|
||||
import { StatusViewComponent } from '../components/status-view/status-view.component';
|
||||
import { ServerHealthComponent } from '../components/server-health/server-health.component';
|
||||
import { ServerStatusComponent } from '../components/server-health/server-status.component';
|
||||
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
|
||||
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
|
||||
import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component';
|
||||
@ -151,6 +153,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
||||
AssetComponent,
|
||||
AssetsComponent,
|
||||
StatusViewComponent,
|
||||
ServerHealthComponent,
|
||||
ServerStatusComponent,
|
||||
FeesBoxComponent,
|
||||
DifficultyComponent,
|
||||
DifficultyMiningComponent,
|
||||
@ -277,6 +281,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
||||
AssetComponent,
|
||||
AssetsComponent,
|
||||
StatusViewComponent,
|
||||
ServerHealthComponent,
|
||||
ServerStatusComponent,
|
||||
FeesBoxComponent,
|
||||
DifficultyComponent,
|
||||
DifficultyMiningComponent,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "bisq",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8996,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "liquid",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8998,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "liquid",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8994,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"ENABLED": false,
|
||||
"NETWORK": "mainnet",
|
||||
"BACKEND": "esplora",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "mainnet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8999,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"ENABLED": false,
|
||||
"NETWORK": "signet",
|
||||
"BACKEND": "esplora",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "signet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8995,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"ENABLED": false,
|
||||
"NETWORK": "testnet",
|
||||
"BACKEND": "esplora",
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"OFFICIAL": true,
|
||||
"NETWORK": "testnet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8997,
|
||||
|
Loading…
x
Reference in New Issue
Block a user