Merge pull request #5156 from mempool/simon/default-frontend-network-setting

Root frontend network setting
This commit is contained in:
softsimon 2024-06-22 07:56:13 +09:00 committed by GitHub
commit 8d66374374
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 69 additions and 37 deletions

View File

@ -16,6 +16,7 @@ fi
# Runtime overrides - read env vars defined in docker compose
__MAINNET_ENABLED__=${MAINNET_ENABLED:=true}
__TESTNET_ENABLED__=${TESTNET_ENABLED:=false}
__SIGNET_ENABLED__=${SIGNET_ENABLED:=false}
__LIQUID_ENABLED__=${LIQUID_ENABLED:=false}
@ -28,6 +29,7 @@ __NGINX_PORT__=${NGINX_PORT:=8999}
__BLOCK_WEIGHT_UNITS__=${BLOCK_WEIGHT_UNITS:=4000000}
__MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_BLOCKS_AMOUNT:=8}
__BASE_MODULE__=${BASE_MODULE:=mempool}
__ROOT_NETWORK__=${ROOT_NETWORK:=}
__MEMPOOL_WEBSITE_URL__=${MEMPOOL_WEBSITE_URL:=https://mempool.space}
__LIQUID_WEBSITE_URL__=${LIQUID_WEBSITE_URL:=https://liquid.network}
__MINING_DASHBOARD__=${MINING_DASHBOARD:=true}
@ -42,6 +44,7 @@ __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
__ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false}
# Export as environment variables to be used by envsubst
export __MAINNET_ENABLED__
export __TESTNET_ENABLED__
export __SIGNET_ENABLED__
export __LIQUID_ENABLED__
@ -54,6 +57,7 @@ export __NGINX_PORT__
export __BLOCK_WEIGHT_UNITS__
export __MEMPOOL_BLOCKS_AMOUNT__
export __BASE_MODULE__
export __ROOT_NETWORK__
export __MEMPOOL_WEBSITE_URL__
export __LIQUID_WEBSITE_URL__
export __MINING_DASHBOARD__

View File

@ -4,6 +4,7 @@
"SIGNET_ENABLED": false,
"LIQUID_ENABLED": false,
"LIQUID_TESTNET_ENABLED": false,
"MAINNET_ENABLED": true,
"ITEMS_PER_PAGE": 10,
"KEEP_BLOCKS_AMOUNT": 8,
"NGINX_PROTOCOL": "http",
@ -12,6 +13,7 @@
"BLOCK_WEIGHT_UNITS": 4000000,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"BASE_MODULE": "mempool",
"ROOT_NETWORK": "",
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
"LIQUID_WEBSITE_URL": "https://liquid.network",
"MINING_DASHBOARD": true,

View File

@ -62,12 +62,12 @@
}
</a>
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="env.TESTNET_ENABLED || env.TESTNET4_ENABLED || env.SIGNET_ENABLED || env.LIQUID_ENABLED || env.LIQUID_TESTNET_ENABLED">
<div (window:resize)="onResize()" ngbDropdown class="dropdown-container" *ngIf="isDropdownVisible">
<button ngbDropdownToggle type="button" class="btn btn-secondary dropdown-toggle-split d-flex justify-content-center align-items-center" aria-haspopup="true">
<app-svg-images class="d-flex justify-content-center align-items-center current-network-svg" [name]="network.val === '' ? 'bitcoin' : network.val" width="20" height="20" viewBox="0 0 65 65"></app-svg-images>
</button>
<div ngbDropdownMenu [ngClass]="{'dropdown-menu-right' : isMobile}">
<a ngbDropdownItem class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
<a ngbDropdownItem *ngIf="env.MAINNET_ENABLED" class="mainnet" [routerLink]="networkPaths['mainnet'] || '/'"><app-svg-images name="bitcoin" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Mainnet</a>
<a ngbDropdownItem *ngIf="env.SIGNET_ENABLED" class="signet" [class.active]="network.val === 'signet'" [routerLink]="networkPaths['signet'] || '/signet'"><app-svg-images name="signet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Signet</a>
<a ngbDropdownItem *ngIf="env.TESTNET_ENABLED" class="testnet" [class.active]="network.val === 'testnet'" [routerLink]="networkPaths['testnet'] || '/testnet'"><app-svg-images name="testnet" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet3</a>
<a ngbDropdownItem *ngIf="env.TESTNET4_ENABLED" class="testnet4" [class.active]="network.val === 'testnet4'" [routerLink]="networkPaths['testnet4'] || '/testnet4'"><app-svg-images name="testnet4" width="22" height="22" viewBox="0 0 65 65" style="width: 25px; height: 25px;" class="mainnet mr-1"></app-svg-images> Testnet4 <span class="badge badge-pill badge-warning beta-network" i18n="beta">beta</span></a>

