diff --git a/backend/README.md b/backend/README.md index 823998fdc..3d7c23eaa 100644 --- a/backend/README.md +++ b/backend/README.md @@ -110,6 +110,11 @@ Run the Mempool backend: ``` npm run start + +``` +You can also set env var `MEMPOOL_CONFIG_FILE` to specify a custom config file location: +``` +MEMPOOL_CONFIG_FILE=/path/to/mempool-config.json npm run start ``` When it's running, you should see output like this: diff --git a/backend/package.json b/backend/package.json index 082449dac..3cd05e8ab 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,7 +22,10 @@ "main": "index.ts", "scripts": { "tsc": "./node_modules/typescript/bin/tsc -p tsconfig.build.json", - "build": "npm run tsc", + "build": "npm run tsc && npm run create-resources", + "create-resources": "cp ./src/tasks/price-feeds/mtgox-weekly.json ./dist/tasks && node dist/api/fetch-version.js", + "package": "npm run build && rm -rf package && mv dist package && mv node_modules package && npm run package-rm-build-deps", + "package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)", "start": "node --max-old-space-size=2048 dist/index.js", "start-production": "node --max-old-space-size=4096 dist/index.js", "test": "./node_modules/.bin/jest --coverage", diff --git a/backend/src/api/backend-info.ts b/backend/src/api/backend-info.ts index d98675671..57bb5fe13 100644 --- a/backend/src/api/backend-info.ts +++ b/backend/src/api/backend-info.ts @@ -1,60 +1,37 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import logger from '../logger'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; import { IBackendInfo } from '../mempool.interfaces'; -const { spawnSync } = require('child_process'); class BackendInfo { - private gitCommitHash = ''; - private hostname = ''; - private version = ''; + private backendInfo: IBackendInfo; constructor() { - this.setLatestCommitHash(); - this.setVersion(); - this.hostname = os.hostname(); - } - - public getBackendInfo(): IBackendInfo { - return { - hostname: this.hostname, - gitCommit: this.gitCommitHash, - version: this.version, + // This file is created by ./fetch-version.ts during building + const versionFile = path.join(__dirname, 'version.json') + var versionInfo; + if (fs.existsSync(versionFile)) { + versionInfo = JSON.parse(fs.readFileSync(versionFile).toString()); + } else { + // Use dummy values if `versionFile` doesn't exist (e.g., during testing) + versionInfo = { + version: '?', + gitCommit: '?' + }; + } + this.backendInfo = { + hostname: os.hostname(), + version: versionInfo.version, + gitCommit: versionInfo.gitCommit }; } + public getBackendInfo(): IBackendInfo { + return this.backendInfo; + } + public getShortCommitHash() { - return this.gitCommitHash.slice(0, 7); - } - - private setLatestCommitHash(): void { - //TODO: share this logic with `generate-config.js` - if (process.env.DOCKER_COMMIT_HASH) { - this.gitCommitHash = process.env.DOCKER_COMMIT_HASH; - } else { - try { - const gitRevParse = spawnSync('git', ['rev-parse', '--short', 'HEAD']); - if (!gitRevParse.error) { - const output = gitRevParse.stdout.toString('utf-8').replace(/[\n\r\s]+$/, ''); - this.gitCommitHash = output ? output : '?'; - } else if (gitRevParse.error.code === 'ENOENT') { - console.log('git not found, cannot parse git hash'); - this.gitCommitHash = '?'; - } - } catch (e: any) { - console.log('Could not load git commit info: ' + e.message); - this.gitCommitHash = '?'; - } - } - } - - private setVersion(): void { - try { - const packageJson = fs.readFileSync('package.json').toString(); - this.version = JSON.parse(packageJson).version; - } catch (e) { - throw new Error(e instanceof Error ? e.message : 'Error'); - } + return this.backendInfo.gitCommit.slice(0, 7); } } diff --git a/backend/src/api/explorer/channels.api.ts b/backend/src/api/explorer/channels.api.ts index db6e92920..a52b0f28f 100644 --- a/backend/src/api/explorer/channels.api.ts +++ b/backend/src/api/explorer/channels.api.ts @@ -375,6 +375,7 @@ class ChannelsApi { 'transaction_vout': channel.transaction_vout, 'closing_transaction_id': channel.closing_transaction_id, 'closing_reason': channel.closing_reason, + 'closing_date': channel.closing_date, 'updated_at': channel.updated_at, 'created': channel.created, 'status': channel.status, diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index 128405ffd..9d82dc83d 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -5,6 +5,49 @@ import { ILightningApi } from '../lightning/lightning-api.interface'; import { ITopNodesPerCapacity, ITopNodesPerChannels } from '../../mempool.interfaces'; class NodesApi { + public async $getWorldNodes(): Promise { + try { + let query = ` + SELECT nodes.public_key as publicKey, IF(nodes.alias = '', SUBSTRING(nodes.public_key, 1, 20), alias) as alias, + CAST(COALESCE(nodes.capacity, 0) as INT) as capacity, + CAST(COALESCE(nodes.channels, 0) as INT) as channels, + nodes.longitude, nodes.latitude, + geo_names_country.names as country, geo_names_iso.names as isoCode + FROM nodes + LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' + LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' + WHERE status = 1 AND nodes.as_number IS NOT NULL + ORDER BY capacity + `; + + const [nodes]: any[] = await DB.query(query); + + for (let i = 0; i < nodes.length; ++i) { + nodes[i].country = JSON.parse(nodes[i].country); + } + + query = ` + SELECT MAX(nodes.capacity) as maxLiquidity, MAX(nodes.channels) as maxChannels + FROM nodes + WHERE status = 1 AND nodes.as_number IS NOT NULL + `; + + const [maximums]: any[] = await DB.query(query); + + return { + maxLiquidity: maximums[0].maxLiquidity, + maxChannels: maximums[0].maxChannels, + nodes: nodes.map(node => [ + node.longitude, node.latitude, + node.publicKey, node.alias, node.capacity, node.channels, + node.country, node.isoCode + ]) + }; + } catch (e) { + logger.err(`Can't get world nodes list. Reason: ${e instanceof Error ? e.message : e}`); + } + } + public async $getNode(public_key: string): Promise { try { // General info @@ -133,10 +176,13 @@ class NodesApi { CAST(COALESCE(nodes.capacity, 0) as INT) as capacity, CAST(COALESCE(nodes.channels, 0) as INT) as channels, UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt, - geo_names_city.names as city, geo_names_country.names as country + geo_names_city.names as city, geo_names_country.names as country, + geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision FROM nodes LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' + LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' + LEFT JOIN geo_names geo_names_subdivision on geo_names_subdivision.id = nodes.subdivision_id AND geo_names_subdivision.type = 'division' ORDER BY capacity DESC LIMIT 100 `; @@ -175,10 +221,13 @@ class NodesApi { CAST(COALESCE(nodes.channels, 0) as INT) as channels, CAST(COALESCE(nodes.capacity, 0) as INT) as capacity, UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt, - geo_names_city.names as city, geo_names_country.names as country + geo_names_city.names as city, geo_names_country.names as country, + geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision FROM nodes LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' + LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' + LEFT JOIN geo_names geo_names_subdivision on geo_names_subdivision.id = nodes.subdivision_id AND geo_names_subdivision.type = 'division' ORDER BY channels DESC LIMIT 100 `; @@ -221,11 +270,14 @@ class NodesApi { CAST(COALESCE(node_stats.channels, 0) as INT) as channels, CAST(COALESCE(node_stats.capacity, 0) as INT) as capacity, UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt, - geo_names_city.names as city, geo_names_country.names as country + geo_names_city.names as city, geo_names_country.names as country, + geo_names_iso.names as iso_code, geo_names_subdivision.names as subdivision FROM node_stats RIGHT JOIN nodes ON nodes.public_key = node_stats.public_key LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country' LEFT JOIN geo_names geo_names_city ON geo_names_city.id = nodes.city_id AND geo_names_city.type = 'city' + LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code' + LEFT JOIN geo_names geo_names_subdivision on geo_names_subdivision.id = nodes.subdivision_id AND geo_names_subdivision.type = 'division' WHERE added = FROM_UNIXTIME(${latestDate}) ORDER BY first_seen LIMIT 100 diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 7a5ff880a..cf3f75208 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -9,6 +9,7 @@ class NodesRoutes { public initRoutes(app: Application) { app + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/world', this.$getWorldNodes) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/country/:country', this.$getNodesPerCountry) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/search/:search', this.$searchNode) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp-ranking', this.$getISPRanking) @@ -115,7 +116,6 @@ class NodesRoutes { private async $getISPRanking(req: Request, res: Response): Promise { try { const nodesPerAs = await nodesApi.$getNodesISPRanking(); - res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString()); @@ -125,6 +125,18 @@ class NodesRoutes { } } + private async $getWorldNodes(req: Request, res: Response) { + try { + const worldNodes = await nodesApi.$getWorldNodes(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString()); + res.json(worldNodes); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getNodesPerCountry(req: Request, res: Response) { try { const [country]: any[] = await DB.query( diff --git a/backend/src/api/fetch-version.ts b/backend/src/api/fetch-version.ts new file mode 100644 index 000000000..cb0813c35 --- /dev/null +++ b/backend/src/api/fetch-version.ts @@ -0,0 +1,37 @@ +import fs from 'fs'; +import path from "path"; +const { spawnSync } = require('child_process'); + +function getVersion(): string { + const packageJson = fs.readFileSync('package.json').toString(); + return JSON.parse(packageJson).version; +} + +function getGitCommit(): string { + if (process.env.MEMPOOL_COMMIT_HASH) { + return process.env.MEMPOOL_COMMIT_HASH; + } else { + const gitRevParse = spawnSync('git', ['rev-parse', '--short', 'HEAD']); + if (!gitRevParse.error) { + const output = gitRevParse.stdout.toString('utf-8').replace(/[\n\r\s]+$/, ''); + if (output) { + return output; + } else { + console.log('Could not fetch git commit: No repo available'); + } + } else if (gitRevParse.error.code === 'ENOENT') { + console.log('Could not fetch git commit: Command `git` is unavailable'); + } + } + return '?'; +} + +const versionInfo = { + version: getVersion(), + gitCommit: getGitCommit() +} + +fs.writeFileSync( + path.join(__dirname, 'version.json'), + JSON.stringify(versionInfo, null, 2) + "\n" +); diff --git a/backend/src/config.ts b/backend/src/config.ts index 8c91e104b..43ba16cb3 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,4 +1,6 @@ -const configFile = require('../mempool-config.json'); +const configFromFile = require( + process.env.MEMPOOL_CONFIG_FILE ? process.env.MEMPOOL_CONFIG_FILE : '../mempool-config.json' +); interface IConfig { MEMPOOL: { @@ -249,7 +251,7 @@ class Config implements IConfig { MAXMIND: IConfig['MAXMIND']; constructor() { - const configs = this.merge(configFile, defaults); + const configs = this.merge(configFromFile, defaults); this.MEMPOOL = configs.MEMPOOL; this.ESPLORA = configs.ESPLORA; this.ELECTRUM = configs.ELECTRUM; diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts index b9b7df92c..8d8f1759f 100644 --- a/backend/src/tasks/lightning/network-sync.service.ts +++ b/backend/src/tasks/lightning/network-sync.service.ts @@ -12,9 +12,11 @@ import { ResultSetHeader } from 'mysql2'; import fundingTxFetcher from './sync-tasks/funding-tx-fetcher'; import NodesSocketsRepository from '../../repositories/NodesSocketsRepository'; import { Common } from '../../api/common'; +import blocks from '../../api/blocks'; class NetworkSyncService { loggerTimer = 0; + closedChannelsScanBlock = 0; constructor() {} @@ -240,10 +242,22 @@ class NetworkSyncService { } private async $scanForClosedChannels(): Promise { + if (this.closedChannelsScanBlock === blocks.getCurrentBlockHeight()) { + logger.debug(`We've already scan closed channels for this block, skipping.`); + return; + } + let progress = 0; try { - logger.info(`Starting closed channels scan`); + let log = `Starting closed channels scan`; + if (this.closedChannelsScanBlock > 0) { + log += `. Last scan was at block ${this.closedChannelsScanBlock}`; + } else { + log += ` for the first time`; + } + logger.info(log); + const channels = await channelsApi.$getChannelsByStatus([0, 1]); for (const channel of channels) { const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout); @@ -263,7 +277,9 @@ class NetworkSyncService { this.loggerTimer = new Date().getTime() / 1000; } } - logger.info(`Closed channels scan complete.`); + + this.closedChannelsScanBlock = blocks.getCurrentBlockHeight(); + logger.info(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`); } catch (e) { logger.err('$scanForClosedChannels() error: ' + (e instanceof Error ? e.message : e)); } diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 81066efb2..891e2bce3 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import path from "path"; import { Common } from '../api/common'; import config from '../config'; import logger from '../logger'; @@ -159,7 +160,7 @@ class PriceUpdater { const existingPriceTimes = await PricesRepository.$getPricesTimes(); // Insert MtGox weekly prices - const pricesJson: any[] = JSON.parse(fs.readFileSync('./src/tasks/price-feeds/mtgox-weekly.json').toString()); + const pricesJson: any[] = JSON.parse(fs.readFileSync(path.join(__dirname, 'mtgox-weekly.json')).toString()); const prices = this.getEmptyPricesObj(); let insertedCount: number = 0; for (const price of pricesJson) { diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 8d13bc7f4..9be457bb2 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,7 +1,7 @@ FROM node:16.16.0-buster-slim AS builder ARG commitHash -ENV DOCKER_COMMIT_HASH=${commitHash} +ENV MEMPOOL_COMMIT_HASH=${commitHash} WORKDIR /build COPY . . @@ -9,18 +9,15 @@ COPY . . RUN apt-get update RUN apt-get install -y build-essential python3 pkg-config RUN npm install --omit=dev --omit=optional -RUN npm run build +RUN npm run package FROM node:16.16.0-buster-slim WORKDIR /backend -COPY --from=builder /build/ . - -RUN chmod +x /backend/start.sh -RUN chmod +x /backend/wait-for-it.sh - -RUN chown -R 1000:1000 /backend && chmod -R 755 /backend +RUN chown 1000:1000 ./ +COPY --from=builder --chown=1000:1000 /build/package ./package/ +COPY --from=builder --chown=1000:1000 /build/mempool-config.json /build/start.sh /build/wait-for-it.sh ./ USER 1000 diff --git a/docker/backend/start.sh b/docker/backend/start.sh old mode 100644 new mode 100755 index 8e6a34013..ade739f1f --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -205,4 +205,4 @@ sed -i "s!__LND_REST_API_URL__!${__LND_REST_API_URL__}!g" mempool-config.json # CLN sed -i "s!__CLN_SOCKET__!${__CLN_SOCKET__}!g" mempool-config.json -node /backend/dist/index.js +node /backend/package/index.js diff --git a/docker/backend/wait-for-it.sh b/docker/backend/wait-for-it.sh old mode 100644 new mode 100755 diff --git a/docker/init.sh b/docker/init.sh index 49b53d646..4eb06f0c1 100755 --- a/docker/init.sh +++ b/docker/init.sh @@ -1,10 +1,7 @@ #!/bin/sh #backend -gitMaster="\.\.\/\.git\/refs\/heads\/master" -git ls-remote https://github.com/mempool/mempool.git "$1^{}" | awk '{ print $1}' > ./backend/master cp ./docker/backend/* ./backend/ -sed -i "s/${gitMaster}/master/g" ./backend/src/api/backend-info.ts #frontend localhostIP="127.0.0.1" diff --git a/frontend/angular.json b/frontend/angular.json index 4eb697071..1ed29cad9 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -170,6 +170,10 @@ }, "configurations": { "production": { + "assets": [ + "src/favicon.ico", + "src/robots.txt" + ], "fileReplacements": [ { "replace": "src/environments/environment.ts", diff --git a/frontend/package.json b/frontend/package.json index d2f7f2f6c..b5055a5de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging", "start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", "build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets && npm run build-mempool.js", - "sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources", + "sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js", "sync-assets-dev": "node sync-assets.js dev", "generate-config": "node generate-config.js", "build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js", diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 36d765a17..5cfb2c905 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -187,8 +187,8 @@ -
-

Self-Hosted Integrations

+
+

Community Integrations

-
- -
-

Wallet Integrations

-
+ + + BTCPay + Bisq + + + BlueWallet + + + + Muun + - + Electrum @@ -244,18 +250,14 @@ Phoenix + + + LNBits + Mercury - - - Muun - - - - BlueWallet - Blixt diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index 2cd755bde..2cc5d5102 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -43,7 +43,6 @@ .alliances, .enterprise-sponsor, .community-integrations-sponsor, - .selfhosted-integrations-sponsor, .maintainers { margin-top: 68px; margin-bottom: 68px; @@ -117,7 +116,6 @@ .community-sponsor, .project-translators, .community-integrations-sponsor, - .selfhosted-integrations-sponsor, .maintainers { .wrapper { display: inline-block; @@ -193,6 +191,6 @@ } .community-integrations-sponsor { - max-width: 830px; + max-width: 970px; margin: auto; } diff --git a/frontend/src/app/components/asset-circulation/asset-circulation.component.ts b/frontend/src/app/components/asset-circulation/asset-circulation.component.ts index 4d65417e6..d64607fe1 100644 --- a/frontend/src/app/components/asset-circulation/asset-circulation.component.ts +++ b/frontend/src/app/components/asset-circulation/asset-circulation.component.ts @@ -4,7 +4,6 @@ import { map } from 'rxjs/operators'; import { moveDec } from 'src/app/bitcoin.utils'; import { AssetsService } from 'src/app/services/assets.service'; import { ElectrsApiService } from 'src/app/services/electrs-api.service'; -import { formatNumber } from '@angular/common'; import { environment } from 'src/environments/environment'; @Component({ diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 5c071a357..d9670936d 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -161,6 +161,9 @@ export interface ITopNodesPerChannels { updatedAt?: number, city?: any, country?: any, + subdivision?: any, + iso_code?: string, + geolocation?: any; } export interface ITopNodesPerCapacity { @@ -172,6 +175,9 @@ export interface ITopNodesPerCapacity { updatedAt?: number, city?: any, country?: any, + subdivision?: any, + iso_code?: string, + geolocation?: any; } export interface INodesRanking { @@ -188,6 +194,9 @@ export interface IOldestNodes { updatedAt?: number, city?: any, country?: any, + subdivision?: any, + iso_code?: string, + geolocation?: any; } export interface IChannel { diff --git a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html index 30d1a3585..8b486eff5 100644 --- a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html +++ b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html @@ -19,31 +19,31 @@ Fee rate - {{ channel.fee_rate }} ppm ({{ channel.fee_rate / 10000 | number }}%) + {{ channel.fee_rate ?? '-' }} ppm ({{ channel.fee_rate / 10000 | number }}%) Base fee - + Min HTLC - + Max HTLC - + Timelock delta - + diff --git a/frontend/src/app/lightning/channel/channel.component.html b/frontend/src/app/lightning/channel/channel.component.html index 2824b8dba..9a0c424fb 100644 --- a/frontend/src/app/lightning/channel/channel.component.html +++ b/frontend/src/app/lightning/channel/channel.component.html @@ -25,13 +25,17 @@ - + - - + + + + + +
CreatedCreated
Last update
Last update
Closing date
diff --git a/frontend/src/app/lightning/channel/channel.component.ts b/frontend/src/app/lightning/channel/channel.component.ts index b1e39dda7..c6d06683a 100644 --- a/frontend/src/app/lightning/channel/channel.component.ts +++ b/frontend/src/app/lightning/channel/channel.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { Observable, of, zip } from 'rxjs'; -import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; +import { catchError, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { IChannel } from 'src/app/interfaces/node-api.interface'; import { ElectrsApiService } from 'src/app/services/electrs-api.service'; import { SeoService } from 'src/app/services/seo.service'; @@ -31,9 +31,11 @@ export class ChannelComponent implements OnInit { .pipe( switchMap((params: ParamMap) => { this.error = null; - this.seoService.setTitle(`Channel: ${params.get('short_id')}`); return this.lightningApiService.getChannel$(params.get('short_id')) .pipe( + tap((value) => { + this.seoService.setTitle(`Channel: ${value.short_id}`); + }), catchError((err) => { this.error = err; return of(null); diff --git a/frontend/src/app/lightning/channels-list/channels-list.component.html b/frontend/src/app/lightning/channels-list/channels-list.component.html index af87cefa4..a51e03ef8 100644 --- a/frontend/src/app/lightning/channels-list/channels-list.component.html +++ b/frontend/src/app/lightning/channels-list/channels-list.component.html @@ -35,7 +35,8 @@ Node Alias   Status - Fee Rate + Fee Rate + Closing date Capacity Channel ID @@ -71,9 +72,12 @@ - + {{ channel.fee_rate }} ppm ({{ channel.fee_rate / 10000 | number }}%) + + + diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.html b/frontend/src/app/lightning/nodes-map/nodes-map.component.html index b762b2d24..75f8aeb08 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.html +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.html @@ -2,10 +2,7 @@
- Lightning nodes world heat map - + Lightning nodes world map
(Tor nodes excluded)
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts index d2fcb31e0..6c809916e 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -1,14 +1,15 @@ -import { ChangeDetectionStrategy, Component, NgZone, OnDestroy, OnInit } from '@angular/core'; -import { mempoolFeeColors } from 'src/app/app.constants'; +import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core'; import { SeoService } from 'src/app/services/seo.service'; import { ApiService } from 'src/app/services/api.service'; -import { combineLatest, Observable, tap } from 'rxjs'; +import { Observable, tap, zip } from 'rxjs'; import { AssetsService } from 'src/app/services/assets.service'; import { EChartsOption, registerMap } from 'echarts'; -import { download } from 'src/app/shared/graphs.utils'; +import { lerpColor } from 'src/app/shared/graphs.utils'; import { Router } from '@angular/router'; import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; import { StateService } from 'src/app/services/state.service'; +import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe'; +import { getFlagEmoji } from 'src/app/shared/common.utils'; @Component({ selector: 'app-nodes-map', @@ -16,7 +17,7 @@ import { StateService } from 'src/app/services/state.service'; styleUrls: ['./nodes-map.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class NodesMap implements OnInit, OnDestroy { +export class NodesMap implements OnInit { observable$: Observable; chartInstance = undefined; @@ -26,44 +27,52 @@ export class NodesMap implements OnInit, OnDestroy { }; constructor( + @Inject(LOCALE_ID) public locale: string, private seoService: SeoService, private apiService: ApiService, private stateService: StateService, private assetsService: AssetsService, private router: Router, private zone: NgZone, + private amountShortenerPipe: AmountShortenerPipe ) { } - ngOnDestroy(): void {} - ngOnInit(): void { this.seoService.setTitle($localize`Lightning nodes world map`); - this.observable$ = combineLatest([ + this.observable$ = zip( this.assetsService.getWorldMapJson$, - this.apiService.getNodesPerCountry() - ]).pipe(tap((data) => { + this.apiService.getWorldNodes$() + ).pipe(tap((data) => { registerMap('world', data[0]); - const countries = []; - let max = 0; - for (const country of data[1]) { - countries.push({ - name: country.name.en, - value: country.count, - iso: country.iso.toLowerCase(), - }); - max = Math.max(max, country.count); + const nodes: any[] = []; + console.log(data[1].nodes[0]); + for (const node of data[1].nodes) { + // We add a bit of noise so nodes at the same location are not all + // on top of each other + const random = Math.random() * 2 * Math.PI; + const random2 = Math.random() * 0.01; + nodes.push([ + node[0] + random2 * Math.cos(random), + node[1] + random2 * Math.sin(random), + node[4], // Liquidity + node[3], // Alias + node[2], // Public key + node[5], // Channels + node[6].en, // Country + node[7], // ISO Code + ]); } - this.prepareChartOptions(countries, max); + this.prepareChartOptions(nodes, data[1].maxLiquidity); })); } - prepareChartOptions(countries, max) { + prepareChartOptions(nodes, maxLiquidity) { let title: object; - if (countries.length === 0) { + if (nodes.length === 0) { title = { textStyle: { color: 'grey', @@ -76,53 +85,80 @@ export class NodesMap implements OnInit, OnDestroy { } this.chartOptions = { - title: countries.length === 0 ? title : undefined, - tooltip: { - backgroundColor: 'rgba(17, 19, 31, 1)', - borderRadius: 4, - shadowColor: 'rgba(0, 0, 0, 0.5)', - textStyle: { - color: '#b1b1b1', + silent: false, + title: title ?? undefined, + tooltip: {}, + geo: { + animation: false, + silent: true, + center: [0, 5], + zoom: 1.3, + tooltip: { + show: false }, - borderColor: '#000', - formatter: function(country) { - if (country.data === undefined) { - return `${country.name}
0 nodes

`; - } else { - return `${country.data.name}
${country.data.value} nodes

`; - } + map: 'world', + roam: true, + itemStyle: { + borderColor: 'black', + color: '#272b3f' + }, + scaleLimit: { + min: 1.3, + max: 100000, + }, + emphasis: { + disabled: true, } }, - visualMap: { - left: 'right', - show: true, - min: 1, - max: max, - text: ['High', 'Low'], - calculable: true, - textStyle: { - color: 'white', - }, - inRange: { - color: mempoolFeeColors.map(color => `#${color}`), - }, - }, - series: { - type: 'map', - map: 'world', - emphasis: { - label: { - show: false, + series: [ + { + large: false, + type: 'scatter', + data: nodes, + coordinateSystem: 'geo', + geoIndex: 0, + progressive: 500, + symbolSize: function (params) { + return 10 * Math.pow(params[2] / maxLiquidity, 0.2) + 3; + }, + tooltip: { + trigger: 'item', + show: true, + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: '#b1b1b1', + align: 'left', + }, + borderColor: '#000', + formatter: (value) => { + const data = value.data; + const alias = data[3].length > 0 ? data[3] : data[4].slice(0, 20); + const liquidity = data[2] >= 100000000 ? + `${this.amountShortenerPipe.transform(data[2] / 100000000)} BTC` : + `${this.amountShortenerPipe.transform(data[2], 2)} sats`; + + return ` + ${alias}
+ ${liquidity}
+ ${data[5]} channels
+ ${getFlagEmoji(data[7])} ${data[6]} + `; + } }, itemStyle: { - areaColor: '#FDD835', - } + color: function (params) { + return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[2] / maxLiquidity, 0.2))}`; + }, + opacity: 1, + borderColor: 'black', + borderWidth: 0, + }, + blendMode: 'lighter', + zlevel: 2, }, - data: countries, - itemStyle: { - areaColor: '#5A6A6D' - }, - } + ] }; } @@ -134,30 +170,16 @@ export class NodesMap implements OnInit, OnDestroy { this.chartInstance = ec; this.chartInstance.on('click', (e) => { - if (e.data && e.data.value > 0) { + if (e.data) { this.zone.run(() => { - const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/nodes/country/${e.data.iso}`); + const url = new RelativeUrlPipe(this.stateService).transform(`/lightning/node/${e.data[4]}`); this.router.navigate([url]); }); } }); - } - onSaveChart() { - // @ts-ignore - const prevBottom = this.chartOptions.grid.bottom; - const now = new Date(); - // @ts-ignore - this.chartOptions.grid.bottom = 30; - this.chartOptions.backgroundColor = '#11131f'; - this.chartInstance.setOption(this.chartOptions); - download(this.chartInstance.getDataURL({ - pixelRatio: 2, - excludeComponents: ['dataZoom'], - }), `lightning-nodes-heatmap-clearnet-${Math.round(now.getTime() / 1000)}.svg`); - // @ts-ignore - this.chartOptions.grid.bottom = prevBottom; - this.chartOptions.backgroundColor = 'none'; - this.chartInstance.setOption(this.chartOptions); + this.chartInstance.on('georoam', (e) => { + this.chartInstance.resize(); + }); } } diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index 09b00e032..9f1b3fe88 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -45,7 +45,7 @@ export class NodesPerCountryChartComponent implements OnInit { ngOnInit(): void { this.seoService.setTitle($localize`Lightning nodes per country`); - this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry() + this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$() .pipe( map(data => { for (let i = 0; i < data.length; ++i) { diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html index 16f4265a2..190cf6219 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html @@ -6,15 +6,17 @@
+ - + - + + + + + + + + + + + + + + + +
Alias First seen Last update Capacity ChannelsCityLocation
{{ node.alias }} @@ -39,6 +41,32 @@
+ + + + + + + + + + + +
diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts index 644e6741a..e0bf5eb66 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.ts @@ -16,11 +16,17 @@ export class NodesPerCountry implements OnInit { nodes$: Observable; country: {name: string, flag: string}; + skeletonLines: number[] = []; + constructor( private apiService: ApiService, private seoService: SeoService, private route: ActivatedRoute, - ) { } + ) { + for (let i = 0; i < 20; ++i) { + this.skeletonLines.push(i); + } + } ngOnInit(): void { this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country) diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts index 4f7656526..635e5bc74 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts @@ -47,7 +47,9 @@ export class NodesPerISPChartComponent implements OnInit { } ngOnInit(): void { - this.seoService.setTitle($localize`Lightning nodes per ISP`); + if (!this.widget) { + this.seoService.setTitle($localize`Lightning nodes per ISP`); + } this.nodesPerAsObservable$ = combineLatest([ this.sortBySubject.pipe(startWith(true)), @@ -105,7 +107,7 @@ export class NodesPerISPChartComponent implements OnInit { } generateChartSerieData(ispRanking): PieSeriesOption[] { - let shareThreshold = 0.5; + let shareThreshold = 0.4; if (this.widget && isMobile() || isMobile()) { shareThreshold = 1; } else if (this.widget) { diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html index a8931d843..4ea3c2d11 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html @@ -3,15 +3,17 @@
+ - + - + + + + + + + + + + + + + + + +
Alias First seen Last update Capacity ChannelsCityLocation
{{ node.alias }} @@ -36,6 +38,32 @@
+ + + + + + + + + + + +
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts index cc57056fc..f7edf783a 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts @@ -15,11 +15,17 @@ export class NodesPerISP implements OnInit { nodes$: Observable; isp: {name: string, id: number}; + skeletonLines: number[] = []; + constructor( private apiService: ApiService, private seoService: SeoService, private route: ActivatedRoute, - ) { } + ) { + for (let i = 0; i < 20; ++i) { + this.skeletonLines.push(i); + } + } ngOnInit(): void { this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp) diff --git a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html index 8df85c7dd..e82ff0ac8 100644 --- a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html +++ b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html @@ -35,7 +35,7 @@ - {{ node?.city?.en ?? '-' }} + diff --git a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts index 23f248b0e..d1cec0780 100644 --- a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts @@ -1,5 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; +import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; +import { SeoService } from 'src/app/services/seo.service'; import { IOldestNodes } from '../../../interfaces/node-api.interface'; import { LightningApiService } from '../../lightning-api.service'; @@ -15,19 +17,38 @@ export class OldestNodes implements OnInit { oldestNodes$: Observable; skeletonRows: number[] = []; - constructor(private apiService: LightningApiService) {} + constructor( + private apiService: LightningApiService, + private seoService: SeoService + ) {} ngOnInit(): void { + if (!this.widget) { + this.seoService.setTitle($localize`Oldest lightning nodes`); + } + for (let i = 1; i <= (this.widget ? 10 : 100); ++i) { this.skeletonRows.push(i); } if (this.widget === false) { - this.oldestNodes$ = this.apiService.getOldestNodes$(); + this.oldestNodes$ = this.apiService.getOldestNodes$().pipe( + map((ranking) => { + for (const i in ranking) { + ranking[i].geolocation = { + country: ranking[i].country?.en, + city: ranking[i].city?.en, + subdivision: ranking[i].subdivision?.en, + iso: ranking[i].iso_code, + }; + } + return ranking; + }) + ); } else { this.oldestNodes$ = this.apiService.getOldestNodes$().pipe( map((nodes: IOldestNodes[]) => { - return nodes.slice(0, 10); + return nodes.slice(0, 7); }) ); } diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html index 42eb6543e..80e93f7ac 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -35,7 +35,7 @@ - {{ node?.city?.en ?? '-' }} + diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts index c79c396ee..ad396ee31 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -1,7 +1,9 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { INodesRanking, ITopNodesPerCapacity } from 'src/app/interfaces/node-api.interface'; +import { SeoService } from 'src/app/services/seo.service'; import { isMobile } from 'src/app/shared/common.utils'; +import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; import { LightningApiService } from '../../lightning-api.service'; @Component({ @@ -17,15 +19,34 @@ export class TopNodesPerCapacity implements OnInit { topNodesPerCapacity$: Observable; skeletonRows: number[] = []; - constructor(private apiService: LightningApiService) {} + constructor( + private apiService: LightningApiService, + private seoService: SeoService + ) {} ngOnInit(): void { + if (!this.widget) { + this.seoService.setTitle($localize`Liquidity Ranking`); + } + for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) { this.skeletonRows.push(i); } if (this.widget === false) { - this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$(); + this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$().pipe( + map((ranking) => { + for (const i in ranking) { + ranking[i].geolocation = { + country: ranking[i].country?.en, + city: ranking[i].city?.en, + subdivision: ranking[i].subdivision?.en, + iso: ranking[i].iso_code, + }; + } + return ranking; + }) + ); } else { this.topNodesPerCapacity$ = this.nodes$.pipe( map((ranking) => { diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html index df9d79bac..ef2a05659 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html @@ -35,9 +35,9 @@ - {{ node?.city?.en ?? '-' }} + - + diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts index 44e7aa518..f8a2ae52b 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts @@ -1,7 +1,9 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { INodesRanking, ITopNodesPerChannels } from 'src/app/interfaces/node-api.interface'; +import { SeoService } from 'src/app/services/seo.service'; import { isMobile } from 'src/app/shared/common.utils'; +import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; import { LightningApiService } from '../../lightning-api.service'; @Component({ @@ -17,15 +19,34 @@ export class TopNodesPerChannels implements OnInit { topNodesPerChannels$: Observable; skeletonRows: number[] = []; - constructor(private apiService: LightningApiService) {} + constructor( + private apiService: LightningApiService, + private seoService: SeoService + ) {} ngOnInit(): void { + if (!this.widget) { + this.seoService.setTitle($localize`Connectivity Ranking`); + } + for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) { this.skeletonRows.push(i); } if (this.widget === false) { - this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$(); + this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$().pipe( + map((ranking) => { + for (const i in ranking) { + ranking[i].geolocation = { + country: ranking[i].country?.en, + city: ranking[i].city?.en, + subdivision: ranking[i].subdivision?.en, + iso: ranking[i].iso_code, + }; + } + return ranking; + }) + ); } else { this.topNodesPerChannels$ = this.nodes$.pipe( map((ranking) => { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 5f036c575..8c0f5ecd0 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -267,10 +267,14 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp/' + isp); } - getNodesPerCountry(): Observable { + getNodesPerCountry$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries'); } + getWorldNodes$(): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/world'); + } + getChannelsGeo$(publicKey?: string, style?: 'graph' | 'nodepage' | 'widget' | 'channelpage'): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels-geo' + diff --git a/frontend/src/app/shared/components/sats/sats.component.html b/frontend/src/app/shared/components/sats/sats.component.html index a648cdfcb..1b4ab9143 100644 --- a/frontend/src/app/shared/components/sats/sats.component.html +++ b/frontend/src/app/shared/components/sats/sats.component.html @@ -1,5 +1,8 @@ -‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number : digitsInfo }} -L- -tL- -t- -s-sats +{{ valueOverride }} +‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number : digitsInfo }} + + L- + tL- + t- + s-sats + \ No newline at end of file diff --git a/frontend/src/app/shared/components/sats/sats.component.ts b/frontend/src/app/shared/components/sats/sats.component.ts index d9801d249..39be66ecd 100644 --- a/frontend/src/app/shared/components/sats/sats.component.ts +++ b/frontend/src/app/shared/components/sats/sats.component.ts @@ -11,6 +11,7 @@ export class SatsComponent implements OnInit { @Input() satoshis: number; @Input() digitsInfo = '1.0-0'; @Input() addPlus = false; + @Input() valueOverride: string | undefined = undefined; network = ''; stateSubscription: Subscription; diff --git a/frontend/src/app/shared/components/timestamp/timestamp.component.html b/frontend/src/app/shared/components/timestamp/timestamp.component.html index 769b292d4..69abce53f 100644 --- a/frontend/src/app/shared/components/timestamp/timestamp.component.html +++ b/frontend/src/app/shared/components/timestamp/timestamp.component.html @@ -1,4 +1,7 @@ -‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }} -
- () -
+- + + ‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }} +
+ () +
+
diff --git a/frontend/src/app/shared/components/timestamp/timestamp.component.ts b/frontend/src/app/shared/components/timestamp/timestamp.component.ts index dc577a185..120a5dfe4 100644 --- a/frontend/src/app/shared/components/timestamp/timestamp.component.ts +++ b/frontend/src/app/shared/components/timestamp/timestamp.component.ts @@ -11,15 +11,13 @@ export class TimestampComponent implements OnChanges { @Input() dateString: string; @Input() customFormat: string; - seconds: number; - - constructor() { } + seconds: number | undefined = undefined; ngOnChanges(): void { if (this.unixTime) { this.seconds = this.unixTime; } else if (this.dateString) { - this.seconds = new Date(this.dateString).getTime() / 1000 + this.seconds = new Date(this.dateString).getTime() / 1000; } } diff --git a/frontend/src/resources/profile/btcpayserver.svg b/frontend/src/resources/profile/btcpayserver.svg new file mode 100644 index 000000000..1c18a4cfe --- /dev/null +++ b/frontend/src/resources/profile/btcpayserver.svg @@ -0,0 +1 @@ +BTCPayServer diff --git a/frontend/src/resources/profile/electrum.jpg b/frontend/src/resources/profile/electrum.jpg deleted file mode 100644 index c1c063d75..000000000 Binary files a/frontend/src/resources/profile/electrum.jpg and /dev/null differ diff --git a/frontend/src/resources/profile/electrum.png b/frontend/src/resources/profile/electrum.png new file mode 100644 index 000000000..ca24d5f45 Binary files /dev/null and b/frontend/src/resources/profile/electrum.png differ diff --git a/frontend/src/resources/profile/lnbits.svg b/frontend/src/resources/profile/lnbits.svg new file mode 100644 index 000000000..0969df927 --- /dev/null +++ b/frontend/src/resources/profile/lnbits.svg @@ -0,0 +1 @@ + diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index a6b59bdb1..8937e2abb 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -4,7 +4,7 @@ var fs = require('fs'); const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; let configContent = {}; -var PATH = 'dist/mempool/browser/en-US/resources/'; +var PATH = 'dist/mempool/browser/resources/'; if (process.argv[2] && process.argv[2] === 'dev') { PATH = 'src/resources/'; } diff --git a/nginx-mempool.conf b/nginx-mempool.conf index 58d45f3cc..a6f701478 100644 --- a/nginx-mempool.conf +++ b/nginx-mempool.conf @@ -18,7 +18,7 @@ expires 10m; } location /resources { - try_files /$lang/$uri /$lang/$uri/ $uri $uri/ /en-US/$uri @index-redirect; + try_files $uri @index-redirect; expires 1h; } location @index-redirect { @@ -27,10 +27,6 @@ # location block using regex are matched in order - # used to rewrite resources from // to /en-US/ - location ~ ^/(ar|bg|bs|ca|cs|da|de|et|el|es|eo|eu|fa|fr|gl|ko|hr|id|it|he|ka|lv|lt|hu|mk|ms|nl|ja|nb|nn|pl|pt|pt-BR|ro|ru|sk|sl|sr|sh|fi|sv|th|tr|uk|vi|zh|hi)/resources/ { - rewrite ^/[a-zA-Z-]*/resources/(.*) /en-US/resources/$1; - } # used for cookie override location ~ ^/(ar|bg|bs|ca|cs|da|de|et|el|es|eo|eu|fa|fr|gl|ko|hr|id|it|he|ka|lv|lt|hu|mk|ms|nl|ja|nb|nn|pl|pt|pt-BR|ro|ru|sk|sl|sr|sh|fi|sv|th|tr|uk|vi|zh|hi)/ { try_files $uri $uri/ /$1/index.html =404; diff --git a/production/install b/production/install index 4eac1817b..40f89389f 100755 --- a/production/install +++ b/production/install @@ -1009,7 +1009,6 @@ osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_ osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-build-all upgrade osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-kill-all stop osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-start-all start -osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-restart-all restart case $OS in diff --git a/production/mempool-restart-all b/production/mempool-restart-all deleted file mode 100755 index 13e551066..000000000 --- a/production/mempool-restart-all +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env zsh -HOSTNAME=$(hostname) - -echo restarting mempool backends | wall -echo "${HOSTNAME} restarted mempool backends" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.ops -ps uaxw|grep 'dist/index'|grep -v grep|grep -v services|awk '{print $2}'|xargs -n 1 kill - -exit 0 diff --git a/production/mempool.crontab b/production/mempool.crontab index cc1bcd878..0e7b6af6b 100644 --- a/production/mempool.crontab +++ b/production/mempool.crontab @@ -5,5 +5,5 @@ 37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 & # hourly liquid asset update -6 * * * * cd $HOME/liquid/frontend && npm run sync-assets && rsync -av $HOME/liquid/frontend/dist/mempool/browser/en-US/resources/assets* $HOME/public_html/liquid/en-US/resources/ >/dev/null 2>&1 +6 * * * * cd $HOME/liquid/frontend && npm run sync-assets && rsync -av $HOME/liquid/frontend/dist/mempool/browser/resources/assets* $HOME/public_html/liquid/resources/ >/dev/null 2>&1 diff --git a/production/nginx/server-common.conf b/production/nginx/server-common.conf index 26e81f7fa..dedd36411 100644 --- a/production/nginx/server-common.conf +++ b/production/nginx/server-common.conf @@ -58,12 +58,6 @@ location = / { expires 5m; } -# used to rewrite resources from // to /en-US/ -# cache /resources/** for 1 week since they don't change often -location ~ ^/[a-z][a-z]/resources/(.*) { - try_files $uri /en-US/resources/$1 =404; - expires 1w; -} # cache //main.f40e91d908a068a2.js forever since they never change location ~ ^/([a-z][a-z])/(.+\..+\.(js|css)) { try_files $uri =404; @@ -84,7 +78,7 @@ location ~ ^/([a-z][a-z])/ { # cache /resources/** for 1 week since they don't change often location /resources { - try_files $uri /en-US/$uri /en-US/index.html; + try_files $uri /en-US/index.html; expires 1w; } # cache /main.f40e91d908a068a2.js forever since they never change