Merge branch 'master' into nymkappa/bugfix/isp-chart-color
This commit is contained in:
commit
91c607b0e8
@ -110,6 +110,11 @@ Run the Mempool backend:
|
|||||||
|
|
||||||
```
|
```
|
||||||
npm run start
|
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:
|
When it's running, you should see output like this:
|
||||||
|
@ -22,7 +22,10 @@
|
|||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tsc": "./node_modules/typescript/bin/tsc -p tsconfig.build.json",
|
"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": "node --max-old-space-size=2048 dist/index.js",
|
||||||
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
||||||
"test": "./node_modules/.bin/jest --coverage",
|
"test": "./node_modules/.bin/jest --coverage",
|
||||||
|
@ -1,60 +1,37 @@
|
|||||||
import * as fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as os from 'os';
|
import path from 'path';
|
||||||
import logger from '../logger';
|
import os from 'os';
|
||||||
import { IBackendInfo } from '../mempool.interfaces';
|
import { IBackendInfo } from '../mempool.interfaces';
|
||||||
const { spawnSync } = require('child_process');
|
|
||||||
|
|
||||||
class BackendInfo {
|
class BackendInfo {
|
||||||
private gitCommitHash = '';
|
private backendInfo: IBackendInfo;
|
||||||
private hostname = '';
|
|
||||||
private version = '';
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.setLatestCommitHash();
|
// This file is created by ./fetch-version.ts during building
|
||||||
this.setVersion();
|
const versionFile = path.join(__dirname, 'version.json')
|
||||||
this.hostname = os.hostname();
|
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 = {
|
||||||
public getBackendInfo(): IBackendInfo {
|
hostname: os.hostname(),
|
||||||
return {
|
version: versionInfo.version,
|
||||||
hostname: this.hostname,
|
gitCommit: versionInfo.gitCommit
|
||||||
gitCommit: this.gitCommitHash,
|
|
||||||
version: this.version,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getBackendInfo(): IBackendInfo {
|
||||||
|
return this.backendInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public getShortCommitHash() {
|
public getShortCommitHash() {
|
||||||
return this.gitCommitHash.slice(0, 7);
|
return this.backendInfo.gitCommit.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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,6 +375,7 @@ class ChannelsApi {
|
|||||||
'transaction_vout': channel.transaction_vout,
|
'transaction_vout': channel.transaction_vout,
|
||||||
'closing_transaction_id': channel.closing_transaction_id,
|
'closing_transaction_id': channel.closing_transaction_id,
|
||||||
'closing_reason': channel.closing_reason,
|
'closing_reason': channel.closing_reason,
|
||||||
|
'closing_date': channel.closing_date,
|
||||||
'updated_at': channel.updated_at,
|
'updated_at': channel.updated_at,
|
||||||
'created': channel.created,
|
'created': channel.created,
|
||||||
'status': channel.status,
|
'status': channel.status,
|
||||||
|
@ -5,6 +5,49 @@ import { ILightningApi } from '../lightning/lightning-api.interface';
|
|||||||
import { ITopNodesPerCapacity, ITopNodesPerChannels } from '../../mempool.interfaces';
|
import { ITopNodesPerCapacity, ITopNodesPerChannels } from '../../mempool.interfaces';
|
||||||
|
|
||||||
class NodesApi {
|
class NodesApi {
|
||||||
|
public async $getWorldNodes(): Promise<any> {
|
||||||
|
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<any> {
|
public async $getNode(public_key: string): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// General info
|
// General info
|
||||||
@ -133,10 +176,13 @@ class NodesApi {
|
|||||||
CAST(COALESCE(nodes.capacity, 0) as INT) as capacity,
|
CAST(COALESCE(nodes.capacity, 0) as INT) as capacity,
|
||||||
CAST(COALESCE(nodes.channels, 0) as INT) as channels,
|
CAST(COALESCE(nodes.channels, 0) as INT) as channels,
|
||||||
UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt,
|
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
|
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_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_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
|
ORDER BY capacity DESC
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
`;
|
`;
|
||||||
@ -175,10 +221,13 @@ class NodesApi {
|
|||||||
CAST(COALESCE(nodes.channels, 0) as INT) as channels,
|
CAST(COALESCE(nodes.channels, 0) as INT) as channels,
|
||||||
CAST(COALESCE(nodes.capacity, 0) as INT) as capacity,
|
CAST(COALESCE(nodes.capacity, 0) as INT) as capacity,
|
||||||
UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt,
|
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
|
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_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_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
|
ORDER BY channels DESC
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
`;
|
`;
|
||||||
@ -221,11 +270,14 @@ class NodesApi {
|
|||||||
CAST(COALESCE(node_stats.channels, 0) as INT) as channels,
|
CAST(COALESCE(node_stats.channels, 0) as INT) as channels,
|
||||||
CAST(COALESCE(node_stats.capacity, 0) as INT) as capacity,
|
CAST(COALESCE(node_stats.capacity, 0) as INT) as capacity,
|
||||||
UNIX_TIMESTAMP(nodes.first_seen) as firstSeen, UNIX_TIMESTAMP(nodes.updated_at) as updatedAt,
|
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
|
FROM node_stats
|
||||||
RIGHT JOIN nodes ON nodes.public_key = node_stats.public_key
|
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_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_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})
|
WHERE added = FROM_UNIXTIME(${latestDate})
|
||||||
ORDER BY first_seen
|
ORDER BY first_seen
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
|
@ -9,6 +9,7 @@ class NodesRoutes {
|
|||||||
|
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
app
|
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/country/:country', this.$getNodesPerCountry)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/search/:search', this.$searchNode)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/search/:search', this.$searchNode)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/isp-ranking', this.$getISPRanking)
|
.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<void> {
|
private async $getISPRanking(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const nodesPerAs = await nodesApi.$getNodesISPRanking();
|
const nodesPerAs = await nodesApi.$getNodesISPRanking();
|
||||||
|
|
||||||
res.header('Pragma', 'public');
|
res.header('Pragma', 'public');
|
||||||
res.header('Cache-control', 'public');
|
res.header('Cache-control', 'public');
|
||||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
|
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) {
|
private async $getNodesPerCountry(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const [country]: any[] = await DB.query(
|
const [country]: any[] = await DB.query(
|
||||||
|
37
backend/src/api/fetch-version.ts
Normal file
37
backend/src/api/fetch-version.ts
Normal file
@ -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"
|
||||||
|
);
|
@ -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 {
|
interface IConfig {
|
||||||
MEMPOOL: {
|
MEMPOOL: {
|
||||||
@ -249,7 +251,7 @@ class Config implements IConfig {
|
|||||||
MAXMIND: IConfig['MAXMIND'];
|
MAXMIND: IConfig['MAXMIND'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFile, defaults);
|
const configs = this.merge(configFromFile, defaults);
|
||||||
this.MEMPOOL = configs.MEMPOOL;
|
this.MEMPOOL = configs.MEMPOOL;
|
||||||
this.ESPLORA = configs.ESPLORA;
|
this.ESPLORA = configs.ESPLORA;
|
||||||
this.ELECTRUM = configs.ELECTRUM;
|
this.ELECTRUM = configs.ELECTRUM;
|
||||||
|
@ -12,9 +12,11 @@ import { ResultSetHeader } from 'mysql2';
|
|||||||
import fundingTxFetcher from './sync-tasks/funding-tx-fetcher';
|
import fundingTxFetcher from './sync-tasks/funding-tx-fetcher';
|
||||||
import NodesSocketsRepository from '../../repositories/NodesSocketsRepository';
|
import NodesSocketsRepository from '../../repositories/NodesSocketsRepository';
|
||||||
import { Common } from '../../api/common';
|
import { Common } from '../../api/common';
|
||||||
|
import blocks from '../../api/blocks';
|
||||||
|
|
||||||
class NetworkSyncService {
|
class NetworkSyncService {
|
||||||
loggerTimer = 0;
|
loggerTimer = 0;
|
||||||
|
closedChannelsScanBlock = 0;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@ -240,10 +242,22 @@ class NetworkSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $scanForClosedChannels(): Promise<void> {
|
private async $scanForClosedChannels(): Promise<void> {
|
||||||
|
if (this.closedChannelsScanBlock === blocks.getCurrentBlockHeight()) {
|
||||||
|
logger.debug(`We've already scan closed channels for this block, skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
|
|
||||||
try {
|
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]);
|
const channels = await channelsApi.$getChannelsByStatus([0, 1]);
|
||||||
for (const channel of channels) {
|
for (const channel of channels) {
|
||||||
const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout);
|
const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout);
|
||||||
@ -263,7 +277,9 @@ class NetworkSyncService {
|
|||||||
this.loggerTimer = new Date().getTime() / 1000;
|
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) {
|
} catch (e) {
|
||||||
logger.err('$scanForClosedChannels() error: ' + (e instanceof Error ? e.message : e));
|
logger.err('$scanForClosedChannels() error: ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import path from "path";
|
||||||
import { Common } from '../api/common';
|
import { Common } from '../api/common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
@ -159,7 +160,7 @@ class PriceUpdater {
|
|||||||
const existingPriceTimes = await PricesRepository.$getPricesTimes();
|
const existingPriceTimes = await PricesRepository.$getPricesTimes();
|
||||||
|
|
||||||
// Insert MtGox weekly prices
|
// 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();
|
const prices = this.getEmptyPricesObj();
|
||||||
let insertedCount: number = 0;
|
let insertedCount: number = 0;
|
||||||
for (const price of pricesJson) {
|
for (const price of pricesJson) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM node:16.16.0-buster-slim AS builder
|
FROM node:16.16.0-buster-slim AS builder
|
||||||
|
|
||||||
ARG commitHash
|
ARG commitHash
|
||||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY . .
|
COPY . .
|
||||||
@ -9,18 +9,15 @@ COPY . .
|
|||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y build-essential python3 pkg-config
|
RUN apt-get install -y build-essential python3 pkg-config
|
||||||
RUN npm install --omit=dev --omit=optional
|
RUN npm install --omit=dev --omit=optional
|
||||||
RUN npm run build
|
RUN npm run package
|
||||||
|
|
||||||
FROM node:16.16.0-buster-slim
|
FROM node:16.16.0-buster-slim
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
COPY --from=builder /build/ .
|
RUN chown 1000:1000 ./
|
||||||
|
COPY --from=builder --chown=1000:1000 /build/package ./package/
|
||||||
RUN chmod +x /backend/start.sh
|
COPY --from=builder --chown=1000:1000 /build/mempool-config.json /build/start.sh /build/wait-for-it.sh ./
|
||||||
RUN chmod +x /backend/wait-for-it.sh
|
|
||||||
|
|
||||||
RUN chown -R 1000:1000 /backend && chmod -R 755 /backend
|
|
||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
|
2
docker/backend/start.sh
Normal file → Executable file
2
docker/backend/start.sh
Normal file → Executable file
@ -205,4 +205,4 @@ sed -i "s!__LND_REST_API_URL__!${__LND_REST_API_URL__}!g" mempool-config.json
|
|||||||
# CLN
|
# CLN
|
||||||
sed -i "s!__CLN_SOCKET__!${__CLN_SOCKET__}!g" mempool-config.json
|
sed -i "s!__CLN_SOCKET__!${__CLN_SOCKET__}!g" mempool-config.json
|
||||||
|
|
||||||
node /backend/dist/index.js
|
node /backend/package/index.js
|
||||||
|
0
docker/backend/wait-for-it.sh
Normal file → Executable file
0
docker/backend/wait-for-it.sh
Normal file → Executable file
@ -1,10 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#backend
|
#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/
|
cp ./docker/backend/* ./backend/
|
||||||
sed -i "s/${gitMaster}/master/g" ./backend/src/api/backend-info.ts
|
|
||||||
|
|
||||||
#frontend
|
#frontend
|
||||||
localhostIP="127.0.0.1"
|
localhostIP="127.0.0.1"
|
||||||
|
@ -170,6 +170,10 @@
|
|||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/robots.txt"
|
||||||
|
],
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
{
|
{
|
||||||
"replace": "src/environments/environment.ts",
|
"replace": "src/environments/environment.ts",
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
"start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging",
|
"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",
|
"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",
|
"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",
|
"sync-assets-dev": "node sync-assets.js dev",
|
||||||
"generate-config": "node generate-config.js",
|
"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",
|
"build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js",
|
||||||
|
@ -187,8 +187,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="selfhosted-integrations-sponsor">
|
<div class="community-integrations-sponsor">
|
||||||
<h3 i18n="about.self-hosted-integrations">Self-Hosted Integrations</h3>
|
<h3 i18n="about.community-integrations">Community Integrations</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
||||||
<img class="image" src="/resources/profile/umbrel.png" />
|
<img class="image" src="/resources/profile/umbrel.png" />
|
||||||
@ -218,18 +218,24 @@
|
|||||||
<img class="image" src="/resources/profile/start9.png" />
|
<img class="image" src="/resources/profile/start9.png" />
|
||||||
<span>EmbassyOS</span>
|
<span>EmbassyOS</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a href="https://github.com/btcpayserver/btcpayserver" target="_blank" title="BTCPay Server">
|
||||||
</div>
|
<img class="image" src="/resources/profile/btcpayserver.svg" />
|
||||||
|
<span>BTCPay</span>
|
||||||
<div class="community-integrations-sponsor">
|
</a>
|
||||||
<h3 i18n="about.wallet-integrations">Wallet Integrations</h3>
|
|
||||||
<div class="wrapper">
|
|
||||||
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
||||||
<img class="image" src="/resources/profile/bisq_network.png" />
|
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||||
<span>Bisq</span>
|
<span>Bisq</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank" title="BlueWallet">
|
||||||
|
<img class="image" src="/resources/profile/bluewallet.png" />
|
||||||
|
<span>BlueWallet</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/muun/apollo" target="_blank" title="Muun Wallet">
|
||||||
|
<img class="image" src="/resources/profile/muun.png" />
|
||||||
|
<span>Muun</span>
|
||||||
|
</a>
|
||||||
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||||
<img class="image" src="/resources/profile/electrum.jpg" />
|
<img class="image" src="/resources/profile/electrum.png" />
|
||||||
<span>Electrum</span>
|
<span>Electrum</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank" title="Specter Wallet">
|
<a href="https://github.com/cryptoadvance/specter-desktop" target="_blank" title="Specter Wallet">
|
||||||
@ -244,18 +250,14 @@
|
|||||||
<img class="image" src="/resources/profile/phoenix.jpg" />
|
<img class="image" src="/resources/profile/phoenix.jpg" />
|
||||||
<span>Phoenix</span>
|
<span>Phoenix</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits">
|
||||||
|
<img class="image" src="/resources/profile/lnbits.svg" />
|
||||||
|
<span>LNBits</span>
|
||||||
|
</a>
|
||||||
<a href="https://github.com/layer2tech/mercury-wallet" target="_blank" title="Mercury Wallet">
|
<a href="https://github.com/layer2tech/mercury-wallet" target="_blank" title="Mercury Wallet">
|
||||||
<img class="image" src="/resources/profile/mercury.svg" />
|
<img class="image" src="/resources/profile/mercury.svg" />
|
||||||
<span>Mercury</span>
|
<span>Mercury</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/muun/apollo" target="_blank" title="Muun Wallet">
|
|
||||||
<img class="image" src="/resources/profile/muun.png" />
|
|
||||||
<span>Muun</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/BlueWallet/BlueWallet" target="_blank" title="BlueWallet">
|
|
||||||
<img class="image" src="/resources/profile/bluewallet.png" />
|
|
||||||
<span>BlueWallet</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank" title="Blixt Wallet">
|
<a href="https://github.com/hsjoberg/blixt-wallet" target="_blank" title="Blixt Wallet">
|
||||||
<img class="image" src="/resources/profile/blixt.png" />
|
<img class="image" src="/resources/profile/blixt.png" />
|
||||||
<span>Blixt</span>
|
<span>Blixt</span>
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
.alliances,
|
.alliances,
|
||||||
.enterprise-sponsor,
|
.enterprise-sponsor,
|
||||||
.community-integrations-sponsor,
|
.community-integrations-sponsor,
|
||||||
.selfhosted-integrations-sponsor,
|
|
||||||
.maintainers {
|
.maintainers {
|
||||||
margin-top: 68px;
|
margin-top: 68px;
|
||||||
margin-bottom: 68px;
|
margin-bottom: 68px;
|
||||||
@ -117,7 +116,6 @@
|
|||||||
.community-sponsor,
|
.community-sponsor,
|
||||||
.project-translators,
|
.project-translators,
|
||||||
.community-integrations-sponsor,
|
.community-integrations-sponsor,
|
||||||
.selfhosted-integrations-sponsor,
|
|
||||||
.maintainers {
|
.maintainers {
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -193,6 +191,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.community-integrations-sponsor {
|
.community-integrations-sponsor {
|
||||||
max-width: 830px;
|
max-width: 970px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { map } from 'rxjs/operators';
|
|||||||
import { moveDec } from 'src/app/bitcoin.utils';
|
import { moveDec } from 'src/app/bitcoin.utils';
|
||||||
import { AssetsService } from 'src/app/services/assets.service';
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||||
import { formatNumber } from '@angular/common';
|
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -161,6 +161,9 @@ export interface ITopNodesPerChannels {
|
|||||||
updatedAt?: number,
|
updatedAt?: number,
|
||||||
city?: any,
|
city?: any,
|
||||||
country?: any,
|
country?: any,
|
||||||
|
subdivision?: any,
|
||||||
|
iso_code?: string,
|
||||||
|
geolocation?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITopNodesPerCapacity {
|
export interface ITopNodesPerCapacity {
|
||||||
@ -172,6 +175,9 @@ export interface ITopNodesPerCapacity {
|
|||||||
updatedAt?: number,
|
updatedAt?: number,
|
||||||
city?: any,
|
city?: any,
|
||||||
country?: any,
|
country?: any,
|
||||||
|
subdivision?: any,
|
||||||
|
iso_code?: string,
|
||||||
|
geolocation?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodesRanking {
|
export interface INodesRanking {
|
||||||
@ -188,6 +194,9 @@ export interface IOldestNodes {
|
|||||||
updatedAt?: number,
|
updatedAt?: number,
|
||||||
city?: any,
|
city?: any,
|
||||||
country?: any,
|
country?: any,
|
||||||
|
subdivision?: any,
|
||||||
|
iso_code?: string,
|
||||||
|
geolocation?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChannel {
|
export interface IChannel {
|
||||||
|
@ -19,31 +19,31 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Fee rate</td>
|
<td i18n="address.total-sent">Fee rate</td>
|
||||||
<td>
|
<td>
|
||||||
{{ channel.fee_rate }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
{{ channel.fee_rate ?? '-' }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Base fee</td>
|
<td i18n="address.total-sent">Base fee</td>
|
||||||
<td>
|
<td>
|
||||||
<app-sats [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
|
<app-sats [valueOverride]="!channel.base_fee_mtokens ? '- ' : undefined" [satoshis]="channel.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Min HTLC</td>
|
<td i18n="address.total-sent">Min HTLC</td>
|
||||||
<td>
|
<td>
|
||||||
<app-sats [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
<app-sats [valueOverride]="!channel.min_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.min_htlc_mtokens / 1000"></app-sats>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Max HTLC</td>
|
<td i18n="address.total-sent">Max HTLC</td>
|
||||||
<td>
|
<td>
|
||||||
<app-sats [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
<app-sats [valueOverride]="!channel.max_htlc_mtokens ? '- ' : undefined" [satoshis]="channel.max_htlc_mtokens / 1000"></app-sats>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Timelock delta</td>
|
<td i18n="address.total-sent">Timelock delta</td>
|
||||||
<td>
|
<td>
|
||||||
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta }"></ng-container>
|
<ng-container *ngTemplateOutlet="blocksPlural; context: {$implicit: channel.cltv_delta ?? '-' }"></ng-container>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -25,13 +25,17 @@
|
|||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="address.total-sent">Created</td>
|
<td i18n="lightning.created">Created</td>
|
||||||
<td><app-timestamp [dateString]="channel.created"></app-timestamp></td>
|
<td><app-timestamp [dateString]="channel.created"></app-timestamp></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr *ngIf="channel.status !== 2">
|
||||||
<td i18n="address.total-sent">Last update</td>
|
<td i18n="lightning.last-update">Last update</td>
|
||||||
<td><app-timestamp [dateString]="channel.updated_at"></app-timestamp></td>
|
<td><app-timestamp [dateString]="channel.updated_at"></app-timestamp></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="channel.status === 2">
|
||||||
|
<td i18n="lightning.closing_date">Closing date</td>
|
||||||
|
<td><app-timestamp [dateString]="channel.closing_date"></app-timestamp></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { Observable, of, zip } from 'rxjs';
|
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 { IChannel } from 'src/app/interfaces/node-api.interface';
|
||||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
@ -31,9 +31,11 @@ export class ChannelComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.seoService.setTitle(`Channel: ${params.get('short_id')}`);
|
|
||||||
return this.lightningApiService.getChannel$(params.get('short_id'))
|
return this.lightningApiService.getChannel$(params.get('short_id'))
|
||||||
.pipe(
|
.pipe(
|
||||||
|
tap((value) => {
|
||||||
|
this.seoService.setTitle(`Channel: ${value.short_id}`);
|
||||||
|
}),
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
this.error = err;
|
this.error = err;
|
||||||
return of(null);
|
return of(null);
|
||||||
|
@ -35,7 +35,8 @@
|
|||||||
<th class="alias text-left" i18n="nodes.alias">Node Alias</th>
|
<th class="alias text-left" i18n="nodes.alias">Node Alias</th>
|
||||||
<th class="alias text-left d-none d-md-table-cell" i18n="channels.transaction"> </th>
|
<th class="alias text-left d-none d-md-table-cell" i18n="channels.transaction"> </th>
|
||||||
<th class="alias text-left d-none d-md-table-cell" i18n="status">Status</th>
|
<th class="alias text-left d-none d-md-table-cell" i18n="status">Status</th>
|
||||||
<th class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
<th *ngIf="status !== 'closed'" class="channels text-left d-none d-md-table-cell" i18n="channels.rate">Fee Rate</th>
|
||||||
|
<th *ngIf="status === 'closed'" class="channels text-left d-none d-md-table-cell" i18n="channels.closing_date">Closing date</th>
|
||||||
<th class="capacity text-right d-none d-md-table-cell" i18n="nodes.capacity">Capacity</th>
|
<th class="capacity text-right d-none d-md-table-cell" i18n="nodes.capacity">Capacity</th>
|
||||||
<th class="capacity text-right" i18n="channels.id">Channel ID</th>
|
<th class="capacity text-right" i18n="channels.id">Channel ID</th>
|
||||||
</thead>
|
</thead>
|
||||||
@ -71,9 +72,12 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td class="capacity text-left d-none d-md-table-cell">
|
<td *ngIf="status !== 'closed'" class="capacity text-left d-none d-md-table-cell">
|
||||||
{{ channel.fee_rate }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
{{ channel.fee_rate }} <span class="symbol">ppm ({{ channel.fee_rate / 10000 | number }}%)</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td *ngIf="status === 'closed'" class="capacity text-left d-none d-md-table-cell">
|
||||||
|
<app-timestamp [unixTime]="channel.closing_date"></app-timestamp>
|
||||||
|
</td>
|
||||||
<td class="capacity text-right d-none d-md-table-cell">
|
<td class="capacity text-right d-none d-md-table-cell">
|
||||||
<app-amount *ngIf="channel.capacity > 100000000; else smallchannel" [satoshis]="channel.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
<app-amount *ngIf="channel.capacity > 100000000; else smallchannel" [satoshis]="channel.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||||
<ng-template #smallchannel>
|
<ng-template #smallchannel>
|
||||||
|
@ -2,10 +2,7 @@
|
|||||||
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
||||||
<span i18n="lightning.nodes-heatmap">Lightning nodes world heat map</span>
|
<span i18n="lightning.nodes-world-map">Lightning nodes world map</span>
|
||||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px">
|
|
||||||
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true" (click)="onSaveChart()"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
<small style="color: #ffffff66" i18n="lightning.tor-nodes-excluded">(Tor nodes excluded)</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { mempoolFeeColors } from 'src/app/app.constants';
|
|
||||||
import { SeoService } from 'src/app/services/seo.service';
|
import { SeoService } from 'src/app/services/seo.service';
|
||||||
import { ApiService } from 'src/app/services/api.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 { AssetsService } from 'src/app/services/assets.service';
|
||||||
import { EChartsOption, registerMap } from 'echarts';
|
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 { Router } from '@angular/router';
|
||||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
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({
|
@Component({
|
||||||
selector: 'app-nodes-map',
|
selector: 'app-nodes-map',
|
||||||
@ -16,7 +17,7 @@ import { StateService } from 'src/app/services/state.service';
|
|||||||
styleUrls: ['./nodes-map.component.scss'],
|
styleUrls: ['./nodes-map.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class NodesMap implements OnInit, OnDestroy {
|
export class NodesMap implements OnInit {
|
||||||
observable$: Observable<any>;
|
observable$: Observable<any>;
|
||||||
|
|
||||||
chartInstance = undefined;
|
chartInstance = undefined;
|
||||||
@ -26,44 +27,52 @@ export class NodesMap implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private assetsService: AssetsService,
|
private assetsService: AssetsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
private amountShortenerPipe: AmountShortenerPipe
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.seoService.setTitle($localize`Lightning nodes world map`);
|
this.seoService.setTitle($localize`Lightning nodes world map`);
|
||||||
|
|
||||||
this.observable$ = combineLatest([
|
this.observable$ = zip(
|
||||||
this.assetsService.getWorldMapJson$,
|
this.assetsService.getWorldMapJson$,
|
||||||
this.apiService.getNodesPerCountry()
|
this.apiService.getWorldNodes$()
|
||||||
]).pipe(tap((data) => {
|
).pipe(tap((data) => {
|
||||||
registerMap('world', data[0]);
|
registerMap('world', data[0]);
|
||||||
|
|
||||||
const countries = [];
|
const nodes: any[] = [];
|
||||||
let max = 0;
|
console.log(data[1].nodes[0]);
|
||||||
for (const country of data[1]) {
|
for (const node of data[1].nodes) {
|
||||||
countries.push({
|
// We add a bit of noise so nodes at the same location are not all
|
||||||
name: country.name.en,
|
// on top of each other
|
||||||
value: country.count,
|
const random = Math.random() * 2 * Math.PI;
|
||||||
iso: country.iso.toLowerCase(),
|
const random2 = Math.random() * 0.01;
|
||||||
});
|
nodes.push([
|
||||||
max = Math.max(max, country.count);
|
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;
|
let title: object;
|
||||||
if (countries.length === 0) {
|
if (nodes.length === 0) {
|
||||||
title = {
|
title = {
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'grey',
|
color: 'grey',
|
||||||
@ -76,53 +85,80 @@ export class NodesMap implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
title: countries.length === 0 ? title : undefined,
|
silent: false,
|
||||||
|
title: title ?? undefined,
|
||||||
|
tooltip: {},
|
||||||
|
geo: {
|
||||||
|
animation: false,
|
||||||
|
silent: true,
|
||||||
|
center: [0, 5],
|
||||||
|
zoom: 1.3,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
map: 'world',
|
||||||
|
roam: true,
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: 'black',
|
||||||
|
color: '#272b3f'
|
||||||
|
},
|
||||||
|
scaleLimit: {
|
||||||
|
min: 1.3,
|
||||||
|
max: 100000,
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
disabled: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)',
|
backgroundColor: 'rgba(17, 19, 31, 1)',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#b1b1b1',
|
color: '#b1b1b1',
|
||||||
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: function(country) {
|
formatter: (value) => {
|
||||||
if (country.data === undefined) {
|
const data = value.data;
|
||||||
return `<b style="color: white">${country.name}<br>0 nodes</b><br>`;
|
const alias = data[3].length > 0 ? data[3] : data[4].slice(0, 20);
|
||||||
} else {
|
const liquidity = data[2] >= 100000000 ?
|
||||||
return `<b style="color: white">${country.data.name}<br>${country.data.value} nodes</b><br>`;
|
`${this.amountShortenerPipe.transform(data[2] / 100000000)} BTC` :
|
||||||
|
`${this.amountShortenerPipe.transform(data[2], 2)} sats`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<b style="color: white">${alias}</b><br>
|
||||||
|
${liquidity}<br>
|
||||||
|
${data[5]} channels<br>
|
||||||
|
${getFlagEmoji(data[7])} ${data[6]}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
areaColor: '#FDD835',
|
color: function (params) {
|
||||||
}
|
return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[2] / maxLiquidity, 0.2))}`;
|
||||||
},
|
},
|
||||||
data: countries,
|
opacity: 1,
|
||||||
itemStyle: {
|
borderColor: 'black',
|
||||||
areaColor: '#5A6A6D'
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
}
|
blendMode: 'lighter',
|
||||||
|
zlevel: 2,
|
||||||
|
},
|
||||||
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,30 +170,16 @@ export class NodesMap implements OnInit, OnDestroy {
|
|||||||
this.chartInstance = ec;
|
this.chartInstance = ec;
|
||||||
|
|
||||||
this.chartInstance.on('click', (e) => {
|
this.chartInstance.on('click', (e) => {
|
||||||
if (e.data && e.data.value > 0) {
|
if (e.data) {
|
||||||
this.zone.run(() => {
|
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]);
|
this.router.navigate([url]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
onSaveChart() {
|
this.chartInstance.on('georoam', (e) => {
|
||||||
// @ts-ignore
|
this.chartInstance.resize();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export class NodesPerCountryChartComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.seoService.setTitle($localize`Lightning nodes per country`);
|
this.seoService.setTitle($localize`Lightning nodes per country`);
|
||||||
|
|
||||||
this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry()
|
this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$()
|
||||||
.pipe(
|
.pipe(
|
||||||
map(data => {
|
map(data => {
|
||||||
for (let i = 0; i < data.length; ++i) {
|
for (let i = 0; i < data.length; ++i) {
|
||||||
|
@ -6,15 +6,17 @@
|
|||||||
|
|
||||||
<div style="min-height: 295px">
|
<div style="min-height: 295px">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th class="alias text-left" i18n="lightning.alias">Alias</th>
|
<th class="alias text-left" i18n="lightning.alias">Alias</th>
|
||||||
<th class="timestamp-first text-left" i18n="lightning.first_seen">First seen</th>
|
<th class="timestamp-first text-left" i18n="lightning.first_seen">First seen</th>
|
||||||
<th class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
<th class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
||||||
<th class="capacity text-right" i18n="lightning.capacity">Capacity</th>
|
<th class="capacity text-right" i18n="lightning.capacity">Capacity</th>
|
||||||
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
||||||
<th class="city text-right" i18n="lightning.city">City</th>
|
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="nodes$ | async as nodes">
|
|
||||||
|
<tbody *ngIf="nodes$ | async as nodes; else skeleton">
|
||||||
<tr *ngFor="let node of nodes; let i= index; trackBy: trackByPublicKey">
|
<tr *ngFor="let node of nodes; let i= index; trackBy: trackByPublicKey">
|
||||||
<td class="alias text-left text-truncate">
|
<td class="alias text-left text-truncate">
|
||||||
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
||||||
@ -39,6 +41,32 @@
|
|||||||
<app-geolocation [data]="node.geolocation" [type]="'list-country'"></app-geolocation>
|
<app-geolocation [data]="node.geolocation" [type]="'list-country'"></app-geolocation>
|
||||||
</td>
|
</td>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
|
<ng-template #skeleton>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let item of skeletonLines">
|
||||||
|
<td class="alias text-left text-truncate">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp-first text-left">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp-update text-left">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="capacity text-right">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="channels text-right">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="city text-right text-truncate">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,11 +16,17 @@ export class NodesPerCountry implements OnInit {
|
|||||||
nodes$: Observable<any>;
|
nodes$: Observable<any>;
|
||||||
country: {name: string, flag: string};
|
country: {name: string, flag: string};
|
||||||
|
|
||||||
|
skeletonLines: number[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) { }
|
) {
|
||||||
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
this.skeletonLines.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
|
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
|
||||||
|
@ -47,7 +47,9 @@ export class NodesPerISPChartComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.widget) {
|
||||||
this.seoService.setTitle($localize`Lightning nodes per ISP`);
|
this.seoService.setTitle($localize`Lightning nodes per ISP`);
|
||||||
|
}
|
||||||
|
|
||||||
this.nodesPerAsObservable$ = combineLatest([
|
this.nodesPerAsObservable$ = combineLatest([
|
||||||
this.sortBySubject.pipe(startWith(true)),
|
this.sortBySubject.pipe(startWith(true)),
|
||||||
@ -105,7 +107,7 @@ export class NodesPerISPChartComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateChartSerieData(ispRanking): PieSeriesOption[] {
|
generateChartSerieData(ispRanking): PieSeriesOption[] {
|
||||||
let shareThreshold = 0.5;
|
let shareThreshold = 0.4;
|
||||||
if (this.widget && isMobile() || isMobile()) {
|
if (this.widget && isMobile() || isMobile()) {
|
||||||
shareThreshold = 1;
|
shareThreshold = 1;
|
||||||
} else if (this.widget) {
|
} else if (this.widget) {
|
||||||
|
@ -3,15 +3,17 @@
|
|||||||
|
|
||||||
<div style="min-height: 295px">
|
<div style="min-height: 295px">
|
||||||
<table class="table table-borderless">
|
<table class="table table-borderless">
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th class="alias text-left" i18n="lightning.alias">Alias</th>
|
<th class="alias text-left" i18n="lightning.alias">Alias</th>
|
||||||
<th class="timestamp-first text-left" i18n="lightning.first_seen">First seen</th>
|
<th class="timestamp-first text-left" i18n="lightning.first_seen">First seen</th>
|
||||||
<th class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
<th class="timestamp-update text-left" i18n="lightning.last_update">Last update</th>
|
||||||
<th class="capacity text-right" i18n="lightning.capacity">Capacity</th>
|
<th class="capacity text-right" i18n="lightning.capacity">Capacity</th>
|
||||||
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
||||||
<th class="city text-right" i18n="lightning.city">City</th>
|
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="nodes$ | async as nodes">
|
|
||||||
|
<tbody *ngIf="nodes$ | async as nodes; else skeleton">
|
||||||
<tr *ngFor="let node of nodes; let i= index; trackBy: trackByPublicKey">
|
<tr *ngFor="let node of nodes; let i= index; trackBy: trackByPublicKey">
|
||||||
<td class="alias text-left text-truncate">
|
<td class="alias text-left text-truncate">
|
||||||
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
||||||
@ -36,6 +38,32 @@
|
|||||||
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
</td>
|
</td>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
|
<ng-template #skeleton>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let item of skeletonLines">
|
||||||
|
<td class="alias text-left text-truncate">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp-first text-left">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp-update text-left">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="capacity text-right">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="channels text-right">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="city text-right text-truncate">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -15,11 +15,17 @@ export class NodesPerISP implements OnInit {
|
|||||||
nodes$: Observable<any>;
|
nodes$: Observable<any>;
|
||||||
isp: {name: string, id: number};
|
isp: {name: string, id: number};
|
||||||
|
|
||||||
|
skeletonLines: number[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) { }
|
) {
|
||||||
|
for (let i = 0; i < 20; ++i) {
|
||||||
|
this.skeletonLines.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
|
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||||
{{ node?.city?.en ?? '-' }}
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
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 { IOldestNodes } from '../../../interfaces/node-api.interface';
|
||||||
import { LightningApiService } from '../../lightning-api.service';
|
import { LightningApiService } from '../../lightning-api.service';
|
||||||
|
|
||||||
@ -15,19 +17,38 @@ export class OldestNodes implements OnInit {
|
|||||||
oldestNodes$: Observable<IOldestNodes[]>;
|
oldestNodes$: Observable<IOldestNodes[]>;
|
||||||
skeletonRows: number[] = [];
|
skeletonRows: number[] = [];
|
||||||
|
|
||||||
constructor(private apiService: LightningApiService) {}
|
constructor(
|
||||||
|
private apiService: LightningApiService,
|
||||||
|
private seoService: SeoService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.widget) {
|
||||||
|
this.seoService.setTitle($localize`Oldest lightning nodes`);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i <= (this.widget ? 10 : 100); ++i) {
|
for (let i = 1; i <= (this.widget ? 10 : 100); ++i) {
|
||||||
this.skeletonRows.push(i);
|
this.skeletonRows.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.widget === false) {
|
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 = <GeolocationData>{
|
||||||
|
country: ranking[i].country?.en,
|
||||||
|
city: ranking[i].city?.en,
|
||||||
|
subdivision: ranking[i].subdivision?.en,
|
||||||
|
iso: ranking[i].iso_code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ranking;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.oldestNodes$ = this.apiService.getOldestNodes$().pipe(
|
this.oldestNodes$ = this.apiService.getOldestNodes$().pipe(
|
||||||
map((nodes: IOldestNodes[]) => {
|
map((nodes: IOldestNodes[]) => {
|
||||||
return nodes.slice(0, 10);
|
return nodes.slice(0, 7);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||||
{{ node?.city?.en ?? '-' }}
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { INodesRanking, ITopNodesPerCapacity } from 'src/app/interfaces/node-api.interface';
|
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 { isMobile } from 'src/app/shared/common.utils';
|
||||||
|
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
|
||||||
import { LightningApiService } from '../../lightning-api.service';
|
import { LightningApiService } from '../../lightning-api.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -17,15 +19,34 @@ export class TopNodesPerCapacity implements OnInit {
|
|||||||
topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>;
|
topNodesPerCapacity$: Observable<ITopNodesPerCapacity[]>;
|
||||||
skeletonRows: number[] = [];
|
skeletonRows: number[] = [];
|
||||||
|
|
||||||
constructor(private apiService: LightningApiService) {}
|
constructor(
|
||||||
|
private apiService: LightningApiService,
|
||||||
|
private seoService: SeoService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.widget) {
|
||||||
|
this.seoService.setTitle($localize`Liquidity Ranking`);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||||
this.skeletonRows.push(i);
|
this.skeletonRows.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.widget === false) {
|
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 = <GeolocationData>{
|
||||||
|
country: ranking[i].country?.en,
|
||||||
|
city: ranking[i].city?.en,
|
||||||
|
subdivision: ranking[i].subdivision?.en,
|
||||||
|
iso: ranking[i].iso_code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ranking;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.topNodesPerCapacity$ = this.nodes$.pipe(
|
this.topNodesPerCapacity$ = this.nodes$.pipe(
|
||||||
map((ranking) => {
|
map((ranking) => {
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
<app-timestamp [customFormat]="'yyyy-MM-dd'" [unixTime]="node.updatedAt"></app-timestamp>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="!widget" class="location text-right text-truncate">
|
<td *ngIf="!widget" class="location text-right text-truncate">
|
||||||
{{ node?.city?.en ?? '-' }}
|
<app-geolocation [data]="node.geolocation" [type]="'list-isp'"></app-geolocation>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { INodesRanking, ITopNodesPerChannels } from 'src/app/interfaces/node-api.interface';
|
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 { isMobile } from 'src/app/shared/common.utils';
|
||||||
|
import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component';
|
||||||
import { LightningApiService } from '../../lightning-api.service';
|
import { LightningApiService } from '../../lightning-api.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -17,15 +19,34 @@ export class TopNodesPerChannels implements OnInit {
|
|||||||
topNodesPerChannels$: Observable<ITopNodesPerChannels[]>;
|
topNodesPerChannels$: Observable<ITopNodesPerChannels[]>;
|
||||||
skeletonRows: number[] = [];
|
skeletonRows: number[] = [];
|
||||||
|
|
||||||
constructor(private apiService: LightningApiService) {}
|
constructor(
|
||||||
|
private apiService: LightningApiService,
|
||||||
|
private seoService: SeoService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.widget) {
|
||||||
|
this.seoService.setTitle($localize`Connectivity Ranking`);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) {
|
||||||
this.skeletonRows.push(i);
|
this.skeletonRows.push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.widget === false) {
|
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 = <GeolocationData>{
|
||||||
|
country: ranking[i].country?.en,
|
||||||
|
city: ranking[i].city?.en,
|
||||||
|
subdivision: ranking[i].subdivision?.en,
|
||||||
|
iso: ranking[i].iso_code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return ranking;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.topNodesPerChannels$ = this.nodes$.pipe(
|
this.topNodesPerChannels$ = this.nodes$.pipe(
|
||||||
map((ranking) => {
|
map((ranking) => {
|
||||||
|
@ -267,10 +267,14 @@ export class ApiService {
|
|||||||
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp/' + isp);
|
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/isp/' + isp);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodesPerCountry(): Observable<any> {
|
getNodesPerCountry$(): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries');
|
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/countries');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWorldNodes$(): Observable<any> {
|
||||||
|
return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/nodes/world');
|
||||||
|
}
|
||||||
|
|
||||||
getChannelsGeo$(publicKey?: string, style?: 'graph' | 'nodepage' | 'widget' | 'channelpage'): Observable<any> {
|
getChannelsGeo$(publicKey?: string, style?: 'graph' | 'nodepage' | 'widget' | 'channelpage'): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels-geo' +
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/lightning/channels-geo' +
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number : digitsInfo }}
|
<span *ngIf="valueOverride !== undefined">{{ valueOverride }}</span>
|
||||||
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
<span *ngIf="valueOverride === undefined">‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number : digitsInfo }} </span>
|
||||||
|
<span class="symbol">
|
||||||
|
<ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'testnet'">t-</ng-template>
|
<ng-template [ngIf]="network === 'testnet'">t-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'signet'">s-</ng-template>sats</span>
|
<ng-template [ngIf]="network === 'signet'">s-</ng-template>sats
|
||||||
|
</span>
|
@ -11,6 +11,7 @@ export class SatsComponent implements OnInit {
|
|||||||
@Input() satoshis: number;
|
@Input() satoshis: number;
|
||||||
@Input() digitsInfo = '1.0-0';
|
@Input() digitsInfo = '1.0-0';
|
||||||
@Input() addPlus = false;
|
@Input() addPlus = false;
|
||||||
|
@Input() valueOverride: string | undefined = undefined;
|
||||||
|
|
||||||
network = '';
|
network = '';
|
||||||
stateSubscription: Subscription;
|
stateSubscription: Subscription;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
<span *ngIf="seconds === undefined">-</span>
|
||||||
|
<span *ngIf="seconds !== undefined">
|
||||||
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
||||||
<div class="lg-inline">
|
<div class="lg-inline">
|
||||||
<i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i>
|
<i class="symbol">(<app-time-since [time]="seconds" [fastRender]="true"></app-time-since>)</i>
|
||||||
</div>
|
</div>
|
||||||
|
</span>
|
||||||
|
@ -11,15 +11,13 @@ export class TimestampComponent implements OnChanges {
|
|||||||
@Input() dateString: string;
|
@Input() dateString: string;
|
||||||
@Input() customFormat: string;
|
@Input() customFormat: string;
|
||||||
|
|
||||||
seconds: number;
|
seconds: number | undefined = undefined;
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
if (this.unixTime) {
|
if (this.unixTime) {
|
||||||
this.seconds = this.unixTime;
|
this.seconds = this.unixTime;
|
||||||
} else if (this.dateString) {
|
} else if (this.dateString) {
|
||||||
this.seconds = new Date(this.dateString).getTime() / 1000
|
this.seconds = new Date(this.dateString).getTime() / 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
frontend/src/resources/profile/btcpayserver.svg
Normal file
1
frontend/src/resources/profile/btcpayserver.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 105.46 188.47"><defs><style>.cls-1{fill:#cedc21;}.cls-2{fill:#51b13e;}.cls-3{fill:#1e7a44;}.cls-4{fill:#fff;}</style></defs><title>BTCPayServer</title><path class="cls-1" d="M117.24,247.32a11.06,11.06,0,0,1-11-11.06V69.91a11.06,11.06,0,1,1,22.11,0V236.26A11.06,11.06,0,0,1,117.24,247.32Z" transform="translate(-106.19 -58.85)"/><path class="cls-2" d="M117.25,247.32a11.06,11.06,0,0,1-4.75-21l66.66-31.64L110.69,144.2a11.05,11.05,0,1,1,13.11-17.8l83.35,61.41a11,11,0,0,1-1.82,18.88L122,246.25A10.94,10.94,0,0,1,117.25,247.32Z" transform="translate(-106.19 -58.85)"/><path class="cls-1" d="M117.25,181.93a11.05,11.05,0,0,1-6.56-20l68.47-50.45L112.5,79.89a11.05,11.05,0,0,1,9.48-20l83.35,39.56a11.05,11.05,0,0,1,1.82,18.89L123.8,179.78A11,11,0,0,1,117.25,181.93Z" transform="translate(-106.19 -58.85)"/><polygon class="cls-3" points="22.11 70.86 22.11 117.61 53.82 94.25 22.11 70.86"/><rect class="cls-4" y="51.26" width="22.11" height="53.89"/><path class="cls-1" d="M128.3,69.91a11.06,11.06,0,1,0-22.11,0V209H128.3Z" transform="translate(-106.19 -58.85)"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
BIN
frontend/src/resources/profile/electrum.png
Normal file
BIN
frontend/src/resources/profile/electrum.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
1
frontend/src/resources/profile/lnbits.svg
Normal file
1
frontend/src/resources/profile/lnbits.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><defs><path id="a" d="M33.2619 148.1667h154.2143v68.7917H33.2619z"/></defs><g fill="#1f2234" aria-label="LNbits" font-family="sans-serif" font-size=".3095" font-weight="400" letter-spacing=".0031" style="line-height:1.25;white-space:pre;shape-inside:url(#a)" transform="matrix(72.4607 0 0 72.4607 -2399.2814 -10741.3589)"><g transform="matrix(.00244 0 0 .00244 33.0708 148.1594)"><circle cx="101.2976" cy="116.4167" r="84.6667" fill="#673ab7" fill-rule="evenodd"/><path fill="#eee" d="M79.1105 71.9667v49.0613h13.3803v40.141l31.2208-53.5213h-17.8404l17.8404-35.681z"/></g><g fill="#eee" font-family="roboto"/></g></svg>
|
After Width: | Height: | Size: 680 B |
@ -4,7 +4,7 @@ var fs = require('fs');
|
|||||||
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||||
let configContent = {};
|
let configContent = {};
|
||||||
|
|
||||||
var PATH = 'dist/mempool/browser/en-US/resources/';
|
var PATH = 'dist/mempool/browser/resources/';
|
||||||
if (process.argv[2] && process.argv[2] === 'dev') {
|
if (process.argv[2] && process.argv[2] === 'dev') {
|
||||||
PATH = 'src/resources/';
|
PATH = 'src/resources/';
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
expires 10m;
|
expires 10m;
|
||||||
}
|
}
|
||||||
location /resources {
|
location /resources {
|
||||||
try_files /$lang/$uri /$lang/$uri/ $uri $uri/ /en-US/$uri @index-redirect;
|
try_files $uri @index-redirect;
|
||||||
expires 1h;
|
expires 1h;
|
||||||
}
|
}
|
||||||
location @index-redirect {
|
location @index-redirect {
|
||||||
@ -27,10 +27,6 @@
|
|||||||
|
|
||||||
# location block using regex are matched in order
|
# location block using regex are matched in order
|
||||||
|
|
||||||
# used to rewrite resources from /<lang>/ 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
|
# 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)/ {
|
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;
|
try_files $uri $uri/ /$1/index.html =404;
|
||||||
|
@ -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-build-all upgrade
|
||||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-kill-all stop
|
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-start-all start
|
||||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-restart-all restart
|
|
||||||
|
|
||||||
|
|
||||||
case $OS in
|
case $OS in
|
||||||
|
@ -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
|
|
@ -5,5 +5,5 @@
|
|||||||
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &
|
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &
|
||||||
|
|
||||||
# hourly liquid asset update
|
# 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
|
||||||
|
|
||||||
|
@ -58,12 +58,6 @@ location = / {
|
|||||||
expires 5m;
|
expires 5m;
|
||||||
}
|
}
|
||||||
|
|
||||||
# used to rewrite resources from /<lang>/ 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 /<lang>/main.f40e91d908a068a2.js forever since they never change
|
# cache /<lang>/main.f40e91d908a068a2.js forever since they never change
|
||||||
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css)) {
|
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css)) {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
@ -84,7 +78,7 @@ location ~ ^/([a-z][a-z])/ {
|
|||||||
|
|
||||||
# cache /resources/** for 1 week since they don't change often
|
# cache /resources/** for 1 week since they don't change often
|
||||||
location /resources {
|
location /resources {
|
||||||
try_files $uri /en-US/$uri /en-US/index.html;
|
try_files $uri /en-US/index.html;
|
||||||
expires 1w;
|
expires 1w;
|
||||||
}
|
}
|
||||||
# cache /main.f40e91d908a068a2.js forever since they never change
|
# cache /main.f40e91d908a068a2.js forever since they never change
|
||||||
|
Loading…
x
Reference in New Issue
Block a user