View File

@ -31,6 +31,7 @@ export class MasterPageComponent implements OnInit, OnDestroy {
user: any = undefined;
servicesEnabled = false;
menuOpen = false;
isDropdownVisible: boolean;
enterpriseInfo: any;
enterpriseInfo$: Subscription;
@ -74,19 +75,27 @@ export class MasterPageComponent implements OnInit, OnDestroy {
const isServicesPage = this.router.url.includes('/services/');
this.menuOpen = isServicesPage && !this.isSmallScreen();
this.setDropdownVisibility();
}
ngOnDestroy() {
if (this.enterpriseInfo$) {
this.enterpriseInfo$.unsubscribe();
}
setDropdownVisibility(): void {
const networks = [
this.env.TESTNET_ENABLED,
this.env.TESTNET4_ENABLED,
this.env.SIGNET_ENABLED,
this.env.LIQUID_ENABLED,
this.env.LIQUID_TESTNET_ENABLED,
this.env.MAINNET_ENABLED,
];
const enabledNetworksCount = networks.filter((networkEnabled) => networkEnabled).length;
this.isDropdownVisible = enabledNetworksCount > 1;
}
collapse(): void {
this.navCollapsed = !this.navCollapsed;
}
isSmallScreen() {
isSmallScreen(): boolean {
return window.innerWidth <= 767.98;
}
@ -117,4 +126,11 @@ export class MasterPageComponent implements OnInit, OnDestroy {
menuToggled(isOpen: boolean): void {
this.menuOpen = isOpen;
}
ngOnDestroy(): void {
if (this.enterpriseInfo$) {
this.enterpriseInfo$.unsubscribe();
}
}
}

View File

@ -23,7 +23,7 @@ export class LightningApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
this.apiBasePath = network ? '/' + network : '';
this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}

View File

@ -29,7 +29,7 @@ export class ApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
this.apiBasePath = network ? '/' + network : '';
this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}

View File

@ -25,7 +25,7 @@ export class ElectrsApiService {
}
this.apiBasePath = ''; // assume mainnet by default
this.stateService.networkChanged$.subscribe((network) => {
this.apiBasePath = network ? '/' + network : '';
this.apiBasePath = network && network !== this.stateService.env.ROOT_NETWORK ? '/' + network : '';
});
}

View File

