Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
897049903b |
No files matched your search
+29
-31
@@ -2,93 +2,91 @@ name: CI Pipeline for the Backend and Frontend
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, review_requested, synchronize]
|
||||
types: [ opened, review_requested, synchronize ]
|
||||
env:
|
||||
NODE_VERSION: 16.15.0
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16.16.0", "18.5.0"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
flavor: ['dev', 'prod']
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }}
|
||||
name: Backend (${{ matrix.flavor }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.node }}/${{ matrix.flavor }}
|
||||
path: ${{ matrix.flavor }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install
|
||||
if: ${{ matrix.flavor == 'dev'}}
|
||||
run: npm ci
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
|
||||
working-directory: ${{ matrix.flavor }}/backend
|
||||
|
||||
- name: Install (Prod dependencies only)
|
||||
if: ${{ matrix.flavor == 'prod'}}
|
||||
run: npm ci --omit=dev --omit=optional
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
|
||||
run: npm ci --prod --no-optional
|
||||
working-directory: ${{ matrix.flavor }}/backend
|
||||
|
||||
- name: Lint
|
||||
- name: Lint
|
||||
if: ${{ matrix.flavor == 'dev'}}
|
||||
run: npm run lint
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
|
||||
working-directory: ${{ matrix.flavor }}/backend
|
||||
|
||||
# - name: Test
|
||||
# run: npm run test
|
||||
# - name: Test
|
||||
# run: npm run test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend
|
||||
working-directory: ${{ matrix.flavor }}/backend
|
||||
|
||||
frontend:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16.15.0", "18.5.0"]
|
||||
flavor: ["dev", "prod"]
|
||||
fail-fast: false
|
||||
runs-on: "ubuntu-latest"
|
||||
flavor: ['dev', 'prod']
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }}
|
||||
name: Frontend (${{ matrix.flavor }})
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.node }}/${{ matrix.flavor }}
|
||||
path: ${{ matrix.flavor }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install (Prod dependencies only)
|
||||
run: npm ci --omit=dev --omit=optional
|
||||
run: npm ci --prod --no-optional
|
||||
if: ${{ matrix.flavor == 'prod'}}
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
|
||||
working-directory: ${{ matrix.flavor }}/frontend
|
||||
|
||||
- name: Install
|
||||
if: ${{ matrix.flavor == 'dev'}}
|
||||
run: npm ci
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
|
||||
working-directory: ${{ matrix.flavor }}/frontend
|
||||
|
||||
- name: Lint
|
||||
if: ${{ matrix.flavor == 'dev'}}
|
||||
run: npm run lint
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
|
||||
working-directory: ${{ matrix.flavor }}/frontend
|
||||
|
||||
# - name: Test
|
||||
# - name: Test
|
||||
# run: npm run test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend
|
||||
working-directory: ${{ matrix.flavor }}/frontend
|
||||
@@ -20,8 +20,7 @@
|
||||
"EXTERNAL_MAX_RETRY": 1,
|
||||
"EXTERNAL_RETRY_INTERVAL": 0,
|
||||
"USER_AGENT": "mempool",
|
||||
"STDOUT_LOG_MIN_PRIORITY": "debug",
|
||||
"AUTOMATIC_BLOCK_REINDEXING": false
|
||||
"STDOUT_LOG_MIN_PRIORITY": "debug"
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
|
||||
Generated
+26
@@ -33,6 +33,32 @@
|
||||
"prettier": "^2.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
|
||||
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
|
||||
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
|
||||
|
||||
+41
-35
@@ -168,7 +168,7 @@ class Blocks {
|
||||
blockExtended.extras.avgFeeRate = stats.avgfeerate;
|
||||
}
|
||||
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
||||
if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
|
||||
let pool: PoolTag;
|
||||
if (blockExtended.extras?.coinbaseTx !== undefined) {
|
||||
pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx);
|
||||
@@ -405,7 +405,7 @@ class Blocks {
|
||||
|
||||
if (blockHeightTip >= 2016) {
|
||||
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
|
||||
const previousPeriodBlock = await bitcoinClient.getBlock(previousPeriodBlockHash)
|
||||
const previousPeriodBlock = await bitcoinApi.$getBlock(previousPeriodBlockHash);
|
||||
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
|
||||
logger.debug(`Initial difficulty adjustment data set.`);
|
||||
}
|
||||
@@ -527,15 +527,13 @@ class Blocks {
|
||||
}
|
||||
}
|
||||
|
||||
let block = await bitcoinClient.getBlock(hash);
|
||||
const block = await bitcoinApi.$getBlock(hash);
|
||||
|
||||
// Not Bitcoin network, return the block as it
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
|
||||
return block;
|
||||
}
|
||||
|
||||
block = prepareBlock(block);
|
||||
|
||||
// Bitcoin network, add our custom data on top
|
||||
const transactions = await this.$getTransactionsExtended(hash, block.height, true);
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
@@ -579,39 +577,47 @@ class Blocks {
|
||||
}
|
||||
|
||||
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository.$mostRecentBlockHeight();
|
||||
const returnBlocks: BlockExtended[] = [];
|
||||
try {
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight();
|
||||
const returnBlocks: BlockExtended[] = [];
|
||||
|
||||
if (currentHeight < 0) {
|
||||
return returnBlocks;
|
||||
}
|
||||
|
||||
// Check if block height exist in local cache to skip the hash lookup
|
||||
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
|
||||
let startFromHash: string | null = null;
|
||||
if (blockByHeight) {
|
||||
startFromHash = blockByHeight.id;
|
||||
} else if (!Common.indexingEnabled()) {
|
||||
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
|
||||
}
|
||||
|
||||
let nextHash = startFromHash;
|
||||
for (let i = 0; i < limit && currentHeight >= 0; i++) {
|
||||
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
||||
if (block) {
|
||||
returnBlocks.push(block);
|
||||
} else if (Common.indexingEnabled()) {
|
||||
block = await this.$indexBlock(currentHeight);
|
||||
returnBlocks.push(block);
|
||||
} else if (nextHash != null) {
|
||||
block = prepareBlock(await bitcoinClient.getBlock(nextHash));
|
||||
nextHash = block.previousblockhash;
|
||||
returnBlocks.push(block);
|
||||
if (currentHeight < 0) {
|
||||
return returnBlocks;
|
||||
}
|
||||
currentHeight--;
|
||||
}
|
||||
|
||||
return returnBlocks;
|
||||
if (currentHeight === 0 && Common.indexingEnabled()) {
|
||||
currentHeight = await blocksRepository.$mostRecentBlockHeight();
|
||||
}
|
||||
|
||||
// Check if block height exist in local cache to skip the hash lookup
|
||||
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
|
||||
let startFromHash: string | null = null;
|
||||
if (blockByHeight) {
|
||||
startFromHash = blockByHeight.id;
|
||||
} else if (!Common.indexingEnabled()) {
|
||||
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
|
||||
}
|
||||
|
||||
let nextHash = startFromHash;
|
||||
for (let i = 0; i < limit && currentHeight >= 0; i++) {
|
||||
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
||||
if (block) {
|
||||
returnBlocks.push(block);
|
||||
} else if (Common.indexingEnabled()) {
|
||||
block = await this.$indexBlock(currentHeight);
|
||||
returnBlocks.push(block);
|
||||
} else if (nextHash != null) {
|
||||
block = prepareBlock(await bitcoinApi.$getBlock(nextHash));
|
||||
nextHash = block.previousblockhash;
|
||||
returnBlocks.push(block);
|
||||
}
|
||||
currentHeight--;
|
||||
}
|
||||
|
||||
return returnBlocks;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public getLastDifficultyAdjustmentTime(): number {
|
||||
|
||||
+71
-75
@@ -173,25 +173,26 @@ class Mining {
|
||||
*/
|
||||
public async $generatePoolHashrateHistory(): Promise<void> {
|
||||
const now = new Date();
|
||||
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
|
||||
|
||||
// Run only if:
|
||||
// * lastestRunDate is set to 0 (node backend restart, reorg)
|
||||
// * we started a new week (around Monday midnight)
|
||||
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
|
||||
if (!runIndexing) {
|
||||
return;
|
||||
try {
|
||||
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
|
||||
|
||||
// Run only if:
|
||||
// * lastestRunDate is set to 0 (node backend restart, reorg)
|
||||
// * we started a new week (around Monday midnight)
|
||||
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
|
||||
if (!runIndexing) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
||||
|
||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
||||
const genesisTimestamp = genesisBlock.time * 1000;
|
||||
|
||||
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
|
||||
const hashrates: any[] = [];
|
||||
|
||||
const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||
|
||||
const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7));
|
||||
const lastMondayMidnight = this.getDateMidnight(lastMonday);
|
||||
let toTimestamp = lastMondayMidnight.getTime();
|
||||
@@ -206,7 +207,7 @@ class Mining {
|
||||
logger.debug(`Indexing weekly mining pool hashrate`);
|
||||
loadingIndicators.setProgress('weekly-hashrate-indexing', 0);
|
||||
|
||||
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) {
|
||||
while (toTimestamp > genesisTimestamp) {
|
||||
const fromTimestamp = toTimestamp - 604800000;
|
||||
|
||||
// Skip already indexed weeks
|
||||
@@ -216,6 +217,14 @@ class Mining {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we have blocks for the previous week (which mean that the week
|
||||
// we are currently indexing has complete data)
|
||||
const blockStatsPreviousWeek: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
||||
null, (fromTimestamp - 604800000) / 1000, (toTimestamp - 604800000) / 1000);
|
||||
if (blockStatsPreviousWeek.blockCount === 0) { // We are done indexing
|
||||
break;
|
||||
}
|
||||
|
||||
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
||||
null, fromTimestamp / 1000, toTimestamp / 1000);
|
||||
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
|
||||
@@ -223,28 +232,26 @@ class Mining {
|
||||
|
||||
let pools = await PoolsRepository.$getPoolsInfoBetween(fromTimestamp / 1000, toTimestamp / 1000);
|
||||
const totalBlocks = pools.reduce((acc, pool) => acc + pool.blockCount, 0);
|
||||
if (totalBlocks > 0) {
|
||||
pools = pools.map((pool: any) => {
|
||||
pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate;
|
||||
pool.share = (pool.blockCount / totalBlocks);
|
||||
return pool;
|
||||
pools = pools.map((pool: any) => {
|
||||
pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate;
|
||||
pool.share = (pool.blockCount / totalBlocks);
|
||||
return pool;
|
||||
});
|
||||
|
||||
for (const pool of pools) {
|
||||
hashrates.push({
|
||||
hashrateTimestamp: toTimestamp / 1000,
|
||||
avgHashrate: pool['hashrate'],
|
||||
poolId: pool.poolId,
|
||||
share: pool['share'],
|
||||
type: 'weekly',
|
||||
});
|
||||
|
||||
for (const pool of pools) {
|
||||
hashrates.push({
|
||||
hashrateTimestamp: toTimestamp / 1000,
|
||||
avgHashrate: pool['hashrate'] ,
|
||||
poolId: pool.poolId,
|
||||
share: pool['share'],
|
||||
type: 'weekly',
|
||||
});
|
||||
}
|
||||
|
||||
newlyIndexed += hashrates.length;
|
||||
await HashratesRepository.$saveHashrates(hashrates);
|
||||
hashrates.length = 0;
|
||||
}
|
||||
|
||||
newlyIndexed += hashrates.length;
|
||||
await HashratesRepository.$saveHashrates(hashrates);
|
||||
hashrates.length = 0;
|
||||
|
||||
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
|
||||
if (elapsedSeconds > 1) {
|
||||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
@@ -278,19 +285,20 @@ class Mining {
|
||||
* [INDEXING] Generate daily hashrate data
|
||||
*/
|
||||
public async $generateNetworkHashrateHistory(): Promise<void> {
|
||||
// We only run this once a day around midnight
|
||||
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
|
||||
const now = new Date().getUTCDate();
|
||||
if (now === latestRunDate) {
|
||||
return;
|
||||
try {
|
||||
// We only run this once a day around midnight
|
||||
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
|
||||
const now = new Date().getUTCDate();
|
||||
if (now === latestRunDate) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
||||
|
||||
try {
|
||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
||||
const genesisTimestamp = genesisBlock.time * 1000;
|
||||
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
|
||||
const indexedTimestamp = (await HashratesRepository.$getNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
|
||||
const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||
const lastMidnight = this.getDateMidnight(new Date());
|
||||
let toTimestamp = Math.round(lastMidnight.getTime());
|
||||
const hashrates: any[] = [];
|
||||
@@ -305,7 +313,7 @@ class Mining {
|
||||
logger.debug(`Indexing daily network hashrate`);
|
||||
loadingIndicators.setProgress('daily-hashrate-indexing', 0);
|
||||
|
||||
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) {
|
||||
while (toTimestamp > genesisTimestamp) {
|
||||
const fromTimestamp = toTimestamp - 86400000;
|
||||
|
||||
// Skip already indexed days
|
||||
@@ -315,9 +323,17 @@ class Mining {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we have blocks for the previous day (which mean that the day
|
||||
// we are currently indexing has complete data)
|
||||
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
||||
null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000);
|
||||
if (blockStatsPreviousDay.blockCount === 0 && config.MEMPOOL.NETWORK === 'mainnet') { // We are done indexing
|
||||
break;
|
||||
}
|
||||
|
||||
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
||||
null, fromTimestamp / 1000, toTimestamp / 1000);
|
||||
const lastBlockHashrate = blockStats.blockCount === 0 ? 0 : await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
|
||||
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
|
||||
blockStats.lastBlockHeight);
|
||||
|
||||
hashrates.push({
|
||||
@@ -353,7 +369,7 @@ class Mining {
|
||||
}
|
||||
|
||||
// Add genesis block manually
|
||||
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && !indexedTimestamp.includes(genesisTimestamp / 1000)) {
|
||||
if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
|
||||
hashrates.push({
|
||||
hashrateTimestamp: genesisTimestamp / 1000,
|
||||
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
|
||||
@@ -389,37 +405,27 @@ class Mining {
|
||||
}
|
||||
|
||||
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
|
||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
||||
let currentDifficulty = genesisBlock.difficulty;
|
||||
|
||||
let currentDifficulty = 0;
|
||||
let totalIndexed = 0;
|
||||
|
||||
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && indexedHeights[0] !== true) {
|
||||
if (indexedHeights[0] === false) {
|
||||
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||
time: genesisBlock.time,
|
||||
time: 1231006505,
|
||||
height: 0,
|
||||
difficulty: currentDifficulty,
|
||||
difficulty: 1.0,
|
||||
adjustment: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
|
||||
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
|
||||
currentDifficulty = oldestConsecutiveBlock.difficulty;
|
||||
}
|
||||
|
||||
let totalBlockChecked = 0;
|
||||
let timer = new Date().getTime() / 1000;
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block.difficulty !== currentDifficulty) {
|
||||
if (indexedHeights[block.height] === true) { // Already indexed
|
||||
if (block.height >= oldestConsecutiveBlock.height) {
|
||||
currentDifficulty = block.difficulty;
|
||||
}
|
||||
if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
|
||||
currentDifficulty = block.difficulty;
|
||||
continue;
|
||||
}
|
||||
|
||||
let adjustment = block.difficulty / currentDifficulty;
|
||||
let adjustment = block.difficulty / Math.max(1, currentDifficulty);
|
||||
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
|
||||
|
||||
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||
@@ -430,17 +436,7 @@ class Mining {
|
||||
});
|
||||
|
||||
totalIndexed++;
|
||||
if (block.height >= oldestConsecutiveBlock.height) {
|
||||
currentDifficulty = block.difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
totalBlockChecked++;
|
||||
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
|
||||
if (elapsedSeconds > 5) {
|
||||
const progress = Math.round(totalBlockChecked / blocks.length * 100);
|
||||
logger.info(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`);
|
||||
timer = new Date().getTime() / 1000;
|
||||
currentDifficulty = block.difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import config from '../config';
|
||||
import BlocksRepository from '../repositories/BlocksRepository';
|
||||
|
||||
interface Pool {
|
||||
name: string;
|
||||
@@ -33,6 +32,7 @@ class PoolsParser {
|
||||
// First we save every entries without paying attention to pool duplication
|
||||
const poolsDuplicated: Pool[] = [];
|
||||
|
||||
logger.debug('Parse coinbase_tags');
|
||||
const coinbaseTags = Object.entries(poolsJson['coinbase_tags']);
|
||||
for (let i = 0; i < coinbaseTags.length; ++i) {
|
||||
poolsDuplicated.push({
|
||||
@@ -43,6 +43,7 @@ class PoolsParser {
|
||||
'slug': ''
|
||||
});
|
||||
}
|
||||
logger.debug('Parse payout_addresses');
|
||||
const addressesTags = Object.entries(poolsJson['payout_addresses']);
|
||||
for (let i = 0; i < addressesTags.length; ++i) {
|
||||
poolsDuplicated.push({
|
||||
@@ -55,6 +56,7 @@ class PoolsParser {
|
||||
}
|
||||
|
||||
// Then, we find unique mining pool names
|
||||
logger.debug('Identify unique mining pools');
|
||||
const poolNames: string[] = [];
|
||||
for (let i = 0; i < poolsDuplicated.length; ++i) {
|
||||
if (poolNames.indexOf(poolsDuplicated[i].name) === -1) {
|
||||
@@ -117,15 +119,8 @@ class PoolsParser {
|
||||
'slug': slug
|
||||
};
|
||||
|
||||
const existingPool = existingPools.find((pool) => pool.name === poolNames[i]);
|
||||
if (existingPool !== undefined) {
|
||||
// Check if any data was actually updated
|
||||
const equals = (a, b) =>
|
||||
a.length === b.length &&
|
||||
a.every((v, i) => v === b[i]);
|
||||
if (!equals(JSON.parse(existingPool.addresses), poolObj.addresses) || !equals(JSON.parse(existingPool.regexes), poolObj.regexes)) {
|
||||
finalPoolDataUpdate.push(poolObj);
|
||||
}
|
||||
if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) {
|
||||
finalPoolDataUpdate.push(poolObj);
|
||||
} else {
|
||||
logger.debug(`Add '${finalPoolName}' mining pool`);
|
||||
finalPoolDataAdd.push(poolObj);
|
||||
@@ -145,51 +140,40 @@ class PoolsParser {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalPoolDataAdd.length > 0 || finalPoolDataUpdate.length > 0) {
|
||||
logger.debug(`Update pools table now`);
|
||||
logger.debug(`Update pools table now`);
|
||||
|
||||
// Add new mining pools into the database
|
||||
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
|
||||
for (let i = 0; i < finalPoolDataAdd.length; ++i) {
|
||||
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
|
||||
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}',
|
||||
${JSON.stringify(finalPoolDataAdd[i].slug)}),`;
|
||||
}
|
||||
queryAdd = queryAdd.slice(0, -1) + ';';
|
||||
// Add new mining pools into the database
|
||||
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
|
||||
for (let i = 0; i < finalPoolDataAdd.length; ++i) {
|
||||
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
|
||||
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}',
|
||||
${JSON.stringify(finalPoolDataAdd[i].slug)}),`;
|
||||
}
|
||||
queryAdd = queryAdd.slice(0, -1) + ';';
|
||||
|
||||
// Updated existing mining pools in the database
|
||||
const updateQueries: string[] = [];
|
||||
for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
|
||||
updateQueries.push(`
|
||||
UPDATE pools
|
||||
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
|
||||
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}',
|
||||
slug='${finalPoolDataUpdate[i].slug}'
|
||||
WHERE name='${finalPoolDataUpdate[i].name}'
|
||||
;`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$deleteBlocskToReindex(finalPoolDataUpdate);
|
||||
|
||||
if (finalPoolDataAdd.length > 0) {
|
||||
await DB.query({ sql: queryAdd, timeout: 120000 });
|
||||
}
|
||||
for (const query of updateQueries) {
|
||||
await DB.query({ sql: query, timeout: 120000 });
|
||||
}
|
||||
await this.insertUnknownPool();
|
||||
logger.info('Mining pools.json import completed');
|
||||
} catch (e) {
|
||||
logger.err(`Cannot import pools in the database`);
|
||||
throw e;
|
||||
}
|
||||
// Updated existing mining pools in the database
|
||||
const updateQueries: string[] = [];
|
||||
for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
|
||||
updateQueries.push(`
|
||||
UPDATE pools
|
||||
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
|
||||
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}',
|
||||
slug='${finalPoolDataUpdate[i].slug}'
|
||||
WHERE name='${finalPoolDataUpdate[i].name}'
|
||||
;`);
|
||||
}
|
||||
|
||||
try {
|
||||
if (finalPoolDataAdd.length > 0) {
|
||||
await DB.query({ sql: queryAdd, timeout: 120000 });
|
||||
}
|
||||
for (const query of updateQueries) {
|
||||
await DB.query({ sql: query, timeout: 120000 });
|
||||
}
|
||||
await this.insertUnknownPool();
|
||||
logger.info('Mining pools.json import completed');
|
||||
} catch (e) {
|
||||
logger.err(`Cannot insert unknown pool in the database`);
|
||||
logger.err(`Cannot import pools in the database`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -217,36 +201,6 @@ class PoolsParser {
|
||||
logger.err('Unable to insert "Unknown" mining pool');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete blocks which needs to be reindexed
|
||||
*/
|
||||
private async $deleteBlocskToReindex(finalPoolDataUpdate: any[]) {
|
||||
if (config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blockCount = await BlocksRepository.$blockCount(null, null);
|
||||
if (blockCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const updatedPool of finalPoolDataUpdate) {
|
||||
const [pool]: any[] = await DB.query(`SELECT id, name from pools where slug = "${updatedPool.slug}"`);
|
||||
if (pool.length > 0) {
|
||||
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${pool[0].id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore early days of Bitcoin as there were not mining pool yet
|
||||
logger.notice('Deleting blocks with unknown mining pool from height 130635 for future re-indexing');
|
||||
const [unknownPool] = await DB.query(`SELECT id from pools where slug = "unknown"`);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${unknownPool[0].id} AND height > 130635`);
|
||||
|
||||
logger.notice('Truncating hashrates for future re-indexing');
|
||||
await DB.query(`DELETE FROM hashrates`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new PoolsParser();
|
||||
@@ -23,7 +23,6 @@ interface IConfig {
|
||||
EXTERNAL_RETRY_INTERVAL: number;
|
||||
USER_AGENT: string;
|
||||
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
|
||||
AUTOMATIC_BLOCK_REINDEXING: boolean;
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
@@ -114,7 +113,6 @@ const defaults: IConfig = {
|
||||
'EXTERNAL_RETRY_INTERVAL': 0,
|
||||
'USER_AGENT': 'mempool',
|
||||
'STDOUT_LOG_MIN_PRIORITY': 'debug',
|
||||
'AUTOMATIC_BLOCK_REINDEXING': false,
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
|
||||
@@ -35,8 +35,6 @@ class Indexer {
|
||||
this.runIndexer = false;
|
||||
this.indexerRunning = true;
|
||||
|
||||
logger.debug(`Running mining indexer`);
|
||||
|
||||
try {
|
||||
const chainValid = await blocks.$generateBlockDatabase();
|
||||
if (chainValid === false) {
|
||||
@@ -56,15 +54,9 @@ class Indexer {
|
||||
this.indexerRunning = false;
|
||||
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
setTimeout(() => this.reindex(), 10000);
|
||||
this.indexerRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.indexerRunning = false;
|
||||
|
||||
const runEvery = 1000 * 3600; // 1 hour
|
||||
logger.debug(`Indexing completed. Next run planned at ${new Date(new Date().getTime() + runEvery).toUTCString()}`);
|
||||
setTimeout(() => this.reindex(), runEvery);
|
||||
}
|
||||
|
||||
async $resetHashratesIndexingState() {
|
||||
|
||||
@@ -610,24 +610,6 @@ class BlocksRepository {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the oldest block from a consecutive chain of block from the most recent one
|
||||
*/
|
||||
public async $getOldestConsecutiveBlock(): Promise<any> {
|
||||
try {
|
||||
const [rows]: any = await DB.query(`SELECT height, UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty FROM blocks ORDER BY height DESC`);
|
||||
for (let i = 0; i < rows.length - 1; ++i) {
|
||||
if (rows[i].height - rows[i + 1].height > 1) {
|
||||
return rows[i];
|
||||
}
|
||||
}
|
||||
return rows[rows.length - 1];
|
||||
} catch (e) {
|
||||
logger.err('Cannot generate block size and weight history. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BlocksRepository();
|
||||
@@ -46,38 +46,9 @@ class DifficultyAdjustmentsRepository {
|
||||
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`;
|
||||
|
||||
if (descOrder === true) {
|
||||
query += ` ORDER BY height DESC`;
|
||||
query += ` ORDER BY time DESC`;
|
||||
} else {
|
||||
query += ` ORDER BY height`;
|
||||
}
|
||||
|
||||
try {
|
||||
const [rows] = await DB.query(query);
|
||||
return rows as IndexedDifficultyAdjustment[];
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get difficulty adjustments from the database. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getRawAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
|
||||
interval = Common.getSqlInterval(interval);
|
||||
|
||||
let query = `SELECT
|
||||
UNIX_TIMESTAMP(time) as time,
|
||||
height as height,
|
||||
difficulty as difficulty,
|
||||
adjustment as adjustment
|
||||
FROM difficulty_adjustments`;
|
||||
|
||||
if (interval) {
|
||||
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||
}
|
||||
|
||||
if (descOrder === true) {
|
||||
query += ` ORDER BY height DESC`;
|
||||
} else {
|
||||
query += ` ORDER BY height`;
|
||||
query += ` ORDER BY time`;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { escape } from 'mysql2';
|
||||
import { Common } from '../api/common';
|
||||
import config from '../config';
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import PoolsRepository from './PoolsRepository';
|
||||
@@ -29,32 +30,6 @@ class HashratesRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getRawNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
||||
interval = Common.getSqlInterval(interval);
|
||||
|
||||
let query = `SELECT
|
||||
UNIX_TIMESTAMP(hashrate_timestamp) as timestamp,
|
||||
avg_hashrate as avgHashrate
|
||||
FROM hashrates`;
|
||||
|
||||
if (interval) {
|
||||
query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
|
||||
AND hashrates.type = 'daily'`;
|
||||
} else {
|
||||
query += ` WHERE hashrates.type = 'daily'`;
|
||||
}
|
||||
|
||||
query += ` ORDER by hashrate_timestamp`;
|
||||
|
||||
try {
|
||||
const [rows]: any[] = await DB.query(query);
|
||||
return rows;
|
||||
} catch (e) {
|
||||
logger.err('Cannot fetch network hashrate history. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
||||
interval = Common.getSqlInterval(interval);
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@ import { Prices } from '../tasks/price-updater';
|
||||
|
||||
class PricesRepository {
|
||||
public async $savePrices(time: number, prices: Prices): Promise<void> {
|
||||
if (prices.USD === -1) {
|
||||
// Some historical price entries have not USD prices, so we just ignore them to avoid future UX issues
|
||||
// As of today there are only 4 (on 2013-09-05, 2013-09-19, 2013-09-12 and 2013-09-26) so that's fine
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await DB.query(`
|
||||
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
|
||||
@@ -23,17 +17,17 @@ class PricesRepository {
|
||||
}
|
||||
|
||||
public async $getOldestPriceTime(): Promise<number> {
|
||||
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time LIMIT 1`);
|
||||
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time LIMIT 1`);
|
||||
return oldestRow[0] ? oldestRow[0].time : 0;
|
||||
}
|
||||
|
||||
public async $getLatestPriceTime(): Promise<number> {
|
||||
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time DESC LIMIT 1`);
|
||||
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time DESC LIMIT 1`);
|
||||
return oldestRow[0] ? oldestRow[0].time : 0;
|
||||
}
|
||||
|
||||
public async $getPricesTimes(): Promise<number[]> {
|
||||
const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1`);
|
||||
const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices`);
|
||||
return times.map(time => time.time);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,7 +734,7 @@ class Routes {
|
||||
|
||||
public async $getDifficultyAdjustments(req: Request, res: Response) {
|
||||
try {
|
||||
const difficulty = await DifficultyAdjustmentsRepository.$getRawAdjustments(req.params.interval, true);
|
||||
const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, true);
|
||||
res.header('Pragma', 'public');
|
||||
res.header('Cache-control', 'public');
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
@@ -790,7 +790,7 @@ class Routes {
|
||||
|
||||
public async getBlocks(req: Request, res: Response) {
|
||||
try {
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
|
||||
if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
|
||||
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(await blocks.$getBlocks(height, 15));
|
||||
|
||||
@@ -3,14 +3,14 @@ import { BlockExtended } from '../mempool.interfaces';
|
||||
export function prepareBlock(block: any): BlockExtended {
|
||||
return <BlockExtended>{
|
||||
id: block.id ?? block.hash, // hash for indexed block
|
||||
timestamp: block.timestamp ?? block.time ?? block.blockTimestamp, // blockTimestamp for indexed block
|
||||
timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block
|
||||
height: block.height,
|
||||
version: block.version,
|
||||
bits: (typeof block.bits === 'string' ? parseInt(block.bits, 16): block.bits),
|
||||
bits: block.bits,
|
||||
nonce: block.nonce,
|
||||
difficulty: block.difficulty,
|
||||
merkle_root: block.merkle_root ?? block.merkleroot,
|
||||
tx_count: block.tx_count ?? block.nTx,
|
||||
merkle_root: block.merkle_root,
|
||||
tx_count: block.tx_count,
|
||||
size: block.size,
|
||||
weight: block.weight,
|
||||
previousblockhash: block.previousblockhash,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.16.0-buster-slim AS builder
|
||||
FROM node:16.15.0-buster-slim AS builder
|
||||
|
||||
ARG commitHash
|
||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||
@@ -8,10 +8,10 @@ COPY . .
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential python3 pkg-config
|
||||
RUN npm install --omit=dev --omit=optional
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM node:16.16.0-buster-slim
|
||||
FROM node:16.15.0-buster-slim
|
||||
|
||||
WORKDIR /backend
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
"USER_AGENT": "__MEMPOOL_USER_AGENT__",
|
||||
"STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__",
|
||||
"INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__,
|
||||
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__,
|
||||
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__
|
||||
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
||||
@@ -22,8 +22,6 @@ __MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
|
||||
__MEMPOOL_EXTERNAL_RETRY_INTERVAL__=${MEMPOOL_EXTERNAL_RETRY_INTERVAL:=0}
|
||||
__MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool}
|
||||
__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info}
|
||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=false}
|
||||
__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=false}
|
||||
|
||||
# CORE_RPC
|
||||
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
|
||||
@@ -112,8 +110,6 @@ sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" me
|
||||
sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json
|
||||
sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json
|
||||
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
|
||||
sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/g" mempool-config.json
|
||||
|
||||
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
|
||||
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:16.16.0-buster-slim AS builder
|
||||
FROM node:16.15.0-buster-slim AS builder
|
||||
|
||||
ARG commitHash
|
||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||
@@ -8,7 +8,7 @@ WORKDIR /build
|
||||
COPY . .
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential rsync
|
||||
RUN npm install --omit=dev --omit=optional
|
||||
RUN npm i
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.17.8-alpine
|
||||
|
||||
Generated
+22
-29
@@ -36,6 +36,7 @@
|
||||
"echarts": "~5.3.2",
|
||||
"express": "^4.17.1",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-bootrap-multiselect": "^2.0.0",
|
||||
"ngx-echarts": "8.0.1",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"qrcode": "1.5.0",
|
||||
@@ -11041,12 +11042,6 @@
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -12793,6 +12788,19 @@
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"node_modules/ngx-bootrap-multiselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
|
||||
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^10.0.6",
|
||||
"@angular/core": "^10.0.6",
|
||||
"@angular/forms": "^10.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-echarts": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
||||
@@ -13797,17 +13805,6 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||
@@ -26086,12 +26083,6 @@
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
||||
"peer": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -27427,6 +27418,14 @@
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"ngx-bootrap-multiselect": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
|
||||
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"ngx-echarts": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
|
||||
@@ -28188,12 +28187,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
|
||||
},
|
||||
"popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"peer": true
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"echarts": "~5.3.2",
|
||||
"express": "^4.17.1",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-bootrap-multiselect": "^2.0.0",
|
||||
"ngx-echarts": "8.0.1",
|
||||
"ngx-infinite-scroll": "^10.0.1",
|
||||
"qrcode": "1.5.0",
|
||||
|
||||
@@ -25,7 +25,7 @@ PROXY_CONFIG = [
|
||||
'!/bisq', '!/bisq/**', '!/bisq/',
|
||||
'!/liquid', '!/liquid/**', '!/liquid/',
|
||||
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
|
||||
'/testnet/api/**', '/signet/api/**'
|
||||
'/testnet/api/**', '/signet/api/**', '!/api/v1/asset/**', '!/api/asset/**'
|
||||
],
|
||||
target: "https://mempool.space",
|
||||
ws: true,
|
||||
@@ -59,6 +59,12 @@ PROXY_CONFIG = [
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
},
|
||||
{
|
||||
context: ['/api/v1/asset/**/icon', '/api/asset/**'],
|
||||
target: "https://liquid.network",
|
||||
secure: false,
|
||||
changeOrigin: true
|
||||
},
|
||||
{
|
||||
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
|
||||
target: "https://liquid.network",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<div class="d-block float-right" id="filter">
|
||||
<form [formGroup]="radioGroupForm">
|
||||
<ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect>
|
||||
<ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
|
||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
|
||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BisqRoutingModule } from './bisq.routing.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
||||
|
||||
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
||||
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
|
||||
@@ -23,10 +24,6 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
|
||||
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
||||
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
|
||||
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
|
||||
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
|
||||
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -47,21 +44,16 @@ import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-mul
|
||||
BisqMarketComponent,
|
||||
BisqTradesComponent,
|
||||
BisqMainDashboardComponent,
|
||||
NgxDropdownMultiselectComponent,
|
||||
AutofocusDirective,
|
||||
OffClickDirective,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BisqRoutingModule,
|
||||
SharedModule,
|
||||
FontAwesomeModule,
|
||||
NgxBootstrapMultiselectModule,
|
||||
],
|
||||
providers: [
|
||||
BisqApiService,
|
||||
MultiSelectSearchFilter,
|
||||
AutofocusDirective,
|
||||
OffClickDirective,
|
||||
]
|
||||
})
|
||||
export class BisqModule {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lightweight-charts-area',
|
||||
@@ -25,15 +25,6 @@ export class LightweightChartsAreaComponent implements OnInit, OnChanges, OnDest
|
||||
private element: ElementRef,
|
||||
) { }
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
resizeCanvas(): void {
|
||||
this.width = this.element.nativeElement.parentElement.offsetWidth;
|
||||
this.chart.applyOptions({
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.width = this.element.nativeElement.parentElement.offsetWidth;
|
||||
this.container = document.createElement('div');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createChart, CrosshairMode } from 'lightweight-charts';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lightweight-charts',
|
||||
@@ -21,14 +21,6 @@ export class LightweightChartsComponent implements OnInit, OnChanges, OnDestroy
|
||||
private element: ElementRef,
|
||||
) { }
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
resizeCanvas(): void {
|
||||
this.chart.applyOptions({
|
||||
width: this.element.nativeElement.parentElement.offsetWidth,
|
||||
height: this.height,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.chart = createChart(this.element.nativeElement, {
|
||||
width: this.element.nativeElement.parentElement.offsetWidth,
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="td-width" i18n="shared.transaction">Transaction</td>
|
||||
<td i18n="shared.transaction">Transaction</td>
|
||||
<td>
|
||||
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
|
||||
<td class="td-width" i18n="transaction.value|Transaction value">Value</td>
|
||||
<td><app-amount [satoshis]="value"></app-amount></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -23,13 +23,13 @@
|
||||
<td>{{ fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="fee"></app-fiat></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||
<td>
|
||||
{{ feeRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td [innerHTML]="'‎' + (vsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
+1
-5
@@ -9,14 +9,10 @@
|
||||
justify-content: space-between;
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
min-width: 320px;
|
||||
width: 320px;
|
||||
pointer-events: none;
|
||||
|
||||
&.clickable {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.td-width {
|
||||
padding-right: 10px;
|
||||
}
|
||||
@@ -114,11 +114,7 @@
|
||||
}
|
||||
|
||||
.flashing {
|
||||
/* force compositing */
|
||||
will-change: opacity;
|
||||
transform: translateZ(0);
|
||||
/* effective max frame rate = (#keyframes - 1) x steps / duration */
|
||||
animation: opacityPulse 2s steps(30, end);
|
||||
animation: opacityPulse 2s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
}
|
||||
|
||||
.flashing {
|
||||
/* force compositing */
|
||||
will-change: opacity;
|
||||
transform: translateZ(0);
|
||||
/* effective max frame rate = (#keyframes - 1) x steps / duration */
|
||||
animation: opacityPulse 2s steps(30, end);
|
||||
animation: opacityPulse 2s ease-out;
|
||||
animation-iteration-count: infinite;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[ssAutofocus]'
|
||||
})
|
||||
export class AutofocusDirective implements OnInit, OnChanges {
|
||||
|
||||
/**
|
||||
* Will set focus if set to falsy value or not set at all
|
||||
*/
|
||||
@Input() ssAutofocus: any;
|
||||
|
||||
get element(): { focus?: Function } {
|
||||
return this.elemRef.nativeElement;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Host() private elemRef: ElementRef,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const ssAutofocusChange = changes.ssAutofocus;
|
||||
|
||||
if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.ssAutofocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.element.focus && this.element.focus();
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
a {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.dropdown-inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-toggle .caret {
|
||||
margin-left: 4px;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.chunkydropdown-menu {
|
||||
min-width: 20em;
|
||||
}
|
||||
|
||||
.chunkyrow {
|
||||
line-height: 2;
|
||||
margin-left: 1em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.slider {
|
||||
width:3.8em;
|
||||
height:3.8em;
|
||||
display:block;
|
||||
-webkit-transition: all 0.125s linear;
|
||||
-moz-transition: all 0.125s linear;
|
||||
-o-transition: all 0.125s linear;
|
||||
transition: all 0.125s linear;
|
||||
margin-left: 0.125em;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.slideron {
|
||||
margin-left: 1.35em;
|
||||
}
|
||||
|
||||
.content_wrapper{
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0px 5px 5px 5px;
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
|
||||
<button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
|
||||
[ssAutofocus]="!focusBack">
|
||||
{{ title }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
|
||||
(scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
|
||||
[class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
|
||||
style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
|
||||
<div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
|
||||
class="form-control">
|
||||
<div class="input-group-append" *ngIf="filterControl.value.length>0">
|
||||
<button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
|
||||
(click)="checkAll()">
|
||||
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
|
||||
{{ texts.checkAll }}
|
||||
</a>
|
||||
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
|
||||
(click)="uncheckAll()">
|
||||
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
|
||||
{{ texts.uncheckAll }}
|
||||
</a>
|
||||
<a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
|
||||
<a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
|
||||
<a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
|
||||
<a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
|
||||
[ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
|
||||
tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
|
||||
<span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
|
||||
[ngStyle]="getItemStyleSelectionDisabled()">
|
||||
<ng-container [ngSwitch]="settings.checkedStyle">
|
||||
<input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
|
||||
[disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
|
||||
<span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
|
||||
<span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
|
||||
<span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
|
||||
<span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
<span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
|
||||
<div class="slider" [ngClass]="{'slideron': isSelected(option)}">
|
||||
<img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
|
||||
<div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
|
||||
<div class="content_wrapper">
|
||||
<span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</ng-container>
|
||||
<span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
|
||||
[style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
|
||||
{{ option.name }}
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #label>
|
||||
<span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
|
||||
</ng-template>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
-710
@@ -1,710 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DoCheck,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Input,
|
||||
IterableDiffers,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
NG_VALUE_ACCESSOR,
|
||||
Validator,
|
||||
} from '@angular/forms';
|
||||
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { MultiSelectSearchFilter } from './search-filter.pipe';
|
||||
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
const MULTISELECT_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
|
||||
multi: true,
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: no-conflicting-lifecycle
|
||||
@Component({
|
||||
selector: 'ngx-bootstrap-multiselect',
|
||||
templateUrl: './ngx-bootstrap-multiselect.component.html',
|
||||
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
|
||||
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class NgxDropdownMultiselectComponent implements OnInit,
|
||||
OnChanges,
|
||||
DoCheck,
|
||||
OnDestroy,
|
||||
ControlValueAccessor,
|
||||
Validator {
|
||||
|
||||
private localIsVisible = false;
|
||||
private workerDocClicked = false;
|
||||
|
||||
filterControl: FormControl = this.fb.control('');
|
||||
|
||||
@Input() options: Array<IMultiSelectOption>;
|
||||
@Input() settings: IMultiSelectSettings;
|
||||
@Input() texts: IMultiSelectTexts;
|
||||
@Input() disabled = false;
|
||||
@Input() disabledSelection = false;
|
||||
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
|
||||
|
||||
@Output() selectionLimitReached = new EventEmitter();
|
||||
@Output() dropdownClosed = new EventEmitter();
|
||||
@Output() dropdownOpened = new EventEmitter();
|
||||
@Output() added = new EventEmitter();
|
||||
@Output() removed = new EventEmitter();
|
||||
@Output() lazyLoad = new EventEmitter();
|
||||
@Output() filter: Observable<string> = this.filterControl.valueChanges;
|
||||
|
||||
get focusBack(): boolean {
|
||||
return this.settings.focusBack && this._focusBack;
|
||||
}
|
||||
|
||||
destroyed$ = new Subject<any>();
|
||||
|
||||
filteredOptions: IMultiSelectOption[] = [];
|
||||
lazyLoadOptions: IMultiSelectOption[] = [];
|
||||
renderFilteredOptions: IMultiSelectOption[] = [];
|
||||
model: any[] = [];
|
||||
prevModel: any[] = [];
|
||||
parents: any[];
|
||||
title: string;
|
||||
differ: any;
|
||||
numSelected = 0;
|
||||
set isVisible(val: boolean) {
|
||||
this.localIsVisible = val;
|
||||
this.workerDocClicked = val ? false : this.workerDocClicked;
|
||||
}
|
||||
get isVisible(): boolean {
|
||||
return this.localIsVisible;
|
||||
}
|
||||
renderItems = true;
|
||||
checkAllSearchRegister = new Set();
|
||||
checkAllStatus = false;
|
||||
loadedValueIds = [];
|
||||
_focusBack = false;
|
||||
focusedItem: IMultiSelectOption | undefined;
|
||||
|
||||
defaultSettings: IMultiSelectSettings = {
|
||||
closeOnClickOutside: true,
|
||||
pullRight: false,
|
||||
enableSearch: false,
|
||||
searchRenderLimit: 0,
|
||||
searchRenderAfter: 1,
|
||||
searchMaxLimit: 0,
|
||||
searchMaxRenderedItems: 0,
|
||||
checkedStyle: 'checkboxes',
|
||||
buttonClasses: 'btn btn-primary dropdown-toggle',
|
||||
containerClasses: 'dropdown-inline',
|
||||
selectionLimit: 0,
|
||||
minSelectionLimit: 0,
|
||||
closeOnSelect: false,
|
||||
autoUnselect: false,
|
||||
showCheckAll: false,
|
||||
showUncheckAll: false,
|
||||
fixedTitle: false,
|
||||
dynamicTitleMaxItems: 3,
|
||||
maxHeight: '300px',
|
||||
isLazyLoad: false,
|
||||
stopScrollPropagation: false,
|
||||
loadViewDistance: 1,
|
||||
selectAddedValues: false,
|
||||
ignoreLabels: false,
|
||||
maintainSelectionOrderInTitle: false,
|
||||
focusBack: true
|
||||
};
|
||||
defaultTexts: IMultiSelectTexts = {
|
||||
checkAll: 'Select all',
|
||||
uncheckAll: 'Unselect all',
|
||||
checked: 'selected',
|
||||
checkedPlural: 'selected',
|
||||
searchPlaceholder: 'Search...',
|
||||
searchEmptyResult: 'Nothing found...',
|
||||
searchNoRenderText: 'Type in search box to see results...',
|
||||
defaultTitle: 'Select',
|
||||
allSelected: 'All selected',
|
||||
};
|
||||
|
||||
get searchLimit(): number | undefined {
|
||||
return this.settings.searchRenderLimit;
|
||||
}
|
||||
|
||||
get searchRenderAfter(): number | undefined {
|
||||
return this.settings.searchRenderAfter;
|
||||
}
|
||||
|
||||
get searchLimitApplied(): boolean {
|
||||
return this.searchLimit > 0 && this.options.length > this.searchLimit;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private searchFilter: MultiSelectSearchFilter,
|
||||
differs: IterableDiffers,
|
||||
private cdRef: ChangeDetectorRef
|
||||
) {
|
||||
this.differ = differs.find([]).create(null);
|
||||
this.settings = this.defaultSettings;
|
||||
this.texts = this.defaultTexts;
|
||||
}
|
||||
|
||||
clickedOutside(): void {
|
||||
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
|
||||
|
||||
this.isVisible = false;
|
||||
this._focusBack = true;
|
||||
this.dropdownClosed.emit();
|
||||
}
|
||||
|
||||
getItemStyle(option: IMultiSelectOption): any {
|
||||
const style = {};
|
||||
if (!option.isLabel) {
|
||||
style['cursor'] = 'pointer';
|
||||
}
|
||||
if (option.disabled) {
|
||||
style['cursor'] = 'default';
|
||||
}
|
||||
}
|
||||
|
||||
getItemStyleSelectionDisabled(): any {
|
||||
if (this.disabledSelection) {
|
||||
return { cursor: 'default' };
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.title = this.texts.defaultTitle || '';
|
||||
|
||||
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
|
||||
this.updateRenderItems();
|
||||
if (this.settings.isLazyLoad) {
|
||||
this.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['options']) {
|
||||
this.options = this.options || [];
|
||||
this.parents = this.options
|
||||
.filter(option => typeof option.parentId === 'number')
|
||||
.map(option => option.parentId);
|
||||
this.updateRenderItems();
|
||||
|
||||
if (
|
||||
this.settings.isLazyLoad &&
|
||||
this.settings.selectAddedValues &&
|
||||
this.loadedValueIds.length === 0
|
||||
) {
|
||||
this.loadedValueIds = this.loadedValueIds.concat(
|
||||
changes.options.currentValue.map(value => value.id)
|
||||
);
|
||||
}
|
||||
if (
|
||||
this.settings.isLazyLoad &&
|
||||
this.settings.selectAddedValues &&
|
||||
changes.options.previousValue
|
||||
) {
|
||||
const addedValues = changes.options.currentValue.filter(
|
||||
value => this.loadedValueIds.indexOf(value.id) === -1
|
||||
);
|
||||
this.loadedValueIds.concat(addedValues.map(value => value.id));
|
||||
if (this.checkAllStatus) {
|
||||
this.addChecks(addedValues);
|
||||
} else if (this.checkAllSearchRegister.size > 0) {
|
||||
this.checkAllSearchRegister.forEach((searchValue: string) =>
|
||||
this.addChecks(this.applyFilters(addedValues, searchValue))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.texts) {
|
||||
this.updateTitle();
|
||||
}
|
||||
|
||||
this.fireModelChange();
|
||||
}
|
||||
|
||||
if (changes['settings']) {
|
||||
this.settings = { ...this.defaultSettings, ...this.settings };
|
||||
}
|
||||
|
||||
if (changes['texts']) {
|
||||
this.texts = { ...this.defaultTexts, ...this.texts };
|
||||
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroyed$.next(false);
|
||||
}
|
||||
|
||||
updateRenderItems() {
|
||||
this.renderItems =
|
||||
!this.searchLimitApplied ||
|
||||
this.filterControl.value.length >= this.searchRenderAfter;
|
||||
this.filteredOptions = this.applyFilters(
|
||||
this.options,
|
||||
this.settings.isLazyLoad ? '' : this.filterControl.value
|
||||
);
|
||||
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
|
||||
this.focusedItem = undefined;
|
||||
}
|
||||
|
||||
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
|
||||
return this.searchFilter.transform(
|
||||
options,
|
||||
value,
|
||||
this.settings.searchMaxLimit,
|
||||
this.settings.searchMaxRenderedItems,
|
||||
this.searchFunction
|
||||
);
|
||||
}
|
||||
|
||||
fireModelChange(): void {
|
||||
if (this.model != this.prevModel) {
|
||||
this.prevModel = this.model;
|
||||
this.onModelChange(this.model);
|
||||
this.onModelTouched();
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
onModelChange: Function = (_: any) => { };
|
||||
onModelTouched: Function = () => { };
|
||||
|
||||
writeValue(value: any): void {
|
||||
if (value !== undefined && value !== null) {
|
||||
this.model = Array.isArray(value) ? value : [value];
|
||||
this.ngDoCheck();
|
||||
} else {
|
||||
this.model = [];
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: Function): void {
|
||||
this.onModelChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: Function): void {
|
||||
this.onModelTouched = fn;
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean) {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
const changes = this.differ.diff(this.model);
|
||||
if (changes) {
|
||||
this.updateNumSelected();
|
||||
this.updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
validate(_c: AbstractControl): { [key: string]: any } {
|
||||
if (this.model && this.model.length) {
|
||||
return {
|
||||
required: {
|
||||
valid: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
|
||||
return {
|
||||
selection: {
|
||||
valid: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
registerOnValidatorChange(_fn: () => void): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
clearSearch(event: Event) {
|
||||
this.maybeStopPropagation(event);
|
||||
this.filterControl.setValue('');
|
||||
}
|
||||
|
||||
toggleDropdown(e?: Event) {
|
||||
if (this.isVisible) {
|
||||
this._focusBack = true;
|
||||
}
|
||||
|
||||
this.isVisible = !this.isVisible;
|
||||
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
|
||||
this.focusedItem = undefined;
|
||||
}
|
||||
|
||||
closeDropdown(e?: Event) {
|
||||
this.isVisible = true;
|
||||
this.toggleDropdown(e);
|
||||
}
|
||||
|
||||
isSelected(option: IMultiSelectOption): boolean {
|
||||
return this.model && this.model.indexOf(option.id) > -1;
|
||||
}
|
||||
|
||||
setSelected(_event: Event, option: IMultiSelectOption) {
|
||||
if (option.isLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.disabledSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.maybeStopPropagation(_event);
|
||||
this.maybePreventDefault(_event);
|
||||
const index = this.model.indexOf(option.id);
|
||||
const isAtSelectionLimit =
|
||||
this.settings.selectionLimit > 0 &&
|
||||
this.model.length >= this.settings.selectionLimit;
|
||||
const removeItem = (idx, id): void => {
|
||||
this.model.splice(idx, 1);
|
||||
this.removed.emit(id);
|
||||
if (
|
||||
this.settings.isLazyLoad &&
|
||||
this.lazyLoadOptions.some(val => val.id === id)
|
||||
) {
|
||||
this.lazyLoadOptions.splice(
|
||||
this.lazyLoadOptions.indexOf(
|
||||
this.lazyLoadOptions.find(val => val.id === id)
|
||||
),
|
||||
1
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (index > -1) {
|
||||
if (
|
||||
this.settings.minSelectionLimit === undefined ||
|
||||
this.numSelected > this.settings.minSelectionLimit
|
||||
) {
|
||||
removeItem(index, option.id);
|
||||
}
|
||||
const parentIndex =
|
||||
option.parentId && this.model.indexOf(option.parentId);
|
||||
if (parentIndex > -1) {
|
||||
removeItem(parentIndex, option.parentId);
|
||||
} else if (this.parents.indexOf(option.id) > -1) {
|
||||
this.options
|
||||
.filter(
|
||||
child =>
|
||||
this.model.indexOf(child.id) > -1 &&
|
||||
child.parentId === option.id
|
||||
)
|
||||
.forEach(child =>
|
||||
removeItem(this.model.indexOf(child.id), child.id)
|
||||
);
|
||||
}
|
||||
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
|
||||
this.selectionLimitReached.emit(this.model.length);
|
||||
return;
|
||||
} else {
|
||||
const addItem = (id): void => {
|
||||
this.model.push(id);
|
||||
this.added.emit(id);
|
||||
if (
|
||||
this.settings.isLazyLoad &&
|
||||
!this.lazyLoadOptions.some(val => val.id === id)
|
||||
) {
|
||||
this.lazyLoadOptions.push(option);
|
||||
}
|
||||
};
|
||||
|
||||
addItem(option.id);
|
||||
if (!isAtSelectionLimit) {
|
||||
if (option.parentId && !this.settings.ignoreLabels) {
|
||||
const children = this.options.filter(
|
||||
child =>
|
||||
child.id !== option.id && child.parentId === option.parentId
|
||||
);
|
||||
if (children.every(child => this.model.indexOf(child.id) > -1)) {
|
||||
addItem(option.parentId);
|
||||
}
|
||||
} else if (this.parents.indexOf(option.id) > -1) {
|
||||
const children = this.options.filter(
|
||||
child =>
|
||||
this.model.indexOf(child.id) < 0 && child.parentId === option.id
|
||||
);
|
||||
children.forEach(child => addItem(child.id));
|
||||
}
|
||||
} else {
|
||||
removeItem(0, this.model[0]);
|
||||
}
|
||||
}
|
||||
if (this.settings.closeOnSelect) {
|
||||
this.toggleDropdown();
|
||||
}
|
||||
this.model = this.model.slice();
|
||||
this.fireModelChange();
|
||||
|
||||
}, 0)
|
||||
}
|
||||
|
||||
updateNumSelected() {
|
||||
this.numSelected =
|
||||
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
|
||||
}
|
||||
|
||||
updateTitle() {
|
||||
let numSelectedOptions = this.options.length;
|
||||
if (this.settings.ignoreLabels) {
|
||||
numSelectedOptions = this.options.filter(
|
||||
(option: IMultiSelectOption) => !option.isLabel
|
||||
).length;
|
||||
}
|
||||
if (this.numSelected === 0 || this.settings.fixedTitle) {
|
||||
this.title = this.texts ? this.texts.defaultTitle : '';
|
||||
} else if (
|
||||
this.settings.displayAllSelectedText &&
|
||||
this.model.length === numSelectedOptions
|
||||
) {
|
||||
this.title = this.texts ? this.texts.allSelected : '';
|
||||
} else if (
|
||||
this.settings.dynamicTitleMaxItems &&
|
||||
this.settings.dynamicTitleMaxItems >= this.numSelected
|
||||
) {
|
||||
const useOptions =
|
||||
this.settings.isLazyLoad && this.lazyLoadOptions.length
|
||||
? this.lazyLoadOptions
|
||||
: this.options;
|
||||
|
||||
let titleSelections: Array<IMultiSelectOption>;
|
||||
|
||||
if (this.settings.maintainSelectionOrderInTitle) {
|
||||
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
|
||||
titleSelections = this.model
|
||||
.map((selectedId) => optionIds.indexOf(selectedId))
|
||||
.filter((optionIndex) => optionIndex > -1)
|
||||
.map((optionIndex) => useOptions[optionIndex]);
|
||||
} else {
|
||||
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
|
||||
}
|
||||
|
||||
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
|
||||
} else {
|
||||
this.title =
|
||||
this.numSelected +
|
||||
' ' +
|
||||
(this.numSelected === 1
|
||||
? this.texts.checked
|
||||
: this.texts.checkedPlural);
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
searchFilterApplied() {
|
||||
return (
|
||||
this.settings.enableSearch &&
|
||||
this.filterControl.value &&
|
||||
this.filterControl.value.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
addChecks(options) {
|
||||
const checkedOptions = options
|
||||
.filter((option: IMultiSelectOption) => {
|
||||
if (
|
||||
!option.disabled &&
|
||||
(
|
||||
this.model.indexOf(option.id) === -1 &&
|
||||
!(this.settings.ignoreLabels && option.isLabel)
|
||||
)
|
||||
) {
|
||||
this.added.emit(option.id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((option: IMultiSelectOption) => option.id);
|
||||
|
||||
this.model = this.model.concat(checkedOptions);
|
||||
}
|
||||
|
||||
checkAll(): void {
|
||||
if (!this.disabledSelection) {
|
||||
this.addChecks(
|
||||
!this.searchFilterApplied() ? this.options : this.filteredOptions
|
||||
);
|
||||
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
||||
if (this.searchFilterApplied() && !this.checkAllStatus) {
|
||||
this.checkAllSearchRegister.add(this.filterControl.value);
|
||||
} else {
|
||||
this.checkAllSearchRegister.clear();
|
||||
this.checkAllStatus = true;
|
||||
}
|
||||
this.load();
|
||||
}
|
||||
this.fireModelChange();
|
||||
}
|
||||
}
|
||||
|
||||
uncheckAll(): void {
|
||||
if (!this.disabledSelection) {
|
||||
const checkedOptions = this.model;
|
||||
let unCheckedOptions = !this.searchFilterApplied()
|
||||
? this.model
|
||||
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
|
||||
// set unchecked options only to the ones that were checked
|
||||
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
|
||||
this.model = this.model.filter((id: number) => {
|
||||
if (
|
||||
(unCheckedOptions.indexOf(id) < 0 &&
|
||||
this.settings.minSelectionLimit === undefined) ||
|
||||
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
this.removed.emit(id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
|
||||
if (this.searchFilterApplied()) {
|
||||
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
|
||||
this.checkAllSearchRegister.delete(this.filterControl.value);
|
||||
this.checkAllSearchRegister.forEach(function(searchTerm) {
|
||||
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
|
||||
this.addChecks(filterOptions);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.checkAllSearchRegister.clear();
|
||||
this.checkAllStatus = false;
|
||||
}
|
||||
this.load();
|
||||
}
|
||||
this.fireModelChange();
|
||||
}
|
||||
}
|
||||
|
||||
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
|
||||
if (
|
||||
option.disabled ||
|
||||
(
|
||||
this.settings.selectionLimit &&
|
||||
!this.settings.autoUnselect &&
|
||||
this.model.length >= this.settings.selectionLimit &&
|
||||
this.model.indexOf(option.id) === -1 &&
|
||||
this.maybePreventDefault(event)
|
||||
)
|
||||
) {
|
||||
this.maybePreventDefault(event);
|
||||
}
|
||||
}
|
||||
|
||||
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
|
||||
return this.disabledSelection || option && option.disabled;
|
||||
}
|
||||
|
||||
checkScrollPosition(ev): void {
|
||||
const scrollTop = ev.target.scrollTop;
|
||||
const scrollHeight = ev.target.scrollHeight;
|
||||
const scrollElementHeight = ev.target.clientHeight;
|
||||
const roundingPixel = 1;
|
||||
const gutterPixel = 1;
|
||||
|
||||
if (
|
||||
scrollTop >=
|
||||
scrollHeight -
|
||||
(1 + this.settings.loadViewDistance) * scrollElementHeight -
|
||||
roundingPixel -
|
||||
gutterPixel
|
||||
) {
|
||||
this.load();
|
||||
}
|
||||
}
|
||||
|
||||
checkScrollPropagation(ev, element): void {
|
||||
const scrollTop = element.scrollTop;
|
||||
const scrollHeight = element.scrollHeight;
|
||||
const scrollElementHeight = element.clientHeight;
|
||||
|
||||
if (
|
||||
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
|
||||
(ev.deltaY < 0 && scrollTop <= 0)
|
||||
) {
|
||||
ev = ev || window.event;
|
||||
this.maybePreventDefault(ev);
|
||||
ev.returnValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
trackById(idx: number, selectOption: IMultiSelectOption): void {
|
||||
return selectOption.id;
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.lazyLoad.emit({
|
||||
length: this.options.length,
|
||||
filter: this.filterControl.value,
|
||||
checkAllSearches: this.checkAllSearchRegister,
|
||||
checkAllStatus: this.checkAllStatus,
|
||||
});
|
||||
}
|
||||
|
||||
focusItem(dir: number, e?: Event): void {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.maybePreventDefault(e);
|
||||
|
||||
const idx = this.filteredOptions.indexOf(this.focusedItem);
|
||||
|
||||
if (idx === -1) {
|
||||
this.focusedItem = this.filteredOptions[0];
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIdx = idx + dir;
|
||||
const newIdx =
|
||||
nextIdx < 0
|
||||
? this.filteredOptions.length - 1
|
||||
: nextIdx % this.filteredOptions.length;
|
||||
|
||||
this.focusedItem = this.filteredOptions[newIdx];
|
||||
}
|
||||
|
||||
private maybePreventDefault(e?: Event): void {
|
||||
if (e && e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeStopPropagation(e?: Event): void {
|
||||
if (e && e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private _escapeRegExp(str: string): RegExp {
|
||||
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
||||
return new RegExp(regExpStr, 'i');
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Directive, HostListener } from '@angular/core';
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { Output } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
// tslint:disable-next-line:directive-selector
|
||||
selector: '[offClick]',
|
||||
})
|
||||
|
||||
export class OffClickDirective {
|
||||
@Output('offClick') onOffClick = new EventEmitter<any>();
|
||||
|
||||
private _clickEvent: MouseEvent;
|
||||
private _touchEvent: TouchEvent;
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
public onClick(event: MouseEvent): void {
|
||||
this._clickEvent = event;
|
||||
}
|
||||
|
||||
@HostListener('touchstart', ['$event'])
|
||||
public onTouch(event: TouchEvent): void {
|
||||
this._touchEvent = event;
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
public onDocumentClick(event: MouseEvent): void {
|
||||
if (event !== this._clickEvent) {
|
||||
this.onOffClick.emit(event);
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('document:touchstart', ['$event'])
|
||||
public onDocumentTouch(event: TouchEvent): void {
|
||||
if (event !== this._touchEvent) {
|
||||
this.onOffClick.emit(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { IMultiSelectOption } from './types';
|
||||
|
||||
interface StringHashMap<T> {
|
||||
[k: string]: T;
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'searchFilter'
|
||||
})
|
||||
export class MultiSelectSearchFilter implements PipeTransform {
|
||||
|
||||
private _lastOptions: IMultiSelectOption[];
|
||||
private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
|
||||
private _searchCacheInclusive: StringHashMap<boolean | number> = {};
|
||||
private _prevSkippedItems: StringHashMap<number> = {};
|
||||
|
||||
transform(
|
||||
options: IMultiSelectOption[],
|
||||
str = '',
|
||||
limit = 0,
|
||||
renderLimit = 0,
|
||||
searchFunction: (str: string) => RegExp,
|
||||
): IMultiSelectOption[] {
|
||||
str = str.toLowerCase();
|
||||
|
||||
// Drop cache because options were updated
|
||||
if (options !== this._lastOptions) {
|
||||
this._lastOptions = options;
|
||||
this._searchCache = {};
|
||||
this._searchCacheInclusive = {};
|
||||
this._prevSkippedItems = {};
|
||||
}
|
||||
|
||||
const filteredOpts = this._searchCache.hasOwnProperty(str)
|
||||
? this._searchCache[str]
|
||||
: this._doSearch(options, str, limit, searchFunction);
|
||||
|
||||
const isUnderLimit = options.length <= limit;
|
||||
|
||||
return isUnderLimit
|
||||
? filteredOpts
|
||||
: this._limitRenderedItems(filteredOpts, renderLimit);
|
||||
}
|
||||
|
||||
private _getSubsetOptions(
|
||||
options: IMultiSelectOption[],
|
||||
prevOptions: IMultiSelectOption[],
|
||||
prevSearchStr: string
|
||||
) {
|
||||
const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
|
||||
|
||||
if (prevInclusiveOrIdx === true) {
|
||||
// If have previous results and it was inclusive, do only subsearch
|
||||
return prevOptions;
|
||||
} else if (typeof prevInclusiveOrIdx === 'number') {
|
||||
// Or reuse prev results with unchecked ones
|
||||
return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
|
||||
const prevStr = str.slice(0, -1);
|
||||
const prevResults = this._searchCache[prevStr];
|
||||
const prevResultShift = this._prevSkippedItems[prevStr] || 0;
|
||||
|
||||
if (prevResults) {
|
||||
options = this._getSubsetOptions(options, prevResults, prevStr);
|
||||
}
|
||||
|
||||
const optsLength = options.length;
|
||||
const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
|
||||
const regexp = searchFunction(str);
|
||||
const filteredOpts: IMultiSelectOption[] = [];
|
||||
|
||||
let i = 0, founded = 0, removedFromPrevResult = 0;
|
||||
|
||||
const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
|
||||
const getChildren = (option: IMultiSelectOption) =>
|
||||
options.filter(child => child.parentId === option.id);
|
||||
const getParent = (option: IMultiSelectOption) =>
|
||||
options.find(parent => option.parentId === parent.id);
|
||||
const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
|
||||
const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
|
||||
|
||||
for (; i < optsLength && founded < maxFound; ++i) {
|
||||
const option = options[i];
|
||||
const directMatch = doesOptionMatch(option);
|
||||
|
||||
if (directMatch) {
|
||||
foundFn(option);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof option.parentId === 'undefined') {
|
||||
const childrenMatch = getChildren(option).some(doesOptionMatch);
|
||||
|
||||
if (childrenMatch) {
|
||||
foundFn(option);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof option.parentId !== 'undefined') {
|
||||
const parentMatch = doesOptionMatch(getParent(option));
|
||||
|
||||
if (parentMatch) {
|
||||
foundFn(option);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
notFoundFn();
|
||||
}
|
||||
|
||||
const totalIterations = i + prevResultShift;
|
||||
|
||||
this._searchCache[str] = filteredOpts;
|
||||
this._searchCacheInclusive[str] = i === optsLength || totalIterations;
|
||||
this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
|
||||
|
||||
return filteredOpts;
|
||||
}
|
||||
|
||||
private _limitRenderedItems<T>(items: T[], limit: number): T[] {
|
||||
return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
export interface IMultiSelectOption {
|
||||
id: any;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
isLabel?: boolean;
|
||||
parentId?: any;
|
||||
params?: any;
|
||||
classes?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export interface IMultiSelectSettings {
|
||||
pullRight?: boolean;
|
||||
enableSearch?: boolean;
|
||||
closeOnClickOutside?: boolean;
|
||||
/**
|
||||
* 0 - By default
|
||||
* If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
|
||||
* then render items only when user typed more then or equal `searchRenderAfter` charachters
|
||||
*/
|
||||
searchRenderLimit?: number;
|
||||
/**
|
||||
* 3 - By default
|
||||
*/
|
||||
searchRenderAfter?: number;
|
||||
/**
|
||||
* 0 - By default
|
||||
* If >0 will render only N first items
|
||||
*/
|
||||
searchMaxLimit?: number;
|
||||
/**
|
||||
* 0 - By default
|
||||
* Used with searchMaxLimit to further limit rendering for optimization
|
||||
* Should be less than searchMaxLimit to take effect
|
||||
*/
|
||||
searchMaxRenderedItems?: number;
|
||||
checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
|
||||
buttonClasses?: string;
|
||||
itemClasses?: string;
|
||||
containerClasses?: string;
|
||||
selectionLimit?: number;
|
||||
minSelectionLimit?: number;
|
||||
closeOnSelect?: boolean;
|
||||
autoUnselect?: boolean;
|
||||
showCheckAll?: boolean;
|
||||
showUncheckAll?: boolean;
|
||||
fixedTitle?: boolean;
|
||||
dynamicTitleMaxItems?: number;
|
||||
maxHeight?: string;
|
||||
displayAllSelectedText?: boolean;
|
||||
isLazyLoad?: boolean;
|
||||
loadViewDistance?: number;
|
||||
stopScrollPropagation?: boolean;
|
||||
selectAddedValues?: boolean;
|
||||
/**
|
||||
* false - By default
|
||||
* If activated label IDs don't count and won't be written to the model.
|
||||
*/
|
||||
ignoreLabels?: boolean;
|
||||
/**
|
||||
* false - By default
|
||||
* If activated, the title will show selections in the order they were selected.
|
||||
*/
|
||||
maintainSelectionOrderInTitle?: boolean;
|
||||
/**
|
||||
* @default true
|
||||
* Set the focus back to the input control when the dropdown closed
|
||||
*/
|
||||
focusBack?: boolean;
|
||||
}
|
||||
|
||||
export interface IMultiSelectTexts {
|
||||
checkAll?: string;
|
||||
uncheckAll?: string;
|
||||
checked?: string;
|
||||
checkedPlural?: string;
|
||||
searchPlaceholder?: string;
|
||||
searchEmptyResult?: string;
|
||||
searchNoRenderText?: string;
|
||||
defaultTitle?: string;
|
||||
allSelected?: string;
|
||||
}
|
||||
+333
-509
File diff suppressed because it is too large.
Load diff
+324
-491
File diff suppressed because it is too large.
Load diff
+336
-512
File diff suppressed because it is too large.
Load diff
+333
-509
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+334
-510
File diff suppressed because it is too large.
Load diff
+359
-520
File diff suppressed because it is too large.
Load diff
+335
-511
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+327
-491
File diff suppressed because it is too large.
Load diff
+322
-489
File diff suppressed because it is too large.
Load diff
+327
-491
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+333
-509
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+327
-491
File diff suppressed because it is too large.
Load diff
+345
-521
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+332
-508
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+332
-509
File diff suppressed because it is too large.
Load diff
+334
-510
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+351
-593
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
+333
-494
File diff suppressed because it is too large.
Load diff
@@ -878,10 +878,6 @@
|
||||
<context context-type="sourcefile">src/app/bisq/bisq-transactions/bisq-transactions.component.html</context>
|
||||
<context context-type="linenumber">20,21</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
|
||||
<context context-type="linenumber">124,125</context>
|
||||
@@ -1267,14 +1263,14 @@
|
||||
<source>Trades</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
|
||||
<context context-type="linenumber">99</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="bisq-graph-volume" datatype="html">
|
||||
<source>Volume</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4b137ec8bf73a47063740b75c0c40d5fd3c48015" datatype="html">
|
||||
@@ -1922,6 +1918,15 @@
|
||||
<context context-type="linenumber">264,266</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="bc4c61d3713989e3c8c6610fca3ea1ca1cb19edb" datatype="html">
|
||||
<source>Value</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
</context-group>
|
||||
<note priority="1" from="description">Transaction value</note>
|
||||
<note priority="1" from="meaning">transaction.value</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="cb1b52c13b95fa29ea4044f2bbe0ac623b890c80" datatype="html">
|
||||
<source>Fee</source>
|
||||
<context-group purpose="location">
|
||||
|
||||
+328
-491
File diff suppressed because it is too large.
Load diff
@@ -1046,7 +1046,3 @@ th {
|
||||
box-shadow: -10px -15px 75px rgba(#eba814, 1);
|
||||
transition: 100ms all ease-in;
|
||||
}
|
||||
|
||||
.page-item {
|
||||
font-family: monospace;
|
||||
}
|
||||
@@ -82,11 +82,11 @@ pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-
|
||||
|
||||
### Node.js + npm
|
||||
|
||||
Build Node.js v16.16.0 and npm v8 from source using `nvm`:
|
||||
Build Node.js v16.15 and npm v8 from source using `nvm`:
|
||||
```
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
|
||||
source $HOME/.zshrc
|
||||
nvm install v16.16.0 --shared-zlib
|
||||
nvm install v16.15.0
|
||||
nvm alias default node
|
||||
```
|
||||
|
||||
|
||||
+242
-317
@@ -183,9 +183,6 @@ case $OS in
|
||||
TOR_PKG=tor
|
||||
TOR_USER=_tor
|
||||
TOR_GROUP=_tor
|
||||
NGINX_USER=www
|
||||
NGINX_ETC_FOLDER=/usr/local/etc/nginx
|
||||
NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf
|
||||
CERTBOT_PKG=py39-certbot
|
||||
;;
|
||||
|
||||
@@ -200,8 +197,6 @@ case $OS in
|
||||
TOR_USER=debian-tor
|
||||
TOR_GROUP=debian-tor
|
||||
CERTBOT_PKG=python3-certbot-nginx
|
||||
NGINX_USER=www-data
|
||||
NGINX_ETC_FOLDER=/etc/nginx
|
||||
NGINX_CONFIGURATION=/etc/nginx/nginx.conf
|
||||
;;
|
||||
esac
|
||||
@@ -651,193 +646,193 @@ ext4CreateDir()
|
||||
|
||||
# does bitcoin exist?
|
||||
|
||||
###########
|
||||
## dialog #
|
||||
###########
|
||||
#
|
||||
#: ${DIALOG=dialog}
|
||||
#
|
||||
#: ${DIALOG_OK=0}
|
||||
#: ${DIALOG_CANCEL=1}
|
||||
#: ${DIALOG_HELP=2}
|
||||
#: ${DIALOG_EXTRA=3}
|
||||
#: ${DIALOG_ITEM_HELP=4}
|
||||
#: ${DIALOG_ESC=255}
|
||||
#
|
||||
#: ${SIG_OFFNE=0}
|
||||
#: ${SIG_HUP=1}
|
||||
#: ${SIG_INT=2}
|
||||
#: ${SIG_QUIT=3}
|
||||
#: ${SIG_KILL=9}
|
||||
#: ${SIG_TERM=15}
|
||||
#
|
||||
#input=`tempfile 2>/dev/null` || input=/tmp/input$$
|
||||
#output=`tempfile 2>/dev/null` || output=/tmp/test$$
|
||||
#trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM
|
||||
#
|
||||
#DIALOG_ERROR=254
|
||||
#export DIALOG_ERROR
|
||||
#
|
||||
#backtitle="Mempool Fullnode Installer"
|
||||
#title="Mempool Fullnode Installer"
|
||||
#returncode=0
|
||||
#
|
||||
##################
|
||||
## dialog part 1 #
|
||||
##################
|
||||
#
|
||||
#$CUT >$input <<-EOF
|
||||
#Tor:Enable Tor v3 HS Onion:ON
|
||||
#Certbot:Enable HTTPS using Certbot:ON
|
||||
#Mainnet:Enable Bitcoin Mainnet:ON
|
||||
#Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
|
||||
#Testnet:Enable Bitcoin Testnet:ON
|
||||
#Liquid:Enable Elements Liquid:ON
|
||||
#Bisq:Enable Bisq:ON
|
||||
#Lightmode:Enable Electrs Lightmode to save disk space:ON
|
||||
#Smalldisk:Disable Electrs Compaction to save disk space:ON
|
||||
#Firewall:Enable Firewall:ON
|
||||
#EOF
|
||||
#
|
||||
#cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output
|
||||
#cat $output >$input
|
||||
#
|
||||
#$DIALOG --backtitle "${backtitle}" \
|
||||
# --title "${title}" "$@" \
|
||||
# --checklist "Toggle the features below to configure your fullnode:\n" \
|
||||
# 20 80 10 \
|
||||
# --file $input 2> $output
|
||||
#
|
||||
#retval=$?
|
||||
#
|
||||
#tempfile=$output
|
||||
#if [ $retval != $DIALOG_OK ];then
|
||||
# echo "Installation aborted."
|
||||
# exit 1
|
||||
#fi
|
||||
#
|
||||
#if grep Tor $tempfile >/dev/null 2>&1;then
|
||||
# TOR_INSTALL=ON
|
||||
#else
|
||||
# TOR_INSTALL=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Certbot $tempfile >/dev/null 2>&1;then
|
||||
# CERTBOT_INSTALL=ON
|
||||
#else
|
||||
# CERTBOT_INSTALL=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Mainnet $tempfile >/dev/null 2>&1;then
|
||||
# BITCOIN_MAINNET_ENABLE=ON
|
||||
#else
|
||||
# BITCOIN_MAINNET_ENABLE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
|
||||
# BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||
#else
|
||||
# BITCOIN_MAINNET_MINFEE_ENABLE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Testnet $tempfile >/dev/null 2>&1;then
|
||||
# BITCOIN_TESTNET_ENABLE=ON
|
||||
#else
|
||||
# BITCOIN_TESTNET_ENABLE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Liquid $tempfile >/dev/null 2>&1;then
|
||||
# ELEMENTS_INSTALL=ON
|
||||
# ELEMENTS_LIQUID_ENABLE=ON
|
||||
#else
|
||||
# ELEMENTS_INSTALL=OFF
|
||||
# ELEMENTS_LIQUID_ENABLE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Bisq $tempfile >/dev/null 2>&1;then
|
||||
# BISQ_INSTALL=ON
|
||||
# BISQ_MAINNET_ENABLE=ON
|
||||
#else
|
||||
# BISQ_INSTALL=OFF
|
||||
# BISQ_MAINNET_ENABLE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Lightmode $tempfile >/dev/null 2>&1;then
|
||||
# BITCOIN_ELECTRS_LIGHT_MODE=ON
|
||||
#else
|
||||
# BITCOIN_ELECTRS_LIGHT_MODE=OFF
|
||||
#fi
|
||||
#
|
||||
#if grep Smalldisk $tempfile >/dev/null 2>&1;then
|
||||
# BITCOIN_ELECTRS_LIGHT_MODE=ON
|
||||
#else
|
||||
# BITCOIN_ELECTRS_LIGHT_MODE=OFF
|
||||
#fi
|
||||
#
|
||||
##################
|
||||
## dialog part 2 #
|
||||
##################
|
||||
#
|
||||
#$DIALOG --cr-wrap \
|
||||
# --title "INPUT BOX" --clear \
|
||||
# --inputbox "$@" \
|
||||
#"Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile
|
||||
#HOSTNAME=$(cat $tempfile)
|
||||
#
|
||||
##################
|
||||
## dialog part 3 #
|
||||
##################
|
||||
#
|
||||
## --form text height width formheight
|
||||
## [ label y x item y x flen ilen ]
|
||||
# #"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \
|
||||
#
|
||||
#$DIALOG --ok-label "Submit" \
|
||||
# --backtitle "$backtitle" "$@" \
|
||||
# --form "Your fullnode will be installed as follows:" 0 0 0 \
|
||||
# "BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \
|
||||
# "BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \
|
||||
# "BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \
|
||||
# "BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \
|
||||
# "BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \
|
||||
# "BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \
|
||||
# "BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \
|
||||
# "BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \
|
||||
# "BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \
|
||||
# "BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \
|
||||
# "BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \
|
||||
# "BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \
|
||||
# "BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \
|
||||
# "BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \
|
||||
# "BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \
|
||||
# "ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \
|
||||
# "ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \
|
||||
# "ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \
|
||||
# "ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \
|
||||
# "ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \
|
||||
# "ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \
|
||||
# "MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \
|
||||
# "MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \
|
||||
# "MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \
|
||||
# "MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \
|
||||
# "MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \
|
||||
# "MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \
|
||||
# "MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \
|
||||
# "MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \
|
||||
# "MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \
|
||||
# "MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \
|
||||
# "MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \
|
||||
# "HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \
|
||||
# "TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \
|
||||
# "CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \
|
||||
#2> $tempfile
|
||||
#
|
||||
#retval=$?
|
||||
#
|
||||
#if [ $retval != $DIALOG_OK ];then
|
||||
# echo "Installation aborted."
|
||||
# exit 1
|
||||
#fi
|
||||
##########
|
||||
# dialog #
|
||||
##########
|
||||
|
||||
: ${DIALOG=dialog}
|
||||
|
||||
: ${DIALOG_OK=0}
|
||||
: ${DIALOG_CANCEL=1}
|
||||
: ${DIALOG_HELP=2}
|
||||
: ${DIALOG_EXTRA=3}
|
||||
: ${DIALOG_ITEM_HELP=4}
|
||||
: ${DIALOG_ESC=255}
|
||||
|
||||
: ${SIG_OFFNE=0}
|
||||
: ${SIG_HUP=1}
|
||||
: ${SIG_INT=2}
|
||||
: ${SIG_QUIT=3}
|
||||
: ${SIG_KILL=9}
|
||||
: ${SIG_TERM=15}
|
||||
|
||||
input=`tempfile 2>/dev/null` || input=/tmp/input$$
|
||||
output=`tempfile 2>/dev/null` || output=/tmp/test$$
|
||||
trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM
|
||||
|
||||
DIALOG_ERROR=254
|
||||
export DIALOG_ERROR
|
||||
|
||||
backtitle="Mempool Fullnode Installer"
|
||||
title="Mempool Fullnode Installer"
|
||||
returncode=0
|
||||
|
||||
#################
|
||||
# dialog part 1 #
|
||||
#################
|
||||
|
||||
$CUT >$input <<-EOF
|
||||
Tor:Enable Tor v3 HS Onion:ON
|
||||
Certbot:Enable HTTPS using Certbot:ON
|
||||
Mainnet:Enable Bitcoin Mainnet:ON
|
||||
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
|
||||
Testnet:Enable Bitcoin Testnet:ON
|
||||
Liquid:Enable Elements Liquid:ON
|
||||
Bisq:Enable Bisq:ON
|
||||
Lightmode:Enable Electrs Lightmode to save disk space:ON
|
||||
Smalldisk:Disable Electrs Compaction to save disk space:ON
|
||||
Firewall:Enable Firewall:ON
|
||||
EOF
|
||||
|
||||
cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output
|
||||
cat $output >$input
|
||||
|
||||
$DIALOG --backtitle "${backtitle}" \
|
||||
--title "${title}" "$@" \
|
||||
--checklist "Toggle the features below to configure your fullnode:\n" \
|
||||
20 80 10 \
|
||||
--file $input 2> $output
|
||||
|
||||
retval=$?
|
||||
|
||||
tempfile=$output
|
||||
if [ $retval != $DIALOG_OK ];then
|
||||
echo "Installation aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep Tor $tempfile >/dev/null 2>&1;then
|
||||
TOR_INSTALL=ON
|
||||
else
|
||||
TOR_INSTALL=OFF
|
||||
fi
|
||||
|
||||
if grep Certbot $tempfile >/dev/null 2>&1;then
|
||||
CERTBOT_INSTALL=ON
|
||||
else
|
||||
CERTBOT_INSTALL=OFF
|
||||
fi
|
||||
|
||||
if grep Mainnet $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_MAINNET_ENABLE=ON
|
||||
else
|
||||
BITCOIN_MAINNET_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||
else
|
||||
BITCOIN_MAINNET_MINFEE_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Testnet $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_TESTNET_ENABLE=ON
|
||||
else
|
||||
BITCOIN_TESTNET_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Liquid $tempfile >/dev/null 2>&1;then
|
||||
ELEMENTS_INSTALL=ON
|
||||
ELEMENTS_LIQUID_ENABLE=ON
|
||||
else
|
||||
ELEMENTS_INSTALL=OFF
|
||||
ELEMENTS_LIQUID_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Bisq $tempfile >/dev/null 2>&1;then
|
||||
BISQ_INSTALL=ON
|
||||
BISQ_MAINNET_ENABLE=ON
|
||||
else
|
||||
BISQ_INSTALL=OFF
|
||||
BISQ_MAINNET_ENABLE=OFF
|
||||
fi
|
||||
|
||||
if grep Lightmode $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_ELECTRS_LIGHT_MODE=ON
|
||||
else
|
||||
BITCOIN_ELECTRS_LIGHT_MODE=OFF
|
||||
fi
|
||||
|
||||
if grep Smalldisk $tempfile >/dev/null 2>&1;then
|
||||
BITCOIN_ELECTRS_LIGHT_MODE=ON
|
||||
else
|
||||
BITCOIN_ELECTRS_LIGHT_MODE=OFF
|
||||
fi
|
||||
|
||||
#################
|
||||
# dialog part 2 #
|
||||
#################
|
||||
|
||||
$DIALOG --cr-wrap \
|
||||
--title "INPUT BOX" --clear \
|
||||
--inputbox "$@" \
|
||||
"Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile
|
||||
HOSTNAME=$(cat $tempfile)
|
||||
|
||||
#################
|
||||
# dialog part 3 #
|
||||
#################
|
||||
|
||||
# --form text height width formheight
|
||||
# [ label y x item y x flen ilen ]
|
||||
#"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \
|
||||
|
||||
$DIALOG --ok-label "Submit" \
|
||||
--backtitle "$backtitle" "$@" \
|
||||
--form "Your fullnode will be installed as follows:" 0 0 0 \
|
||||
"BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \
|
||||
"BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \
|
||||
"BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \
|
||||
"BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \
|
||||
"BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \
|
||||
"BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \
|
||||
"BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \
|
||||
"BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \
|
||||
"BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \
|
||||
"BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \
|
||||
"BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \
|
||||
"BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \
|
||||
"BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \
|
||||
"BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \
|
||||
"BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \
|
||||
"ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \
|
||||
"ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \
|
||||
"ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \
|
||||
"ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \
|
||||
"ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \
|
||||
"ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \
|
||||
"MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \
|
||||
"MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \
|
||||
"MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \
|
||||
"MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \
|
||||
"MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \
|
||||
"MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \
|
||||
"MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \
|
||||
"MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \
|
||||
"MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \
|
||||
"MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \
|
||||
"MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \
|
||||
"HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \
|
||||
"TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \
|
||||
"CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \
|
||||
2> $tempfile
|
||||
|
||||
retval=$?
|
||||
|
||||
if [ $retval != $DIALOG_OK ];then
|
||||
echo "Installation aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
############################
|
||||
# START DOING ACTUAL STUFF #
|
||||
@@ -846,6 +841,8 @@ ext4CreateDir()
|
||||
date
|
||||
echo "[*] Mempool installation script for ${OS}"
|
||||
|
||||
set -x
|
||||
|
||||
###################################
|
||||
# create filesystems if necessary #
|
||||
###################################
|
||||
@@ -909,7 +906,7 @@ echo "[*] Installing nvm.sh from GitHub"
|
||||
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
|
||||
|
||||
echo "[*] Building NodeJS via nvm.sh"
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.16.0 --shared-zlib'
|
||||
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.15.0'
|
||||
|
||||
####################
|
||||
# Tor installation #
|
||||
@@ -928,8 +925,6 @@ if [ "${TOR_INSTALL}" = ON ];then
|
||||
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${MEMPOOL_TOR_HS}/ >> ${TOR_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:81 >> ${TOR_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONFIGURATION}"
|
||||
else
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__TOR_RESOURCES__!${TOR_RESOURCES}!" "${TOR_CONFIGURATION}"
|
||||
fi
|
||||
|
||||
case $OS in
|
||||
@@ -941,7 +936,7 @@ if [ "${TOR_INSTALL}" = ON ];then
|
||||
|
||||
# start tor now so it can bootstrap in time for bitcoin starting a few mins later
|
||||
echo "[*] Starting Tor service"
|
||||
osSudo "${ROOT_USER}" service tor restart
|
||||
osSudo "${ROOT_USER}" service tor start
|
||||
fi
|
||||
|
||||
########################
|
||||
@@ -1287,25 +1282,7 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||
;;
|
||||
|
||||
Debian)
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/elements-liquid.service" "${DEBIAN_SERVICE_HOME}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
#######################################
|
||||
# Bitcoin instance for Liquid Testnet #
|
||||
#######################################
|
||||
|
||||
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Liquid service"
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
echo "[*] FIXME: Bitcoin Liquid service must be installed manually on FreeBSD"
|
||||
;;
|
||||
|
||||
Debian)
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/elements-liquidtestnet.service" "${DEBIAN_SERVICE_HOME}"
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/liquid.service" "${DEBIAN_SERVICE_HOME}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -1318,6 +1295,18 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Mainnet electrs start script"
|
||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
|
||||
|
||||
echo "[*] Installing Bitcoin crontab"
|
||||
case $OS in
|
||||
FreeBSD)
|
||||
echo [*] FIXME: must only crontab enabled daemons
|
||||
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
|
||||
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
|
||||
;;
|
||||
Debian)
|
||||
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||
@@ -1332,6 +1321,13 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Testnet electrs start script"
|
||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
|
||||
|
||||
case $OS in
|
||||
Debian)
|
||||
echo "[*] Installing Bitcoin-testnet crontab"
|
||||
(crontab -l ; echo "@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||
@@ -1346,6 +1342,13 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Bitcoin Signet electrs start script"
|
||||
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
|
||||
|
||||
case $OS in
|
||||
Debian)
|
||||
echo "[*] Installing Bitcoin-signet crontab"
|
||||
(crontab -l ; echo "@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||
@@ -1363,9 +1366,12 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Elements crontab"
|
||||
case $OS in
|
||||
FreeBSD)
|
||||
echo "[*] FIXME: must only crontab enabled daemons"
|
||||
echo [*] FIXME: must only crontab enabled daemons
|
||||
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
|
||||
;;
|
||||
Debian)
|
||||
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
|
||||
@@ -1382,6 +1388,13 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Elements Liquid Testnet electrs start script"
|
||||
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
|
||||
|
||||
case $OS in
|
||||
Debian)
|
||||
echo "[*] Installing Elements-testnet crontab"
|
||||
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] Installing Elements Liquid Testnet RPC credentials"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||
@@ -1394,45 +1407,6 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
||||
fi
|
||||
|
||||
################################
|
||||
# Install all Electrs Cronjobs #
|
||||
################################
|
||||
echo "[*] Installing crontabs"
|
||||
case $OS in
|
||||
FreeBSD)
|
||||
echo "[*] FIXME: must only crontab enabled daemons"
|
||||
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
|
||||
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
|
||||
;;
|
||||
Debian)
|
||||
crontab_bitcoin=()
|
||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Electrs Mainnet Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n"
|
||||
fi
|
||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Electrs Testnet Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n"
|
||||
fi
|
||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Electrs Signet Cronjob"
|
||||
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n"
|
||||
fi
|
||||
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
|
||||
|
||||
crontab_elements=()
|
||||
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Liquid Asset Mainnet Cronjob"
|
||||
crontab_elements+="6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1\n"
|
||||
fi
|
||||
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||
echo "[*] Installing Liquid Asset Testnet Cronjob"
|
||||
crontab_elements+="6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1\n"
|
||||
fi
|
||||
echo "${crontab_elements}" | crontab -u "${ELEMENTS_USER}" -
|
||||
;;
|
||||
esac
|
||||
|
||||
#####################################
|
||||
# Bisq instance for Bitcoin Mainnet #
|
||||
#####################################
|
||||
@@ -1531,24 +1505,19 @@ _EOF_
|
||||
|
||||
##### nginx
|
||||
|
||||
|
||||
echo "[*] Read tor v3 onion hostnames"
|
||||
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
|
||||
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
|
||||
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
|
||||
|
||||
echo "[*] Adding Nginx configuration"
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
|
||||
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
|
||||
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api
|
||||
ln -s /mempool/mempool /etc/nginx/mempool
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_USER__!${NGINX_USER}!" "${NGINX_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_ETC_FOLDER__!${NGINX_ETC_FOLDER}!" "${NGINX_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
|
||||
echo "[*] Restarting Nginx"
|
||||
osSudo "${ROOT_USER}" service nginx restart
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
echo "[*] FIXME: nginx must be configured manually on FreeBSD"
|
||||
;;
|
||||
|
||||
Debian)
|
||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
|
||||
#echo "[*] Restarting Nginx"
|
||||
#osSudo "${ROOT_USER}" service nginx restart
|
||||
;;
|
||||
esac
|
||||
|
||||
##### OS systemd
|
||||
|
||||
@@ -1579,40 +1548,11 @@ case $OS in
|
||||
osSudo "${ROOT_USER}" systemctl enable bisq.service
|
||||
fi
|
||||
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" systemctl enable elements-liquid.service
|
||||
fi
|
||||
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||
osSudo "${ROOT_USER}" systemctl enable elements-liquidtestnet.service
|
||||
osSudo "${ROOT_USER}" systemctl enable liquid.service
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
##### OS set Linux user ulimits
|
||||
|
||||
echo "[*] Setting ulimits for users"
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
;;
|
||||
|
||||
Debian)
|
||||
cat >> /etc/security/limits.conf <<EOF
|
||||
* soft nproc 200000
|
||||
* hard nproc 200000
|
||||
* soft nofile 200000
|
||||
* hard nofile 200000
|
||||
EOF
|
||||
echo "session required pam_limits.so" >> /etc/pam.d/common-session
|
||||
;;
|
||||
esac
|
||||
|
||||
##### Build Mempool
|
||||
|
||||
echo "[*] Build Mempool"
|
||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade"
|
||||
|
||||
|
||||
|
||||
##### OS services
|
||||
|
||||
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||
@@ -1688,21 +1628,6 @@ osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade"
|
||||
|
||||
##### finish
|
||||
|
||||
case $OS in
|
||||
|
||||
FreeBSD)
|
||||
;;
|
||||
|
||||
Debian)
|
||||
echo "This are the generated Tor addresses:"
|
||||
echo "${NGINX_MEMPOOL_ONION}"
|
||||
echo "${NGINX_BISQ_ONION}"
|
||||
echo "${NGINX_LIQUID_ONION}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo
|
||||
echo 'Please reboot to start all the services.'
|
||||
echo '[*] Done!'
|
||||
|
||||
exit 0
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
[Unit]
|
||||
Description=Elementsd-liquid
|
||||
Description=Elementsd
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements-liquid.pid
|
||||
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-testnet.pid
|
||||
ExecStop=/usr/local/bin/elements-cli stop
|
||||
|
||||
Type=forking
|
||||
PIDFile=/elements/elements-liquid.pid
|
||||
PIDFile=/elements/elements-testnet.pid
|
||||
Restart=on-failure
|
||||
|
||||
User=elements
|
||||
@@ -1,13 +1,13 @@
|
||||
[Unit]
|
||||
Description=Elementsd-liquidtestnet
|
||||
Description=Elementsd
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-liquidtestnet.pid
|
||||
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements.pid
|
||||
ExecStop=/usr/local/bin/elements-cli stop
|
||||
|
||||
Type=forking
|
||||
PIDFile=/elements/elements-liquidtestnet.pid
|
||||
PIDFile=/elements/elements.pid
|
||||
Restart=on-failure
|
||||
|
||||
User=elements
|
||||
@@ -56,7 +56,7 @@ build_frontend()
|
||||
if [ ! -e "mempool-frontend-config.json" ];then
|
||||
cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json"
|
||||
fi
|
||||
npm install --omit=dev --omit=optional || exit 1
|
||||
npm install --no-optional || exit 1
|
||||
npm run build || exit 1
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ build_backend()
|
||||
-e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \
|
||||
"mempool-config.json"
|
||||
fi
|
||||
npm install --omit=dev --omit=optional || exit 1
|
||||
npm install --no-optional || exit 1
|
||||
npm run build || exit 1
|
||||
}
|
||||
|
||||
|
||||
+56
-24
@@ -1,4 +1,4 @@
|
||||
user __NGINX_USER__;
|
||||
user nobody;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
worker_processes auto;
|
||||
@@ -10,11 +10,11 @@ events {
|
||||
}
|
||||
|
||||
http {
|
||||
# DNS servers for on-demand resolution, change if desired
|
||||
# DNS servers for on-demand recursive resolver
|
||||
resolver 8.8.8.8;
|
||||
|
||||
# include default mime types
|
||||
include __NGINX_ETC_FOLDER__/mime.types;
|
||||
include /usr/local/etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# HTTP basic configuration
|
||||
@@ -32,13 +32,9 @@ http {
|
||||
# MEMPOOL.NINJA
|
||||
server {
|
||||
# clearnet v4/v6
|
||||
#listen 443 ssl http2;
|
||||
#listen [::]:443 ssl http2;
|
||||
server_name _;
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:81;
|
||||
set $onion "__NGINX_MEMPOOL_ONION__";
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name mempool.ninja;
|
||||
|
||||
# for services from mempool.space like contributors on about page
|
||||
set $mempoolSpaceServices "https://mempool.space";
|
||||
@@ -56,24 +52,30 @@ http {
|
||||
set $esploraTestnet "http://esplora-bitcoin-testnet";
|
||||
set $esploraSignet "http://esplora-bitcoin-signet";
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:81;
|
||||
set $onion "mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad";
|
||||
|
||||
# filesystem paths
|
||||
root /mempool/public_html/mainnet/;
|
||||
access_log /var/log/nginx/mempool-access.log;
|
||||
error_log /var/log/nginx/mempool-error.log;
|
||||
|
||||
# ssl configuration
|
||||
ssl_certificate /usr/local/etc/letsencrypt/live/mempool.ninja/fullchain.pem;
|
||||
ssl_certificate_key /usr/local/etc/letsencrypt/live/mempool.ninja/privkey.pem;
|
||||
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# site configuration
|
||||
include mempool/production/nginx/server-mempool.conf;
|
||||
}
|
||||
# BISQ.NINJA
|
||||
server {
|
||||
# clearnet v4/v6
|
||||
#listen 443 ssl http2;
|
||||
#listen [::]:443 ssl http2;
|
||||
server_name _;
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:82;
|
||||
set $onion "__NGINX_BISQ_ONION__";
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name bisq.ninja;
|
||||
|
||||
# for services from mempool.space like contributors on about page
|
||||
set $mempoolSpaceServices "https://mempool.space";
|
||||
@@ -84,24 +86,30 @@ http {
|
||||
# for blockstream/esplora daemon, see upstream-esplora.conf
|
||||
set $esploraMainnet "http://esplora-bitcoin-mainnet";
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:82;
|
||||
set $onion "bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd";
|
||||
|
||||
# filesystem paths
|
||||
root /mempool/public_html/bisq/;
|
||||
access_log /var/log/nginx/bisq-access.log;
|
||||
error_log /var/log/nginx/bisq-error.log;
|
||||
|
||||
# ssl configuration
|
||||
ssl_certificate /usr/local/etc/letsencrypt/live/bisq.ninja/fullchain.pem;
|
||||
ssl_certificate_key /usr/local/etc/letsencrypt/live/bisq.ninja/privkey.pem;
|
||||
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# site configuration
|
||||
include mempool/production/nginx/server-bisq.conf;
|
||||
}
|
||||
# LIQUID.PLACE
|
||||
server {
|
||||
# clearnet v4/v6
|
||||
#listen 443 ssl http2;
|
||||
#listen [::]:443 ssl http2;
|
||||
server_name _;
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:83;
|
||||
set $onion "__NGINX_LIQUID_ONION__";
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name liquid.place;
|
||||
|
||||
# for services from mempool.space like contributors on about page
|
||||
set $mempoolSpaceServices "https://mempool.space";
|
||||
@@ -114,12 +122,36 @@ http {
|
||||
set $esploraMainnet "http://esplora-liquid-mainnet";
|
||||
set $esploraTestnet "http://esplora-liquid-testnet";
|
||||
|
||||
# tor v3
|
||||
listen 127.0.0.1:83;
|
||||
set $onion "liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd";
|
||||
|
||||
# filesystem paths
|
||||
root /mempool/public_html/liquid/;
|
||||
access_log /var/log/nginx/liquid-access.log;
|
||||
error_log /var/log/nginx/liquid-error.log;
|
||||
|
||||
# ssl configuration
|
||||
ssl_certificate /usr/local/etc/letsencrypt/live/liquid.place/fullchain.pem;
|
||||
ssl_certificate_key /usr/local/etc/letsencrypt/live/liquid.place/privkey.pem;
|
||||
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# site configuration
|
||||
include mempool/production/nginx/server-liquid.conf;
|
||||
}
|
||||
# HTTP to HTTPS redirect
|
||||
server {
|
||||
# clearnet v4/v6
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
# only redirect for our hosted domains
|
||||
if ($host ~ "^(mempool.ninja|bisq.ninja|liquid.place)$")
|
||||
{
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
return 503;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -3,21 +3,21 @@ SOCKSPort 9050
|
||||
ControlPort 9051
|
||||
Log notice syslog
|
||||
|
||||
DataDirectory __TOR_RESOURCES__
|
||||
DataDirectory /var/db/tor
|
||||
DataDirectoryGroupReadable 1
|
||||
|
||||
CookieAuthentication 1
|
||||
CookieAuthFile __TOR_RESOURCES__/control_auth_cookie
|
||||
CookieAuthFile /var/db/tor/control_auth_cookie
|
||||
CookieAuthFileGroupReadable 1
|
||||
|
||||
HiddenServiceDir __TOR_RESOURCES__/mempool
|
||||
HiddenServiceDir /var/db/tor/mempool
|
||||
HiddenServicePort 80 127.0.0.1:81
|
||||
HiddenServiceVersion 3
|
||||
|
||||
HiddenServiceDir __TOR_RESOURCES__/bisq
|
||||
HiddenServiceDir /var/db/tor/bisq
|
||||
HiddenServicePort 80 127.0.0.1:82
|
||||
HiddenServiceVersion 3
|
||||
|
||||
HiddenServiceDir __TOR_RESOURCES__/liquid
|
||||
HiddenServiceDir /var/db/tor/liquid
|
||||
HiddenServicePort 80 127.0.0.1:83
|
||||
HiddenServiceVersion 3
|
||||
Reference in new issue
Block a user