Small improvements on the mining page UX
- INDEXING_BLOCKS_AMOUNT = 0 disable indexing, INDEXING_BLOCKS_AMOUNT = -1 indexes everything - Show only available timespan in the mining page according to available datas - Change default INDEXING_BLOCKS_AMOUNT to 1100 Don't use unfiltered mysql user input Enable http cache header for mining pools (1 min)
This commit is contained in:
parent
d66bc57165
commit
6ebbc5667d
@ -12,6 +12,7 @@
|
|||||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||||
|
"INDEXING_BLOCKS_AMOUNT": 1100,
|
||||||
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
||||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||||
"EXTERNAL_ASSETS": [
|
"EXTERNAL_ASSETS": [
|
||||||
|
@ -114,7 +114,7 @@ class Blocks {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise<PoolTag> {
|
private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise<PoolTag> {
|
||||||
if (txMinerInfo === undefined) {
|
if (txMinerInfo === undefined || txMinerInfo.vout.length < 1) {
|
||||||
return await poolsRepository.$getUnknownPool();
|
return await poolsRepository.$getUnknownPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +147,9 @@ class Blocks {
|
|||||||
*/
|
*/
|
||||||
public async $generateBlockDatabase() {
|
public async $generateBlockDatabase() {
|
||||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false || // Bitcoin only
|
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false || // Bitcoin only
|
||||||
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT <= 0 || // Indexing must be enabled
|
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === 0 || // Indexing must be enabled
|
||||||
this.blockIndexingStarted === true || // Indexing must not already be in progress
|
!memPool.isInSync() || // We sync the mempool first
|
||||||
!memPool.isInSync() // We sync the mempool first
|
this.blockIndexingStarted === true // Indexing must not already be in progress
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -163,7 +163,13 @@ class Blocks {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let currentBlockHeight = blockchainInfo.blocks;
|
let currentBlockHeight = blockchainInfo.blocks;
|
||||||
const lastBlockToIndex = Math.max(0, currentBlockHeight - config.MEMPOOL.INDEXING_BLOCKS_AMOUNT + 1);
|
|
||||||
|
let indexingBlockAmount = config.MEMPOOL.INDEXING_BLOCKS_AMOUNT;
|
||||||
|
if (indexingBlockAmount <= -1) {
|
||||||
|
indexingBlockAmount = currentBlockHeight + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
|
||||||
|
|
||||||
logger.info(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);
|
logger.info(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);
|
||||||
|
|
||||||
|
@ -118,11 +118,11 @@ class Mempool {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
hasChange = true;
|
hasChange = true;
|
||||||
// if (diff > 0) {
|
if (diff > 0) {
|
||||||
// logger.debug('Fetched transaction ' + txCount + ' / ' + diff);
|
logger.debug('Fetched transaction ' + txCount + ' / ' + diff);
|
||||||
// } else {
|
} else {
|
||||||
// logger.debug('Fetched transaction ' + txCount);
|
logger.debug('Fetched transaction ' + txCount);
|
||||||
// }
|
}
|
||||||
newTransactions.push(transaction);
|
newTransactions.push(transaction);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
|
@ -2,7 +2,6 @@ import { PoolInfo, PoolStats } from '../mempool.interfaces';
|
|||||||
import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository';
|
import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository';
|
||||||
import PoolsRepository from '../repositories/PoolsRepository';
|
import PoolsRepository from '../repositories/PoolsRepository';
|
||||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||||
import BitcoinApi from './bitcoin/bitcoin-api';
|
|
||||||
|
|
||||||
class Mining {
|
class Mining {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -11,14 +10,25 @@ class Mining {
|
|||||||
/**
|
/**
|
||||||
* Generate high level overview of the pool ranks and general stats
|
* Generate high level overview of the pool ranks and general stats
|
||||||
*/
|
*/
|
||||||
public async $getPoolsStats(interval: string = '100 YEAR') : Promise<object> {
|
public async $getPoolsStats(interval: string | null) : Promise<object> {
|
||||||
|
let sqlInterval: string | null = null;
|
||||||
|
switch (interval) {
|
||||||
|
case '24h': sqlInterval = '1 DAY'; break;
|
||||||
|
case '3d': sqlInterval = '3 DAY'; break;
|
||||||
|
case '1w': sqlInterval = '1 WEEK'; break;
|
||||||
|
case '1m': sqlInterval = '1 MONTH'; break;
|
||||||
|
case '3m': sqlInterval = '3 MONTH'; break;
|
||||||
|
case '6m': sqlInterval = '6 MONTH'; break;
|
||||||
|
case '1y': sqlInterval = '1 YEAR'; break;
|
||||||
|
case '2y': sqlInterval = '2 YEAR'; break;
|
||||||
|
case '3y': sqlInterval = '3 YEAR'; break;
|
||||||
|
default: sqlInterval = null; break;
|
||||||
|
}
|
||||||
|
|
||||||
const poolsStatistics = {};
|
const poolsStatistics = {};
|
||||||
|
|
||||||
const blockHeightTip = await bitcoinClient.getBlockCount();
|
const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(sqlInterval);
|
||||||
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
|
const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(sqlInterval);
|
||||||
const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval);
|
|
||||||
const blockCount: number = await BlocksRepository.$blockCount(interval);
|
|
||||||
const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(interval);
|
|
||||||
|
|
||||||
const poolsStats: PoolStats[] = [];
|
const poolsStats: PoolStats[] = [];
|
||||||
let rank = 1;
|
let rank = 1;
|
||||||
@ -40,12 +50,20 @@ class Mining {
|
|||||||
poolsStats.push(poolStat);
|
poolsStats.push(poolStat);
|
||||||
});
|
});
|
||||||
|
|
||||||
poolsStatistics['blockCount'] = blockCount;
|
|
||||||
poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
|
|
||||||
poolsStatistics['pools'] = poolsStats;
|
poolsStatistics['pools'] = poolsStats;
|
||||||
|
|
||||||
|
const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
|
||||||
|
poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime();
|
||||||
|
|
||||||
|
const blockCount: number = await BlocksRepository.$blockCount(sqlInterval);
|
||||||
|
poolsStatistics['blockCount'] = blockCount;
|
||||||
|
|
||||||
|
const blockHeightTip = await bitcoinClient.getBlockCount();
|
||||||
|
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
|
||||||
|
poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
|
||||||
|
|
||||||
return poolsStatistics;
|
return poolsStatistics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Mining();
|
export default new Mining();
|
||||||
|
@ -78,7 +78,7 @@ const defaults: IConfig = {
|
|||||||
'BLOCK_WEIGHT_UNITS': 4000000,
|
'BLOCK_WEIGHT_UNITS': 4000000,
|
||||||
'INITIAL_BLOCKS_AMOUNT': 8,
|
'INITIAL_BLOCKS_AMOUNT': 8,
|
||||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||||
'INDEXING_BLOCKS_AMOUNT': 432, // ~3 days at 10 minutes / block. Set to 0 to disable indexing
|
'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
|
||||||
'PRICE_FEED_UPDATE_INTERVAL': 3600,
|
'PRICE_FEED_UPDATE_INTERVAL': 3600,
|
||||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||||
'EXTERNAL_ASSETS': [
|
'EXTERNAL_ASSETS': [
|
||||||
|
@ -255,7 +255,7 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y'))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y'))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y'))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y'))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y'))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y'))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'pools', routes.$getPools)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools', routes.$getPools)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,14 +72,16 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Count empty blocks for all pools
|
* Count empty blocks for all pools
|
||||||
*/
|
*/
|
||||||
public async $countEmptyBlocks(interval: string = '100 YEAR'): Promise<EmptyBlocks[]> {
|
public async $countEmptyBlocks(interval: string | null): Promise<EmptyBlocks[]> {
|
||||||
const connection = await DB.pool.getConnection();
|
const query = `
|
||||||
const [rows] = await connection.query(`
|
|
||||||
SELECT pool_id as poolId
|
SELECT pool_id as poolId
|
||||||
FROM blocks
|
FROM blocks
|
||||||
WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
|
WHERE tx_count = 1` +
|
||||||
AND tx_count = 1;
|
(interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``)
|
||||||
`);
|
;
|
||||||
|
|
||||||
|
const connection = await DB.pool.getConnection();
|
||||||
|
const [rows] = await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|
||||||
return <EmptyBlocks[]>rows;
|
return <EmptyBlocks[]>rows;
|
||||||
@ -88,17 +90,39 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Get blocks count for a period
|
* Get blocks count for a period
|
||||||
*/
|
*/
|
||||||
public async $blockCount(interval: string = '100 YEAR'): Promise<number> {
|
public async $blockCount(interval: string | null): Promise<number> {
|
||||||
const connection = await DB.pool.getConnection();
|
const query = `
|
||||||
const [rows] = await connection.query(`
|
|
||||||
SELECT count(height) as blockCount
|
SELECT count(height) as blockCount
|
||||||
FROM blocks
|
FROM blocks` +
|
||||||
WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW();
|
(interval != null ? ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``)
|
||||||
`);
|
;
|
||||||
|
|
||||||
|
const connection = await DB.pool.getConnection();
|
||||||
|
const [rows] = await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|
||||||
return <number>rows[0].blockCount;
|
return <number>rows[0].blockCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the oldest indexed block
|
||||||
|
*/
|
||||||
|
public async $oldestBlockTimestamp(): Promise<number> {
|
||||||
|
const connection = await DB.pool.getConnection();
|
||||||
|
const [rows]: any[] = await connection.query(`
|
||||||
|
SELECT blockTimestamp
|
||||||
|
FROM blocks
|
||||||
|
ORDER BY height
|
||||||
|
LIMIT 1;
|
||||||
|
`);
|
||||||
|
connection.release();
|
||||||
|
|
||||||
|
if (rows.length <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <number>rows[0].blockTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BlocksRepository();
|
export default new BlocksRepository();
|
@ -25,17 +25,20 @@ class PoolsRepository {
|
|||||||
/**
|
/**
|
||||||
* Get basic pool info and block count
|
* Get basic pool info and block count
|
||||||
*/
|
*/
|
||||||
public async $getPoolsInfo(interval: string = '100 YEARS'): Promise<PoolInfo[]> {
|
public async $getPoolsInfo(interval: string | null): Promise<PoolInfo[]> {
|
||||||
const connection = await DB.pool.getConnection();
|
const query = `
|
||||||
const [rows] = await connection.query(`
|
|
||||||
SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link
|
SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools on pools.id = pool_id
|
JOIN pools on pools.id = pool_id` +
|
||||||
WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
|
(interval != null ? ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) +
|
||||||
GROUP BY pool_id
|
` GROUP BY pool_id
|
||||||
ORDER BY COUNT(height) DESC;
|
ORDER BY COUNT(height) DESC
|
||||||
`);
|
`;
|
||||||
|
|
||||||
|
const connection = await DB.pool.getConnection();
|
||||||
|
const [rows] = await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|
||||||
return <PoolInfo[]>rows;
|
return <PoolInfo[]>rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,9 +535,9 @@ class Routes {
|
|||||||
public async $getPools(req: Request, res: Response) {
|
public async $getPools(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
let stats = await miningStats.$getPoolsStats(req.query.interval as string);
|
let stats = await miningStats.$getPoolsStats(req.query.interval as string);
|
||||||
// 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 * 300).toUTCString());
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
res.json(stats);
|
res.json(stats);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
@ -13,7 +13,7 @@ __MEMPOOL_RECOMMENDED_FEE_PERCENTILE__=${MEMPOOL_RECOMMENDED_FEE_PERCENTILE:=50}
|
|||||||
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
|
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
|
||||||
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
||||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
||||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=432}
|
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
|
||||||
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600}
|
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600}
|
||||||
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
||||||
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
|
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
"NGINX_HOSTNAME": "127.0.0.1",
|
"NGINX_HOSTNAME": "127.0.0.1",
|
||||||
"NGINX_PORT": "80",
|
"NGINX_PORT": "80",
|
||||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||||
"INDEXING_BLOCKS_AMOUNT": 432,
|
|
||||||
"BASE_MODULE": "mempool",
|
"BASE_MODULE": "mempool",
|
||||||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
|
@ -65,7 +65,7 @@ export function app(locale: string): express.Express {
|
|||||||
server.get('/mempool-block/*', getLocalizedSSR(indexHtml));
|
server.get('/mempool-block/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/address/*', getLocalizedSSR(indexHtml));
|
server.get('/address/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/blocks', getLocalizedSSR(indexHtml));
|
server.get('/blocks', getLocalizedSSR(indexHtml));
|
||||||
server.get('/pools', getLocalizedSSR(indexHtml));
|
server.get('/mining/pools', getLocalizedSSR(indexHtml));
|
||||||
server.get('/graphs', getLocalizedSSR(indexHtml));
|
server.get('/graphs', getLocalizedSSR(indexHtml));
|
||||||
server.get('/liquid', getLocalizedSSR(indexHtml));
|
server.get('/liquid', getLocalizedSSR(indexHtml));
|
||||||
server.get('/liquid/tx/*', getLocalizedSSR(indexHtml));
|
server.get('/liquid/tx/*', getLocalizedSSR(indexHtml));
|
||||||
@ -86,7 +86,7 @@ export function app(locale: string): express.Express {
|
|||||||
server.get('/testnet/mempool-block/*', getLocalizedSSR(indexHtml));
|
server.get('/testnet/mempool-block/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/address/*', getLocalizedSSR(indexHtml));
|
server.get('/testnet/address/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/blocks', getLocalizedSSR(indexHtml));
|
server.get('/testnet/blocks', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/pools', getLocalizedSSR(indexHtml));
|
server.get('/testnet/mining/pools', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/graphs', getLocalizedSSR(indexHtml));
|
server.get('/testnet/graphs', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/api', getLocalizedSSR(indexHtml));
|
server.get('/testnet/api', getLocalizedSSR(indexHtml));
|
||||||
server.get('/testnet/tv', getLocalizedSSR(indexHtml));
|
server.get('/testnet/tv', getLocalizedSSR(indexHtml));
|
||||||
@ -98,7 +98,7 @@ export function app(locale: string): express.Express {
|
|||||||
server.get('/signet/mempool-block/*', getLocalizedSSR(indexHtml));
|
server.get('/signet/mempool-block/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/address/*', getLocalizedSSR(indexHtml));
|
server.get('/signet/address/*', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/blocks', getLocalizedSSR(indexHtml));
|
server.get('/signet/blocks', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/pools', getLocalizedSSR(indexHtml));
|
server.get('/signet/mining/pools', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/graphs', getLocalizedSSR(indexHtml));
|
server.get('/signet/graphs', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/api', getLocalizedSSR(indexHtml));
|
server.get('/signet/api', getLocalizedSSR(indexHtml));
|
||||||
server.get('/signet/tv', getLocalizedSSR(indexHtml));
|
server.get('/signet/tv', getLocalizedSSR(indexHtml));
|
||||||
|
@ -60,7 +60,7 @@ let routes: Routes = [
|
|||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'pools',
|
path: 'mining/pools',
|
||||||
component: PoolRankingComponent,
|
component: PoolRankingComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -147,6 +147,10 @@ let routes: Routes = [
|
|||||||
path: 'blocks',
|
path: 'blocks',
|
||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/pools',
|
||||||
|
component: PoolRankingComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'graphs',
|
path: 'graphs',
|
||||||
component: StatisticsComponent,
|
component: StatisticsComponent,
|
||||||
@ -225,6 +229,10 @@ let routes: Routes = [
|
|||||||
path: 'blocks',
|
path: 'blocks',
|
||||||
component: LatestBlocksComponent,
|
component: LatestBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/pools',
|
||||||
|
component: PoolRankingComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'graphs',
|
path: 'graphs',
|
||||||
component: StatisticsComponent,
|
component: StatisticsComponent,
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" id="btn-pools">
|
<li class="nav-item" routerLinkActive="active" id="btn-pools">
|
||||||
<a class="nav-link" [routerLink]="['/mining/pools' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="master-page.pools" title="Pools"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/mining/pools' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'hammer']" [fixedWidth]="true" i18n-title="master-page.mining-pools" title="Mining Pools"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
<li class="nav-item" routerLinkActive="active" id="btn-graphs">
|
||||||
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/graphs' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'chart-area']" [fixedWidth]="true" i18n-title="master-page.graphs" title="Graphs"></fa-icon></a>
|
||||||
|
@ -7,37 +7,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-header mb-0 mb-lg-4">
|
<div class="card-header mb-0 mb-lg-4">
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(miningStatsObservable$ | async) as miningStats">
|
||||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1">
|
||||||
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
|
<input ngbButton type="radio" [value]="'24h'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="24h"> 24h
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 3">
|
||||||
|
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3d"> 3D
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 7">
|
||||||
|
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1w"> 1W
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 30">
|
||||||
|
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1m"> 1M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 90">
|
||||||
|
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3m"> 3M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 180">
|
||||||
|
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="6m"> 6M
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 365">
|
||||||
|
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="1y"> 1Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 730">
|
||||||
|
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="2y"> 2Y
|
||||||
|
</label>
|
||||||
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="miningStats.availableTimespanDay >= 1095">
|
||||||
|
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="3y"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/pools' | relativeUrl]" fragment="3d"> 3D
|
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/mining/pools' | relativeUrl]" fragment="all"> ALL
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/pools' | relativeUrl]" fragment="1w"> 1W
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/pools' | relativeUrl]" fragment="1m"> 1M
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/pools' | relativeUrl]" fragment="3m"> 3M
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/pools' | relativeUrl]" fragment="6m"> 6M
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/pools' | relativeUrl]" fragment="1y"> 1Y
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/pools' | relativeUrl]" fragment="2y"> 2Y
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/pools' | relativeUrl]" fragment="3y"> 3Y
|
|
||||||
</label>
|
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
|
||||||
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/pools' | relativeUrl]" fragment="all"> ALL
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -46,31 +46,31 @@
|
|||||||
<table class="table table-borderless text-center pools-table" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
<table class="table table-borderless text-center pools-table" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="d-none d-md-block" i18n="latest-blocks.height">Rank</th>
|
<th class="d-none d-md-block" i18n="mining.rank">Rank</th>
|
||||||
<th class=""></th>
|
<th class=""></th>
|
||||||
<th class="" i18n="latest-blocks.poolName">Name</th>
|
<th class="" i18n="mining.pool-name">Name</th>
|
||||||
<th class="" *ngIf="this.poolsWindowPreference === '1d'" i18n="latest-blocks.timestamp">Hashrate</th>
|
<th class="" *ngIf="this.poolsWindowPreference === '24h'" i18n="mining.hashrate">Hashrate</th>
|
||||||
<th class="" i18n="latest-blocks.mined">Blocks</th>
|
<th class="" i18n="master-page.blocks">Blocks</th>
|
||||||
<th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks</th>
|
<th class="d-none d-md-block" i18n="mining.empty-blocks">Empty Blocks</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="(miningStatsObservable$ | async) as miningStats">
|
<tbody *ngIf="(miningStatsObservable$ | async) as miningStats">
|
||||||
<tr>
|
|
||||||
<td class="d-none d-md-block">-</td>
|
|
||||||
<td class="text-right"><img width="25" height="25" src="./resources/mining-pools/default.svg"></td>
|
|
||||||
<td class="">All miners</td>
|
|
||||||
<td class="" *ngIf="this.poolsWindowPreference === '1d'">{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
|
||||||
<td class="">{{ miningStats.blockCount }}</td>
|
|
||||||
<td class="d-none d-md-block">{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</td>
|
|
||||||
</tr>
|
|
||||||
<tr *ngFor="let pool of miningStats.pools">
|
<tr *ngFor="let pool of miningStats.pools">
|
||||||
<td class="d-none d-md-block">{{ pool.rank }}</td>
|
<td class="d-none d-md-block">{{ pool.rank }}</td>
|
||||||
<td class="text-right"><img width="25" height="25" src="{{ pool.logo }}" onError="this.src = './resources/mining-pools/default.svg'"></td>
|
<td class="text-right"><img width="25" height="25" src="{{ pool.logo }}" onError="this.src = './resources/mining-pools/default.svg'"></td>
|
||||||
<td class=""><a target="#" href="{{ pool.link }}">{{ pool.name }}</a></td>
|
<td class="">{{ pool.name }}</td>
|
||||||
<td class="" *ngIf="this.poolsWindowPreference === '1d'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
<td class="" *ngIf="this.poolsWindowPreference === '24h'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
|
||||||
<td class="">{{ pool['blockText'] }}</td>
|
<td class="">{{ pool['blockText'] }}</td>
|
||||||
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
<td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr style="border-top: 1px solid #555">
|
||||||
|
<td class="d-none d-md-block">-</td>
|
||||||
|
<td class="text-right"><img width="25" height="25" src="./resources/mining-pools/default.svg"></td>
|
||||||
|
<td class="" i18n="mining.all-miners"><b>All miners</b></td>
|
||||||
|
<td class="" *ngIf="this.poolsWindowPreference === '24h'"><b>{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</b></td>
|
||||||
|
<td class=""><b>{{ miningStats.blockCount }}</b></td>
|
||||||
|
<td class="d-none d-md-block"><b>{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</b></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { EChartsOption } from 'echarts';
|
import { EChartsOption } from 'echarts';
|
||||||
import { combineLatest, Observable, of } from 'rxjs';
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
import { catchError, map, skip, startWith, switchMap, tap } from 'rxjs/operators';
|
import { catchError, map, share, skip, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SinglePoolStats } from 'src/app/interfaces/node-api.interface';
|
import { SinglePoolStats } from 'src/app/interfaces/node-api.interface';
|
||||||
import { StorageService } from '../..//services/storage.service';
|
import { StorageService } from '../..//services/storage.service';
|
||||||
import { MiningService, MiningStats } from '../../services/mining.service';
|
import { MiningService, MiningStats } from '../../services/mining.service';
|
||||||
@ -39,7 +39,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
) {
|
) {
|
||||||
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1d';
|
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '24h';
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.poolsWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.poolsWindowPreference);
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
return this.miningService.getMiningStats(this.getSQLInterval(this.poolsWindowPreference))
|
return this.miningService.getMiningStats(this.poolsWindowPreference)
|
||||||
.pipe(
|
.pipe(
|
||||||
catchError((e) => of(this.getEmptyMiningStat()))
|
catchError((e) => of(this.getEmptyMiningStat()))
|
||||||
);
|
);
|
||||||
@ -79,7 +79,8 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
tap(data => {
|
tap(data => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.prepareChartOptions(data);
|
this.prepareChartOptions(data);
|
||||||
})
|
}),
|
||||||
|
share()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
color: "#FFFFFF",
|
color: "#FFFFFF",
|
||||||
},
|
},
|
||||||
formatter: () => {
|
formatter: () => {
|
||||||
if (this.poolsWindowPreference === '1d') {
|
if (this.poolsWindowPreference === '24h') {
|
||||||
return `<u><b>${pool.name} (${pool.share}%)</b></u><br>` +
|
return `<u><b>${pool.name} (${pool.share}%)</b></u><br>` +
|
||||||
pool.lastEstimatedHashrate.toString() + ' PH/s' +
|
pool.lastEstimatedHashrate.toString() + ' PH/s' +
|
||||||
`<br>` + pool.blockCount.toString() + ` blocks`;
|
`<br>` + pool.blockCount.toString() + ` blocks`;
|
||||||
@ -132,10 +133,16 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(miningStats) {
|
prepareChartOptions(miningStats) {
|
||||||
|
let network = this.stateService.network;
|
||||||
|
if (network === '') {
|
||||||
|
network = 'bitcoin';
|
||||||
|
}
|
||||||
|
network = network.charAt(0).toUpperCase() + network.slice(1);
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
title: {
|
title: {
|
||||||
text: (this.poolsWindowPreference === '1d') ? 'Hashrate distribution' : 'Block distribution',
|
text: $localize`:@@mining.pool-chart-title:${network}:NETWORK: mining pools share`,
|
||||||
subtext: (this.poolsWindowPreference === '1d') ? 'Estimated from the # of blocks mined' : null,
|
subtext: $localize`:@@mining.pool-chart-sub-title:Estimated from the # of blocks mined`,
|
||||||
left: 'center',
|
left: 'center',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#FFF',
|
color: '#FFF',
|
||||||
@ -187,21 +194,6 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getSQLInterval(uiInterval: string) {
|
|
||||||
switch (uiInterval) {
|
|
||||||
case '1d': return '1 DAY';
|
|
||||||
case '3d': return '3 DAY';
|
|
||||||
case '1w': return '1 WEEK';
|
|
||||||
case '1m': return '1 MONTH';
|
|
||||||
case '3m': return '3 MONTH';
|
|
||||||
case '6m': return '6 MONTH';
|
|
||||||
case '1y': return '1 YEAR';
|
|
||||||
case '2y': return '2 YEAR';
|
|
||||||
case '3y': return '3 YEAR';
|
|
||||||
default: return '1000 YEAR';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default mining stats if something goes wrong
|
* Default mining stats if something goes wrong
|
||||||
*/
|
*/
|
||||||
@ -212,6 +204,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
|||||||
totalEmptyBlock: 0,
|
totalEmptyBlock: 0,
|
||||||
totalEmptyBlockRatio: '',
|
totalEmptyBlockRatio: '',
|
||||||
pools: [],
|
pools: [],
|
||||||
|
availableTimespanDay: 0,
|
||||||
miningUnits: {
|
miningUnits: {
|
||||||
hashrateDivider: 1,
|
hashrateDivider: 1,
|
||||||
hashrateUnit: '',
|
hashrateUnit: '',
|
||||||
|
@ -68,6 +68,7 @@ export interface SinglePoolStats {
|
|||||||
export interface PoolsStats {
|
export interface PoolsStats {
|
||||||
blockCount: number;
|
blockCount: number;
|
||||||
lastEstimatedHashrate: number;
|
lastEstimatedHashrate: number;
|
||||||
|
oldestIndexedBlockTimestamp: number;
|
||||||
pools: SinglePoolStats[];
|
pools: SinglePoolStats[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,8 +121,11 @@ export class ApiService {
|
|||||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||||
}
|
}
|
||||||
|
|
||||||
listPools$(interval: string) : Observable<PoolsStats> {
|
listPools$(interval: string | null) : Observable<PoolsStats> {
|
||||||
const params = new HttpParams().set('interval', interval);
|
let params = {};
|
||||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/pools', {params});
|
if (interval) {
|
||||||
|
params = new HttpParams().set('interval', interval);
|
||||||
|
}
|
||||||
|
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/mining/pools', {params});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ export interface MiningStats {
|
|||||||
totalEmptyBlockRatio: string;
|
totalEmptyBlockRatio: string;
|
||||||
pools: SinglePoolStats[];
|
pools: SinglePoolStats[];
|
||||||
miningUnits: MiningUnits;
|
miningUnits: MiningUnits;
|
||||||
|
availableTimespanDay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -80,6 +81,10 @@ export class MiningService {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const availableTimespanDay = (
|
||||||
|
(new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp / 1000)
|
||||||
|
) / 3600 / 24;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lastEstimatedHashrate: (stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
|
lastEstimatedHashrate: (stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
|
||||||
blockCount: stats.blockCount,
|
blockCount: stats.blockCount,
|
||||||
@ -87,6 +92,7 @@ export class MiningService {
|
|||||||
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
||||||
pools: poolsStats,
|
pools: poolsStats,
|
||||||
miningUnits: miningUnits,
|
miningUnits: miningUnits,
|
||||||
|
availableTimespanDay: availableTimespanDay,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
|||||||
export class StorageService {
|
export class StorageService {
|
||||||
constructor(private router: Router, private route: ActivatedRoute) {
|
constructor(private router: Router, private route: ActivatedRoute) {
|
||||||
this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
|
this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
|
||||||
this.setDefaultValueIfNeeded('poolsWindowPreference', '1d');
|
this.setDefaultValueIfNeeded('poolsWindowPreference', '1w');
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultValueIfNeeded(key: string, defaultValue: string) {
|
setDefaultValueIfNeeded(key: string, defaultValue: string) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user