@ -1,32 +1,31 @@
import { Injectable } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router';
import { Router, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { StateService } from './state.service';
const networkModules = {
bitcoin: {
subnets: [
{ name: 'mainnet', path: '' },
{ name: 'testnet', path: '/testnet' },
{ name: 'testnet4', path: '/testnet4' },
{ name: 'signet', path: '/signet' },
],
},
liquid: {
subnets: [
{ name: 'liquid', path: '' },
{ name: 'liquidtestnet', path: '/testnet' },
],
}
};
const networks = Object.keys(networkModules);
@Injectable({
providedIn: 'root'
})
export class NavigationService {
subnetPaths = new BehaviorSubject<Record<string,string>>({});
networkModules = {
bitcoin: {
subnets: [
{ name: 'mainnet', path: '' },
{ name: 'testnet', path: this.stateService.env.ROOT_NETWORK === 'testnet' ? '/' : '/testnet' },
{ name: 'testnet4', path: this.stateService.env.ROOT_NETWORK === 'testnet4' ? '/' : '/testnet4' },
{ name: 'signet', path: this.stateService.env.ROOT_NETWORK === 'signet' ? '/' : '/signet' },
],
},
liquid: {
subnets: [
{ name: 'liquid', path: '' },
{ name: 'liquidtestnet', path: '/testnet' },
],
}
};
networks = Object.keys(this.networkModules);
constructor(
private stateService: StateService,
@ -46,11 +45,11 @@ export class NavigationService {
const networkPaths = {};
let route = root;
// traverse the router state tree until all network paths are set, or we reach the end of the tree
while (!networks.reduce((acc, network) => acc && !!networkPaths[network], true) && route) {
while (!this.networks.reduce((acc, network) => acc && !!networkPaths[network], true) && route) {
// 'networkSpecific' paths may correspond to valid routes on other networks, but aren't directly compatible
// (e.g. we shouldn't link a mainnet transaction page to the same txid on testnet or liquid)
if (route.data?.networkSpecific) {
networks.forEach(network => {
this.networks.forEach(network => {
if (networkPaths[network] == null) {
networkPaths[network] = path;
}
@ -59,7 +58,7 @@ export class NavigationService {
// null or empty networks list is shorthand for "compatible with every network"
if (route.data?.networks?.length) {
// if the list is non-empty, only those networks are compatible
networks.forEach(network => {
this.networks.forEach(network => {
if (!route.data.networks.includes(network)) {
if (networkPaths[network] == null) {
networkPaths[network] = path;
@ -76,7 +75,7 @@ export class NavigationService {
}
const subnetPaths = {};
Object.entries(networkModules).forEach(([key, network]) => {
Object.entries(this.networkModules).forEach(([key, network]) => {
network.subnets.forEach(subnet => {
subnetPaths[subnet.name] = subnet.path + (networkPaths[key] != null ? networkPaths[key] : path);
});

View File

@ -43,6 +43,7 @@ export interface Customization {
}
export interface Env {
MAINNET_ENABLED: boolean;
TESTNET_ENABLED: boolean;
TESTNET4_ENABLED: boolean;
SIGNET_ENABLED: boolean;
@ -52,6 +53,7 @@ export interface Env {
KEEP_BLOCKS_AMOUNT: number;
OFFICIAL_MEMPOOL_SPACE: boolean;
BASE_MODULE: string;
ROOT_NETWORK: string;
NGINX_PROTOCOL?: string;
NGINX_HOSTNAME?: string;
NGINX_PORT?: string;
@ -77,12 +79,14 @@ export interface Env {
}
const defaultEnv: Env = {
'MAINNET_ENABLED': true,
'TESTNET_ENABLED': false,
'TESTNET4_ENABLED': false,
'SIGNET_ENABLED': false,
'LIQUID_ENABLED': false,
'LIQUID_TESTNET_ENABLED': false,
'BASE_MODULE': 'mempool',
'ROOT_NETWORK': '',
'ITEMS_PER_PAGE': 10,
'KEEP_BLOCKS_AMOUNT': 8,
'OFFICIAL_MEMPOOL_SPACE': false,
@ -325,7 +329,12 @@ export class StateService {
// (?:preview\/)? optional "preview" prefix (non-capturing)
// (testnet|signet)/ network string (captured as networkMatches[1])
// ($|\/) network string must end or end with a slash
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet)($|\/)/);
let networkMatches: object = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet)($|\/)/);
if (!networkMatches && this.env.ROOT_NETWORK) {
networkMatches = { 1: this.env.ROOT_NETWORK };
}
switch (networkMatches && networkMatches[1]) {
case 'signet':
if (this.network !== 'signet') {

View File

@ -55,7 +55,7 @@ export class WebsocketService {
.pipe(take(1))
.subscribe((response) => this.handleResponse(response));
} else {
this.network = this.stateService.network;
this.network = this.stateService.network === this.stateService.env.ROOT_NETWORK ? '' : this.stateService.network;
this.websocketSubject = webSocket<WebsocketResponse>(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : ''));
const { response: theInitData } = this.transferState.get<any>(initData, null) || {};
@ -75,7 +75,7 @@ export class WebsocketService {
if (network === this.network) {
return;
}
this.network = network;
this.network = network === this.stateService.env.ROOT_NETWORK ? '' : network;
clearTimeout(this.onlineCheckTimeout);
clearTimeout(this.onlineCheckTimeoutTwo);

View File

@ -12,7 +12,9 @@ export class RelativeUrlPipe implements PipeTransform {
transform(value: string, swapNetwork?: string): string {
let network = swapNetwork || this.stateService.network;
if (network === 'mainnet') network = '';
if (network === 'mainnet' || network === this.stateService.env.ROOT_NETWORK) {
network = '';
}
if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') {
network = 'testnet';
} else if (this.stateService.env.BASE_MODULE !== 'mempool') {