mirror of
https://github.com/Retropex/umbrel-bitcoin.git
synced 2025-05-28 21:12:30 +02:00
Allow tweaking bitcoin.conf directly from the UI
Co-authored-by: Luke Childs <lukechilds123@gmail.com> Co-authored-by: Mayank Chhabra <mayankchhabra9@gmail.com> Co-authored-by: Steven Briscoe <me@stevenbriscoe.com>
This commit is contained in:
parent
66c88969de
commit
cc5e1fd98d
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,4 +10,5 @@ lb_settings.json
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
coverage
|
coverage
|
||||||
.todo
|
.todo
|
||||||
bitcoin
|
data/
|
||||||
|
.env.local
|
40
bin/www
40
bin/www
@ -8,6 +8,11 @@ var app = require('../app');
|
|||||||
var debug = require('debug')('nodejs-regular-webapp2:server');
|
var debug = require('debug')('nodejs-regular-webapp2:server');
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
|
|
||||||
|
const diskLogic = require('../logic/disk');
|
||||||
|
const diskService = require('../services/disk');
|
||||||
|
const bitcoindLogic = require('../logic/bitcoind');
|
||||||
|
const constants = require('../utils/const');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
*/
|
*/
|
||||||
@ -29,6 +34,34 @@ server.listen(port);
|
|||||||
server.on('error', onError);
|
server.on('error', onError);
|
||||||
server.on('listening', onListening);
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to create default bitcoin core config files if they do not already exist.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function createConfFilesAndRestartBitcoind() {
|
||||||
|
console.log('umbrel-bitcoin.conf does not exist, creating config files with Umbrel default values');
|
||||||
|
const config = await diskLogic.getJsonStore();
|
||||||
|
|
||||||
|
// set torProxyForClearnet to false for existing installs
|
||||||
|
if (constants.BITCOIN_INITIALIZE_WITH_CLEARNET_OVER_TOR) config.torProxyForClearnet = true;
|
||||||
|
|
||||||
|
await diskLogic.applyCustomBitcoinConfig(config);
|
||||||
|
|
||||||
|
const MAX_TRIES = 60;
|
||||||
|
let tries = 0;
|
||||||
|
|
||||||
|
while (tries < MAX_TRIES) {
|
||||||
|
try {
|
||||||
|
await bitcoindLogic.stop();
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
tries++;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize a port into a number, string, or false.
|
* Normalize a port into a number, string, or false.
|
||||||
*/
|
*/
|
||||||
@ -81,11 +114,16 @@ function onError(error) {
|
|||||||
* Event listener for HTTP server "listening" event.
|
* Event listener for HTTP server "listening" event.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onListening() {
|
async function onListening() {
|
||||||
var addr = server.address();
|
var addr = server.address();
|
||||||
var bind = typeof addr === 'string'
|
var bind = typeof addr === 'string'
|
||||||
? 'pipe ' + addr
|
? 'pipe ' + addr
|
||||||
: 'port ' + addr.port;
|
: 'port ' + addr.port;
|
||||||
debug('Listening on ' + bind);
|
debug('Listening on ' + bind);
|
||||||
console.log('Listening on ' + bind);
|
console.log('Listening on ' + bind);
|
||||||
|
|
||||||
|
// if umbrel-bitcoin.conf does not exist, create default bitcoin core config files and restart bitcoind.
|
||||||
|
if (! await diskService.fileExists(constants.UMBREL_BITCOIN_CONF_FILEPATH)) {
|
||||||
|
createConfFilesAndRestartBitcoind();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,48 @@
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
i2pd_daemon:
|
||||||
|
container_name: i2pd_daemon
|
||||||
|
image: purplei2p/i2pd:release-2.44.0@sha256:d154a599793c393cf9c91f8549ba7ece0bb40e5728e1813aa6dd4c210aa606f6
|
||||||
|
user: "1000:1000"
|
||||||
|
command: --sam.enabled=true --sam.address=0.0.0.0 --sam.port=7656 --loglevel=error
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ${PWD}/data/i2pd:/home/i2pd/data
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: "10.21.0.2"
|
||||||
|
|
||||||
|
tor_server:
|
||||||
|
container_name: tor_server
|
||||||
|
image: getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a
|
||||||
|
user: "1000:1000"
|
||||||
|
restart: on-failure
|
||||||
|
entrypoint: /tor-entrypoint/tor-entrypoint.sh
|
||||||
|
volumes:
|
||||||
|
- ${PWD}/data/tor:/etc/tor
|
||||||
|
- ${PWD}/tor-entrypoint:/tor-entrypoint
|
||||||
|
environment:
|
||||||
|
HOME: "/tmp"
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: "10.21.0.3"
|
||||||
|
|
||||||
bitcoind:
|
bitcoind:
|
||||||
image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507
|
image: lncm/bitcoind:v24.0@sha256:db19fe46f30acd3854f4f0d239278137d828ce3728f925c8d92faaab1ba8556a
|
||||||
command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=umbrel:5071d8b3ba93e53e414446ff9f1b7d7b$$375e9731abd2cd2c2c44d2327ec19f4f2644256fdeaf4fc5229bf98b778aafec
|
user: "1000:1000"
|
||||||
|
command: -port=8333 -rpcport=8332 -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=umbrel:5071d8b3ba93e53e414446ff9f1b7d7b$$375e9731abd2cd2c2c44d2327ec19f4f2644256fdeaf4fc5229bf98b778aafec
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/data/bitcoin:/data/.bitcoin
|
- ${PWD}/data/bitcoin:/data/.bitcoin
|
||||||
restart: on-failure
|
restart: unless-stopped
|
||||||
stop_grace_period: 15m30s
|
stop_grace_period: 15m30s
|
||||||
ports:
|
ports:
|
||||||
- "18443:18443" # regtest
|
- "8332:8332" # rpc
|
||||||
|
- "8333:8333" # p2p
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: "10.21.0.4"
|
||||||
|
|
||||||
server:
|
server:
|
||||||
build: .
|
build: .
|
||||||
depends_on: [bitcoind]
|
depends_on: [bitcoind]
|
||||||
@ -17,12 +50,35 @@ services:
|
|||||||
restart: on-failure
|
restart: on-failure
|
||||||
ports:
|
ports:
|
||||||
- "3005:3005"
|
- "3005:3005"
|
||||||
|
volumes:
|
||||||
|
- ${PWD}/data/app:/data # volume to persist advanced settings json
|
||||||
|
- ${PWD}/data/bitcoin:/bitcoin/.bitcoin # volume to persist umbrel-bitcoin.conf and bitcoin.conf
|
||||||
environment:
|
environment:
|
||||||
PORT: "3005"
|
PORT: "3005"
|
||||||
BITCOIN_HOST: "bitcoind"
|
BITCOIN_HOST: "bitcoind"
|
||||||
RPC_PORT: "18443" # - regtest
|
BITCOIN_P2P_PORT: 8333
|
||||||
|
BITCOIN_RPC_PORT: 8332
|
||||||
|
BITCOIN_DEFAULT_NETWORK: "regtest"
|
||||||
RPC_USER: "umbrel"
|
RPC_USER: "umbrel"
|
||||||
RPC_PASSWORD: "moneyprintergobrrr"
|
RPC_PASSWORD: "moneyprintergobrrr"
|
||||||
BITCOIN_RPC_HIDDEN_SERVICE: "somehiddenservice.onion"
|
BITCOIN_RPC_HIDDEN_SERVICE: "somehiddenservice.onion"
|
||||||
BITCOIN_P2P_HIDDEN_SERVICE: "anotherhiddenservice.onion"
|
BITCOIN_P2P_HIDDEN_SERVICE: "anotherhiddenservice.onion"
|
||||||
DEVICE_DOMAIN_NAME: "test.local"
|
DEVICE_DOMAIN_NAME: "test.local"
|
||||||
|
BITCOIND_IP: "10.21.0.4"
|
||||||
|
TOR_PROXY_IP: "10.21.0.3"
|
||||||
|
TOR_PROXY_PORT: "9050"
|
||||||
|
TOR_PROXY_CONTROL_PORT: "9051"
|
||||||
|
TOR_PROXY_CONTROL_PASSWORD: "umbrelisneat"
|
||||||
|
I2P_DAEMON_IP: "10.21.0.2"
|
||||||
|
I2P_DAEMON_PORT: "7656"
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: "10.21.0.5"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: advanced_settings_test_network
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: "10.21.0.0/16"
|
@ -12,20 +12,34 @@ async function getConnectionsCount() {
|
|||||||
|
|
||||||
var outBoundConnections = 0;
|
var outBoundConnections = 0;
|
||||||
var inBoundConnections = 0;
|
var inBoundConnections = 0;
|
||||||
|
var clearnetConnections = 0;
|
||||||
|
var torConnections = 0;
|
||||||
|
var i2pConnections = 0;
|
||||||
|
|
||||||
peerInfo.result.forEach(function(peer) {
|
peerInfo.result.forEach(function(peer) {
|
||||||
if (peer.inbound === false) {
|
if (peer.inbound === false) {
|
||||||
outBoundConnections++;
|
outBoundConnections++;
|
||||||
|
} else {
|
||||||
return;
|
inBoundConnections++;
|
||||||
}
|
}
|
||||||
inBoundConnections++;
|
|
||||||
|
if (peer.network === "onion") {
|
||||||
|
torConnections++;
|
||||||
|
} else if (peer.network === "i2p") {
|
||||||
|
i2pConnections++;
|
||||||
|
} else {
|
||||||
|
// ipv4 and ipv6 are clearnet
|
||||||
|
clearnetConnections++;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const connections = {
|
const connections = {
|
||||||
total: inBoundConnections + outBoundConnections,
|
total: inBoundConnections + outBoundConnections,
|
||||||
inbound: inBoundConnections,
|
inbound: inBoundConnections,
|
||||||
outbound: outBoundConnections
|
outbound: outBoundConnections,
|
||||||
|
clearnet: clearnetConnections,
|
||||||
|
tor: torConnections,
|
||||||
|
i2p: i2pConnections
|
||||||
};
|
};
|
||||||
|
|
||||||
return connections;
|
return connections;
|
||||||
@ -72,12 +86,16 @@ async function getLocalSyncInfo() {
|
|||||||
var blockCount = blockChainInfo.blocks;
|
var blockCount = blockChainInfo.blocks;
|
||||||
var headerCount = blockChainInfo.headers;
|
var headerCount = blockChainInfo.headers;
|
||||||
var percent = blockChainInfo.verificationprogress;
|
var percent = blockChainInfo.verificationprogress;
|
||||||
|
var pruned = blockChainInfo.pruned;
|
||||||
|
var pruneTargetSize = blockChainInfo.pruneTargetSize;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chain,
|
chain,
|
||||||
percent,
|
percent,
|
||||||
currentBlock: blockCount,
|
currentBlock: blockCount,
|
||||||
headerCount: headerCount // eslint-disable-line object-shorthand,
|
headerCount: headerCount, // eslint-disable-line object-shorthand,
|
||||||
|
pruned,
|
||||||
|
pruneTargetSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +110,7 @@ async function getSyncStatus() {
|
|||||||
return localSyncInfo;
|
return localSyncInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - consider using getNetworkInfo for info on proxy for ipv4 and ipv6
|
||||||
async function getVersion() {
|
async function getVersion() {
|
||||||
const networkInfo = await bitcoindService.getNetworkInfo();
|
const networkInfo = await bitcoindService.getNetworkInfo();
|
||||||
const unformattedVersion = networkInfo.result.subversion;
|
const unformattedVersion = networkInfo.result.subversion;
|
||||||
@ -259,6 +278,11 @@ async function nodeStatusSummary() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function stop() {
|
||||||
|
const stopResponse = await bitcoindService.stop();
|
||||||
|
return {stopResponse};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getBlockHash,
|
getBlockHash,
|
||||||
getTransaction,
|
getTransaction,
|
||||||
@ -273,5 +297,6 @@ module.exports = {
|
|||||||
getSyncStatus,
|
getSyncStatus,
|
||||||
getVersion,
|
getVersion,
|
||||||
nodeStatusDump,
|
nodeStatusDump,
|
||||||
nodeStatusSummary
|
nodeStatusSummary,
|
||||||
|
stop
|
||||||
};
|
};
|
||||||
|
176
logic/disk.js
Normal file
176
logic/disk.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const constants = require("utils/const.js");
|
||||||
|
const diskService = require("services/disk");
|
||||||
|
|
||||||
|
// TODO - consider moving these unit conversions to utils/const.js
|
||||||
|
const GB_TO_MiB = 953.674;
|
||||||
|
const MB_TO_MiB = 0.953674;
|
||||||
|
|
||||||
|
const DEFAULT_ADVANCED_SETTINGS = {
|
||||||
|
clearnet: true,
|
||||||
|
torProxyForClearnet: false,
|
||||||
|
tor: true,
|
||||||
|
i2p: true,
|
||||||
|
incomingConnections: false,
|
||||||
|
cacheSizeMB: 450,
|
||||||
|
mempoolFullRbf: false,
|
||||||
|
prune: {
|
||||||
|
enabled: false,
|
||||||
|
pruneSizeGB: 300,
|
||||||
|
},
|
||||||
|
reindex: false,
|
||||||
|
network: constants.BITCOIN_DEFAULT_NETWORK
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getJsonStore() {
|
||||||
|
try {
|
||||||
|
const jsonStore = await diskService.readJsonFile(constants.JSON_STORE_FILE);
|
||||||
|
return { ...DEFAULT_ADVANCED_SETTINGS, ...jsonStore };
|
||||||
|
} catch (error) {
|
||||||
|
return DEFAULT_ADVANCED_SETTINGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyCustomBitcoinConfig(bitcoinConfig) {
|
||||||
|
await applyBitcoinConfig(bitcoinConfig, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyDefaultBitcoinConfig() {
|
||||||
|
await applyBitcoinConfig(DEFAULT_ADVANCED_SETTINGS, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyBitcoinConfig(bitcoinConfig, shouldOverwriteExistingFile) {
|
||||||
|
await Promise.all([
|
||||||
|
updateJsonStore(bitcoinConfig),
|
||||||
|
generateUmbrelBitcoinConfig(bitcoinConfig),
|
||||||
|
generateBitcoinConfig(shouldOverwriteExistingFile),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's a race condition here if you do two updates in parallel but it's fine for our current use case
|
||||||
|
async function updateJsonStore(newProps) {
|
||||||
|
const jsonStore = await getJsonStore();
|
||||||
|
return diskService.writeJsonFile(constants.JSON_STORE_FILE, {
|
||||||
|
...jsonStore,
|
||||||
|
...newProps
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates umbrel-bitcoin.conf
|
||||||
|
function generateUmbrelBitcoinConfig(settings) {
|
||||||
|
const confString = settingsToMultilineConfString(settings);
|
||||||
|
return diskService.writePlainTextFile(constants.UMBREL_BITCOIN_CONF_FILEPATH, confString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates bitcoin.conf with includeconf=umbrel-bitcoin.conf
|
||||||
|
async function generateBitcoinConfig(shouldOverwriteExistingFile = false) {
|
||||||
|
const baseName = path.basename(constants.UMBREL_BITCOIN_CONF_FILEPATH);
|
||||||
|
const includeConfString = `# Load additional configuration file, relative to the data directory.\nincludeconf=${baseName}`;
|
||||||
|
|
||||||
|
const fileExists = await diskService.fileExists(constants.BITCOIN_CONF_FILEPATH);
|
||||||
|
|
||||||
|
// if bitcoin.conf does not exist or should be overwritten, create it with includeconf=umbrel-bitcoin.conf
|
||||||
|
if (!fileExists || shouldOverwriteExistingFile) {
|
||||||
|
return await diskService.writePlainTextFile(constants.BITCOIN_CONF_FILEPATH, includeConfString);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfContents = await diskService.readUtf8File(constants.BITCOIN_CONF_FILEPATH);
|
||||||
|
|
||||||
|
// if bitcoin.conf exists but does not include includeconf=umbrel-bitcoin.conf, add includeconf=umbrel-bitcoin.conf to the top of the file
|
||||||
|
if (!existingConfContents.includes(includeConfString)) {
|
||||||
|
return await diskService.writePlainTextFile(constants.BITCOIN_CONF_FILEPATH, `${includeConfString}\n${existingConfContents}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// do nothing if bitcoin.conf exists and contains includeconf=umbrel-bitcoin.conf
|
||||||
|
}
|
||||||
|
|
||||||
|
function settingsToMultilineConfString(settings) {
|
||||||
|
const umbrelBitcoinConfig = [];
|
||||||
|
|
||||||
|
// [CHAIN]
|
||||||
|
umbrelBitcoinConfig.push("# [chain]");
|
||||||
|
if (settings.network !== 'main') {
|
||||||
|
umbrelBitcoinConfig.push(`chain=${settings.network}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [CORE]
|
||||||
|
umbrelBitcoinConfig.push("");
|
||||||
|
umbrelBitcoinConfig.push("# [core]");
|
||||||
|
|
||||||
|
// dbcache
|
||||||
|
umbrelBitcoinConfig.push("# Maximum database cache size in MiB");
|
||||||
|
umbrelBitcoinConfig.push(`dbcache=${Math.round(settings.cacheSizeMB * MB_TO_MiB)}`);
|
||||||
|
|
||||||
|
// mempoolfullrbf
|
||||||
|
if (settings.mempoolFullRbf) {
|
||||||
|
umbrelBitcoinConfig.push("# Allow any transaction in the mempool of Bitcoin Node to be replaced with newer versions of the same transaction that include a higher fee.");
|
||||||
|
umbrelBitcoinConfig.push('mempoolfullrbf=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
// prune
|
||||||
|
if (settings.prune.enabled) {
|
||||||
|
umbrelBitcoinConfig.push("# Reduce disk space requirements to this many MiB by enabling pruning (deleting) of old blocks. This mode is incompatible with -txindex and -coinstatsindex. WARNING: Reverting this setting requires re-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, greater than or equal to 550 = automatically prune blocks to stay under target size in MiB).");
|
||||||
|
umbrelBitcoinConfig.push(`prune=${Math.round(settings.prune.pruneSizeGB * GB_TO_MiB)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reindex
|
||||||
|
if (settings.reindex) {
|
||||||
|
umbrelBitcoinConfig.push('# Rebuild chain state and block index from the blk*.dat files on disk.');
|
||||||
|
umbrelBitcoinConfig.push('reindex=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// [NETWORK]
|
||||||
|
umbrelBitcoinConfig.push("");
|
||||||
|
umbrelBitcoinConfig.push("# [network]");
|
||||||
|
|
||||||
|
// clearnet
|
||||||
|
if (settings.clearnet) {
|
||||||
|
umbrelBitcoinConfig.push('# Connect to peers over the clearnet.')
|
||||||
|
umbrelBitcoinConfig.push('onlynet=ipv4');
|
||||||
|
umbrelBitcoinConfig.push('onlynet=ipv6');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.torProxyForClearnet) {
|
||||||
|
umbrelBitcoinConfig.push('# Connect through <ip:port> SOCKS5 proxy.');
|
||||||
|
umbrelBitcoinConfig.push(`proxy=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_PORT}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tor
|
||||||
|
if (settings.tor) {
|
||||||
|
umbrelBitcoinConfig.push('# Use separate SOCKS5 proxy <ip:port> to reach peers via Tor hidden services.');
|
||||||
|
umbrelBitcoinConfig.push('onlynet=onion');
|
||||||
|
umbrelBitcoinConfig.push(`onion=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_PORT}`);
|
||||||
|
umbrelBitcoinConfig.push('# Tor control <ip:port> and password to use when onion listening enabled.');
|
||||||
|
umbrelBitcoinConfig.push(`torcontrol=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_CONTROL_PORT}`);
|
||||||
|
umbrelBitcoinConfig.push(`torpassword=${constants.TOR_PROXY_CONTROL_PASSWORD}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// i2p
|
||||||
|
if (settings.i2p) {
|
||||||
|
umbrelBitcoinConfig.push('# I2P SAM proxy <ip:port> to reach I2P peers.');
|
||||||
|
umbrelBitcoinConfig.push(`i2psam=${constants.I2P_DAEMON_IP}:${constants.I2P_DAEMON_PORT}`);
|
||||||
|
umbrelBitcoinConfig.push('onlynet=i2p');
|
||||||
|
}
|
||||||
|
|
||||||
|
// incoming connections
|
||||||
|
umbrelBitcoinConfig.push('# Enable/disable incoming connections from peers.');
|
||||||
|
const listen = settings.incomingConnections ? 1 : 0;
|
||||||
|
umbrelBitcoinConfig.push(`listen=1`);
|
||||||
|
umbrelBitcoinConfig.push(`listenonion=${listen}`);
|
||||||
|
umbrelBitcoinConfig.push(`i2pacceptincoming=${listen}`);
|
||||||
|
|
||||||
|
umbrelBitcoinConfig.push(`# Required to configure Tor control port properly`);
|
||||||
|
umbrelBitcoinConfig.push(`[${settings.network}]`);
|
||||||
|
umbrelBitcoinConfig.push(`bind=0.0.0.0:8333`);
|
||||||
|
umbrelBitcoinConfig.push(`bind=${constants.BITCOIND_IP}:8334=onion`);
|
||||||
|
|
||||||
|
return umbrelBitcoinConfig.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getJsonStore,
|
||||||
|
applyCustomBitcoinConfig,
|
||||||
|
applyDefaultBitcoinConfig,
|
||||||
|
};
|
@ -2,19 +2,19 @@ const constants = require('utils/const.js');
|
|||||||
const NodeError = require('models/errors.js').NodeError;
|
const NodeError = require('models/errors.js').NodeError;
|
||||||
|
|
||||||
function getBitcoinP2PConnectionDetails() {
|
function getBitcoinP2PConnectionDetails() {
|
||||||
const torAddress = constants.BITCOIN_P2P_HIDDEN_SERVICE;
|
const torAddress = constants.BITCOIN_P2P_HIDDEN_SERVICE;
|
||||||
const port = constants.BITCOIN_P2P_PORT;
|
const port = constants.BITCOIN_P2P_PORT;
|
||||||
const torConnectionString = `${torAddress}:${port}`;
|
const torConnectionString = `${torAddress}:${port}`;
|
||||||
const localAddress = constants.DEVICE_DOMAIN_NAME;
|
const localAddress = constants.DEVICE_DOMAIN_NAME;
|
||||||
const localConnectionString = `${localAddress}:${port}`;
|
const localConnectionString = `${localAddress}:${port}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
torAddress,
|
torAddress,
|
||||||
port,
|
port,
|
||||||
torConnectionString,
|
torConnectionString,
|
||||||
localAddress,
|
localAddress,
|
||||||
localConnectionString
|
localConnectionString
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBitcoinRPCConnectionDetails() {
|
function getBitcoinRPCConnectionDetails() {
|
||||||
|
@ -2,6 +2,8 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const systemLogic = require('logic/system.js');
|
const systemLogic = require('logic/system.js');
|
||||||
|
const diskLogic = require('logic/disk');
|
||||||
|
const bitcoindLogic = require('logic/bitcoind.js');
|
||||||
const safeHandler = require('utils/safeHandler');
|
const safeHandler = require('utils/safeHandler');
|
||||||
|
|
||||||
router.get('/bitcoin-p2p-connection-details', safeHandler(async(req, res) => {
|
router.get('/bitcoin-p2p-connection-details', safeHandler(async(req, res) => {
|
||||||
@ -16,4 +18,48 @@ router.get('/bitcoin-rpc-connection-details', safeHandler(async(req, res) => {
|
|||||||
return res.json(connectionDetails);
|
return res.json(connectionDetails);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
router.get('/bitcoin-config', safeHandler(async(req, res) => {
|
||||||
|
const bitcoinConfig = await diskLogic.getJsonStore();
|
||||||
|
return res.json(bitcoinConfig);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// updateJsonStore / generateUmbrelBitcoinConfig / generateBitcoinConfig are all called through these routes below so that even if user closes the browser prematurely, the backend will complete the update.
|
||||||
|
|
||||||
|
router.post('/update-bitcoin-config', safeHandler(async(req, res) => {
|
||||||
|
// store old bitcoinConfig in memory to revert to in case of errors setting new config and restarting bitcoind
|
||||||
|
const oldBitcoinConfig = await diskLogic.getJsonStore();
|
||||||
|
const newBitcoinConfig = req.body.bitcoinConfig;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await diskLogic.applyCustomBitcoinConfig(newBitcoinConfig);
|
||||||
|
await bitcoindLogic.stop();
|
||||||
|
|
||||||
|
res.json({success: true});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// revert everything to old config values
|
||||||
|
await diskLogic.applyCustomBitcoinConfig(oldBitcoinConfig);
|
||||||
|
|
||||||
|
res.json({success: false}); // show error to user in UI
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.post('/restore-default-bitcoin-config', safeHandler(async(req, res) => {
|
||||||
|
// store old bitcoinConfig in memory to revert to in case of errors setting new config and restarting bitcoind
|
||||||
|
const oldBitcoinConfig = await diskLogic.getJsonStore();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await diskLogic.applyDefaultBitcoinConfig();
|
||||||
|
await bitcoindLogic.stop();
|
||||||
|
|
||||||
|
res.json({success: true});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// revert everything to old config values
|
||||||
|
await diskLogic.applyCustomBitcoinConfig(oldBitcoinConfig);
|
||||||
|
|
||||||
|
res.json({success: false}); // show error to user in UI
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
@ -1,6 +1,5 @@
|
|||||||
const RpcClient = require('bitcoind-rpc');
|
const RpcClient = require('bitcoind-rpc');
|
||||||
const camelizeKeys = require('camelize-keys');
|
const camelizeKeys = require('camelize-keys');
|
||||||
|
|
||||||
const BitcoindError = require('models/errors.js').BitcoindError;
|
const BitcoindError = require('models/errors.js').BitcoindError;
|
||||||
|
|
||||||
const BITCOIND_RPC_PORT = process.env.RPC_PORT || 8332; // eslint-disable-line no-magic-numbers, max-len
|
const BITCOIND_RPC_PORT = process.env.RPC_PORT || 8332; // eslint-disable-line no-magic-numbers, max-len
|
||||||
@ -13,7 +12,7 @@ const rpcClient = new RpcClient({
|
|||||||
user: BITCOIND_RPC_USER, // eslint-disable-line object-shorthand
|
user: BITCOIND_RPC_USER, // eslint-disable-line object-shorthand
|
||||||
pass: BITCOIND_RPC_PASSWORD, // eslint-disable-line object-shorthand
|
pass: BITCOIND_RPC_PASSWORD, // eslint-disable-line object-shorthand
|
||||||
host: BITCOIND_HOST,
|
host: BITCOIND_HOST,
|
||||||
port: BITCOIND_RPC_PORT,
|
port: BITCOIND_RPC_PORT
|
||||||
});
|
});
|
||||||
|
|
||||||
function promiseify(rpcObj, rpcFn, what) {
|
function promiseify(rpcObj, rpcFn, what) {
|
||||||
@ -115,6 +114,10 @@ function help() {
|
|||||||
return promiseify(rpcClient, rpcClient.help, 'help data');
|
return promiseify(rpcClient, rpcClient.help, 'help data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
return promiseify(rpcClient, rpcClient.stop, 'stop');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMiningInfo,
|
getMiningInfo,
|
||||||
getBestBlockHash,
|
getBestBlockHash,
|
||||||
@ -127,4 +130,5 @@ module.exports = {
|
|||||||
getMempoolInfo,
|
getMempoolInfo,
|
||||||
getNetworkInfo,
|
getNetworkInfo,
|
||||||
help,
|
help,
|
||||||
|
stop,
|
||||||
};
|
};
|
||||||
|
126
services/disk.js
Normal file
126
services/disk.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Generic disk functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const logger = require("utils/logger");
|
||||||
|
const fs = require("fs");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
const UINT32_BYTES = 4;
|
||||||
|
|
||||||
|
// Asynchronously checks if a file exists
|
||||||
|
async function fileExists(filePath) {
|
||||||
|
try {
|
||||||
|
await fs.promises.access(filePath);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads a file. Wraps fs.readFile into a native promise
|
||||||
|
function readFile(filePath, encoding) {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
fs.readFile(filePath, encoding, (err, str) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(str);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads a file as a utf8 string. Wraps fs.readFile into a native promise
|
||||||
|
async function readUtf8File(filePath) {
|
||||||
|
return (await readFile(filePath, "utf8")).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readJsonFile(filePath) {
|
||||||
|
return readUtf8File(filePath).then(JSON.parse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a string to a file. Wraps fs.writeFile into a native promise
|
||||||
|
// This is _not_ concurrency safe, so don't export it without making it like writeJsonFile
|
||||||
|
function writeFile(filePath, data, encoding) {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
fs.writeFile(filePath, data, encoding, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJsonFile(filePath, obj) {
|
||||||
|
const tempFileName = `${filePath}.${crypto
|
||||||
|
.randomBytes(UINT32_BYTES)
|
||||||
|
.readUInt32LE(0)}`;
|
||||||
|
|
||||||
|
return writeFile(tempFileName, JSON.stringify(obj, null, 2), "utf8")
|
||||||
|
.then(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
fs.rename(tempFileName, filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
if (err) {
|
||||||
|
fs.unlink(tempFileName, (err) => {
|
||||||
|
logger.warn("Error removing temporary file after error", "disk", {
|
||||||
|
err,
|
||||||
|
tempFileName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePlainTextFile(filePath, string) {
|
||||||
|
const tempFileName = `${filePath}.${crypto
|
||||||
|
.randomBytes(UINT32_BYTES)
|
||||||
|
.readUInt32LE(0)}`;
|
||||||
|
|
||||||
|
return writeFile(tempFileName, string, "utf8")
|
||||||
|
.then(
|
||||||
|
() =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
fs.rename(tempFileName, filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).catch((err) => {
|
||||||
|
if (err) {
|
||||||
|
fs.unlink(tempFileName, (err) => {
|
||||||
|
logger.warn("Error removing temporary file after error", "disk", {
|
||||||
|
err,
|
||||||
|
tempFileName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fileExists,
|
||||||
|
readFile,
|
||||||
|
readUtf8File,
|
||||||
|
readJsonFile,
|
||||||
|
writeJsonFile,
|
||||||
|
writeFile,
|
||||||
|
writePlainTextFile
|
||||||
|
};
|
||||||
|
|
11
tor-entrypoint/tor-entrypoint.sh
Executable file
11
tor-entrypoint/tor-entrypoint.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
TORRC_PATH="/etc/tor/torrc"
|
||||||
|
PLAIN_TEXT_PASSWORD="umbrelisneat"
|
||||||
|
HASH_PASSWORD=`tor --hash-password "$PLAIN_TEXT_PASSWORD"`
|
||||||
|
# clobber old file
|
||||||
|
echo "SocksPort 0.0.0.0:9050" > "${TORRC_PATH}"
|
||||||
|
echo "ControlPort 0.0.0.0:9051" >> "${TORRC_PATH}"
|
||||||
|
echo "CookieAuthentication 1" >> "${TORRC_PATH}"
|
||||||
|
echo "CookieAuthFileGroupReadable 1" >> "${TORRC_PATH}"
|
||||||
|
echo "HashedControlPassword $HASH_PASSWORD" >> "${TORRC_PATH}"
|
||||||
|
tor -f "${TORRC_PATH}"
|
24
ui/package-lock.json
generated
24
ui/package-lock.json
generated
@ -16,6 +16,7 @@
|
|||||||
"countup.js": "^2.0.4",
|
"countup.js": "^2.0.4",
|
||||||
"highcharts": "^9.3.2",
|
"highcharts": "^9.3.2",
|
||||||
"highcharts-vue": "^1.4.0",
|
"highcharts-vue": "^1.4.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"qrcode.vue": "^1.7.0",
|
"qrcode.vue": "^1.7.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
@ -9609,6 +9610,11 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||||
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
@ -15216,10 +15222,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vue-slider-component": {
|
"node_modules/vue-slider-component": {
|
||||||
"version": "3.2.15",
|
"version": "3.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.20.tgz",
|
||||||
"integrity": "sha512-FpmMsQ6MQFn22B6boDcEjRmuawdaHwjHRVZiuv5w37jijHra6+HogjSrh3mb42jE+PUIFFagXi36oFEzpDbadg==",
|
"integrity": "sha512-S5+4d6zdL+/ClpDQoIgImIdXRv2b+75PIy3cDGsZsakhroJD6cSFA0juY/AblGqhvIkNcBIU354eOw6T26DWbA==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue-property-decorator": "^8.0.0"
|
"vue-property-decorator": "^8.0.0"
|
||||||
@ -22998,6 +23003,11 @@
|
|||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||||
|
},
|
||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
@ -27180,9 +27190,9 @@
|
|||||||
"integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg=="
|
"integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg=="
|
||||||
},
|
},
|
||||||
"vue-slider-component": {
|
"vue-slider-component": {
|
||||||
"version": "3.2.15",
|
"version": "3.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.20.tgz",
|
||||||
"integrity": "sha512-FpmMsQ6MQFn22B6boDcEjRmuawdaHwjHRVZiuv5w37jijHra6+HogjSrh3mb42jE+PUIFFagXi36oFEzpDbadg==",
|
"integrity": "sha512-S5+4d6zdL+/ClpDQoIgImIdXRv2b+75PIy3cDGsZsakhroJD6cSFA0juY/AblGqhvIkNcBIU354eOw6T26DWbA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue-property-decorator": "^8.0.0"
|
"vue-property-decorator": "^8.0.0"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"countup.js": "^2.0.4",
|
"countup.js": "^2.0.4",
|
||||||
"highcharts": "^9.3.2",
|
"highcharts": "^9.3.2",
|
||||||
"highcharts-vue": "^1.4.0",
|
"highcharts-vue": "^1.4.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"qrcode.vue": "^1.7.0",
|
"qrcode.vue": "^1.7.0",
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
|
@ -62,7 +62,7 @@ export default {
|
|||||||
|
|
||||||
this.loadingPollInProgress = true;
|
this.loadingPollInProgress = true;
|
||||||
|
|
||||||
// Then check if middleware api is up
|
// Then check if middleware api and bitcoin core are both up
|
||||||
if (this.loadingProgress <= 40) {
|
if (this.loadingProgress <= 40) {
|
||||||
this.loadingProgress = 40;
|
this.loadingProgress = 40;
|
||||||
await this.$store.dispatch("system/getApi");
|
await this.$store.dispatch("system/getApi");
|
||||||
|
437
ui/src/components/AdvancedSettingsModal.vue
Normal file
437
ui/src/components/AdvancedSettingsModal.vue
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
<template v-slot:modal-header="{ close }" title="Advanced Settings">
|
||||||
|
<b-form @submit.prevent="submit">
|
||||||
|
<div
|
||||||
|
class="px-0 px-sm-3 pb-3 d-flex flex-column justify-content-between w-100"
|
||||||
|
>
|
||||||
|
<h3 class="mt-1">Advanced Settings</h3>
|
||||||
|
<b-alert variant="warning" show class="mb-3">
|
||||||
|
<small>
|
||||||
|
Be careful when changing the settings below as they may cause issues
|
||||||
|
with other apps on your Umbrel that connect to your Bitcoin node. Only make
|
||||||
|
changes if you understand the potential effects on connected apps or
|
||||||
|
wallets.
|
||||||
|
</small>
|
||||||
|
</b-alert>
|
||||||
|
|
||||||
|
<b-overlay :show="isSettingsDisabled" rounded="sm">
|
||||||
|
<div
|
||||||
|
class="advanced-settings-container d-flex flex-column p-3 pb-sm-3 bg-light mb-2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="clearnet">
|
||||||
|
<p class="font-weight-bold mb-0">Outgoing Connections to Clearnet Peers</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="clearnet"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.clearnet"
|
||||||
|
@toggle="status => (settings.clearnet = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Connect to peers available on the clearnet (publicly accessible internet).
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="tor">
|
||||||
|
<p class="font-weight-bold mb-0">Outgoing Connections to Tor Peers</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="tor"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.tor"
|
||||||
|
@toggle="status => (settings.tor = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Connect to peers available on the Tor network.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="proxy">
|
||||||
|
<p class="font-weight-bold mb-0">Connect to all Clearnet Peers over Tor</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="proxy"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.torProxyForClearnet"
|
||||||
|
:disabled="isTorProxyDisabled"
|
||||||
|
:tooltip="torProxyTooltip"
|
||||||
|
@toggle="status => (settings.torProxyForClearnet = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Connect to peers available on the clearnet via Tor to preserve your anonymity at the cost of slightly less security.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="I2P">
|
||||||
|
<p class="font-weight-bold mb-0">Outgoing Connections to I2P Peers</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="I2P"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.i2p"
|
||||||
|
@toggle="status => (settings.i2p = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Connect to peers available on the I2P network.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="allow-incoming-connections">
|
||||||
|
<p class="font-weight-bold mb-0">Incoming Connections</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<toggle-switch
|
||||||
|
id="allow-incoming-connections"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.incomingConnections"
|
||||||
|
@toggle="status => (settings.incomingConnections = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Broadcast your node to the Bitcoin network to help other nodes
|
||||||
|
access the blockchain. You may need to set up port forwarding on
|
||||||
|
your router to allow incoming connections from clearnet-only peers.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="cache-size">
|
||||||
|
<p class="font-weight-bold mb-0">Cache Size (MB)</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<b-form-input
|
||||||
|
class="advanced-settings-input"
|
||||||
|
id="cache-size"
|
||||||
|
type="number"
|
||||||
|
v-model="settings.cacheSizeMB"
|
||||||
|
></b-form-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Choose the size of the UTXO set to store in RAM. A larger cache can
|
||||||
|
speed up the initial synchronization of your Bitcoin node, but after
|
||||||
|
the initial sync is complete, a larger cache value does not significantly
|
||||||
|
improve performance and may use more RAM than needed.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="mempool">
|
||||||
|
<p class="font-weight-bold mb-0">Replace-By-Fee (RBF) for All Transactions</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="mempool"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.mempoolFullRbf"
|
||||||
|
@toggle="status => (settings.mempoolFullRbf = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Allow any transaction in the mempool of your Bitcoin node to be replaced with
|
||||||
|
a newer version of the same transaction that includes a higher fee.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="prune-old-blocks">
|
||||||
|
<p class="font-weight-bold mb-0">Prune Old Blocks</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="prune-old-blocks"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.prune.enabled"
|
||||||
|
@toggle="status => (settings.prune.enabled = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Save storage space by pruning (deleting) old blocks and keeping only
|
||||||
|
a limited copy of the blockchain. Use the slider to choose the size
|
||||||
|
of the blockchain you want to store. It may take some time for your
|
||||||
|
node to be online after you turn on pruning. If you turn off pruning
|
||||||
|
after turning it on, you'll need to download the entire blockchain
|
||||||
|
again.
|
||||||
|
</small>
|
||||||
|
<prune-slider
|
||||||
|
id="prune-old-blocks"
|
||||||
|
class="mt-3 mb-3"
|
||||||
|
:minValue="1"
|
||||||
|
:maxValue="maxPruneSizeGB"
|
||||||
|
:startingValue="settings.prune.pruneSizeGB"
|
||||||
|
:disabled="!settings.prune.enabled"
|
||||||
|
@change="value => (settings.prune.pruneSizeGB = value)"
|
||||||
|
></prune-slider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" />
|
||||||
|
|
||||||
|
<!-- <div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="reindex-blockchain">
|
||||||
|
<p class="font-weight-bold mb-0">Reindex Blockchain</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<toggle-switch
|
||||||
|
id="reindex-blockchain"
|
||||||
|
class="align-self-center"
|
||||||
|
:on="settings.reindex"
|
||||||
|
@toggle="status => (settings.reindex = status)"
|
||||||
|
></toggle-switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Rebuild the database index used by your Bitcoin node. This can
|
||||||
|
be useful if the index becomes corrupted.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="advanced-settings-divider" /> -->
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="w-75">
|
||||||
|
<label class="mb-0" for="network">
|
||||||
|
<p class="font-weight-bold mb-0">Network</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<b-form-select
|
||||||
|
id="network"
|
||||||
|
v-model="settings.network"
|
||||||
|
:options="networks"
|
||||||
|
></b-form-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="w-sm-75 d-block text-muted mt-1">
|
||||||
|
Choose which network you want your Bitcoin node to connect to.
|
||||||
|
If you change the network, restart your Umbrel to make sure any
|
||||||
|
apps connected to your Bitcoin node continue to work properly.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- template overlay with empty div to show an overlay with no spinner -->
|
||||||
|
<template #overlay>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
</b-overlay>
|
||||||
|
|
||||||
|
<b-alert variant="warning" :show="showOutgoingConnectionsError" class="mt-2" @dismissed="showOutgoingConnectionsError=false">
|
||||||
|
<small>
|
||||||
|
Please choose at least one source for outgoing connections (Clearnet, Tor, or I2P).
|
||||||
|
</small>
|
||||||
|
</b-alert>
|
||||||
|
|
||||||
|
<div class="mt-2 mb-2">
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="12" lg="6">
|
||||||
|
<b-button @click="clickRestoreDefaults" class="btn-border" variant="outline-secondary" block :disabled="isSettingsDisabled">
|
||||||
|
Restore Default Settings</b-button
|
||||||
|
>
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="12" lg="6">
|
||||||
|
<b-button class="mt-2 mt-lg-0" variant="success" type="submit" block :disabled="isSettingsDisabled">
|
||||||
|
Save and Restart Bitcoin Node</b-button
|
||||||
|
>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import ToggleSwitch from "./Utility/ToggleSwitch.vue";
|
||||||
|
import PruneSlider from "./PruneSlider.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
settings: {},
|
||||||
|
networks: [
|
||||||
|
{ value: "main", text: "mainnet" },
|
||||||
|
{ value: "test", text: "testnet" },
|
||||||
|
{ value: "signet", text: "signet" },
|
||||||
|
{ value: "regtest", text: "regtest" }
|
||||||
|
],
|
||||||
|
maxPruneSizeGB: 300,
|
||||||
|
showOutgoingConnectionsError: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
bitcoinConfig: state => state.user.bitcoinConfig,
|
||||||
|
rpc: state => state.bitcoin.rpc,
|
||||||
|
p2p: state => state.bitcoin.p2p
|
||||||
|
}),
|
||||||
|
isTorProxyDisabled() {
|
||||||
|
return !this.settings.clearnet || !this.settings.tor;
|
||||||
|
},
|
||||||
|
torProxyTooltip() {
|
||||||
|
if (!this.settings.clearnet || !this.settings.tor) {
|
||||||
|
return "Outgoing connections to both clearnet and Tor peers must be enabled to turn this on.";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isTorProxyDisabled(value) {
|
||||||
|
if (!value) return;
|
||||||
|
this.settings.torProxyForClearnet = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
isSettingsDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.setSettings();
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ToggleSwitch,
|
||||||
|
PruneSlider
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit() {
|
||||||
|
this.showOutgoingConnectionsError = false;
|
||||||
|
if (!this.isOutgoingConnectionsValid()) return this.showOutgoingConnectionsError = true;
|
||||||
|
this.$emit("submit", this.settings);
|
||||||
|
},
|
||||||
|
clickRestoreDefaults() {
|
||||||
|
if (window.confirm("Are you sure you want to restore the default settings?")) {
|
||||||
|
this.$emit("clickRestoreDefaults");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSettings() {
|
||||||
|
// deep clone bitcoinConfig in order to properly reset state if user hides modal instead of clicking the save and restart button
|
||||||
|
this.settings = cloneDeep(this.bitcoinConfig);
|
||||||
|
},
|
||||||
|
isOutgoingConnectionsValid() {
|
||||||
|
return this.settings.clearnet || this.settings.tor || this.settings.i2p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- removed scoped in order to place scrollbar on bootstrap-vue .modal-body. Increased verbosity on other classes-->
|
||||||
|
<style lang="scss">
|
||||||
|
.advanced-settings-container {
|
||||||
|
border-radius: 1rem;
|
||||||
|
.advanced-settings-divider {
|
||||||
|
// same styles as bootstrap <b-dropdown-divider/>
|
||||||
|
height: 0;
|
||||||
|
margin: 1.25rem 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-top: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
.advanced-settings-input {
|
||||||
|
max-width: 75px;
|
||||||
|
}
|
||||||
|
// to remove arrows on number input field
|
||||||
|
.advanced-settings-input::-webkit-outer-spin-button, .advanced-settings-input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.advanced-settings-input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-border {
|
||||||
|
border: solid 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
margin-block-end: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sm breakpoint */
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.w-sm-75 {
|
||||||
|
width: 75% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -138,12 +138,17 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
|
isBitcoinCoreOperational: state => state.bitcoin.operational,
|
||||||
syncPercent: state => state.bitcoin.percent,
|
syncPercent: state => state.bitcoin.percent,
|
||||||
blocks: state => state.bitcoin.blocks
|
blocks: state => state.bitcoin.blocks
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchBlocks() {
|
async fetchBlocks() {
|
||||||
|
// don't poll if bitcoin core isn't yet running
|
||||||
|
if (!this.isBitcoinCoreOperational) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
//prevent multiple polls if previous poll already in progress
|
//prevent multiple polls if previous poll already in progress
|
||||||
if (this.pollInProgress) {
|
if (this.pollInProgress) {
|
||||||
return;
|
return;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<img alt="Umbrel" src="@/assets/icon.svg" class="mb-5 logo" />
|
<img alt="Umbrel" src="@/assets/icon.svg" class="mb-5 logo" />
|
||||||
|
|
||||||
<!-- <b-spinner class="my-4" variant="primary"></b-spinner> -->
|
<!-- <b-spinner class="my-4" variant="primary"></b-spinner> -->
|
||||||
|
<small v-if="isRestarting" class="text-muted mb-3">Restarting...</small>
|
||||||
<b-progress
|
<b-progress
|
||||||
:value="progress"
|
:value="progress"
|
||||||
class="mb-2 w-75"
|
class="mb-2 w-75"
|
||||||
@ -25,10 +26,17 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {
|
||||||
|
isRestarting: false
|
||||||
|
};
|
||||||
},
|
},
|
||||||
props: { progress: Number },
|
props: { progress: Number },
|
||||||
created() {},
|
created() {
|
||||||
|
if (this.$route.query.hasOwnProperty("restart")) {
|
||||||
|
this.isRestarting = true;
|
||||||
|
this.$router.replace({ query: {} });
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
components: {}
|
components: {}
|
||||||
};
|
};
|
||||||
|
85
ui/src/components/PruneSlider.vue
Normal file
85
ui/src/components/PruneSlider.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="prune-slider">
|
||||||
|
<vue-slider
|
||||||
|
v-model="value"
|
||||||
|
:tooltip="'always'"
|
||||||
|
:min="minValue"
|
||||||
|
:max="maxValue"
|
||||||
|
:interval="1"
|
||||||
|
:contrained="true"
|
||||||
|
:disabled="disabled"
|
||||||
|
@change="change"
|
||||||
|
>
|
||||||
|
<template v-slot:tooltip="{ value, focus }">
|
||||||
|
<div :class="['custom-tooltip', { focus }]">
|
||||||
|
<small class="text-muted">{{ value }}GB</small>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vue-slider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueSlider from "vue-slider-component";
|
||||||
|
import "vue-slider-component/theme/default.css";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
VueSlider
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: this.startingValue
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
minValue: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
maxValue: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
startingValue: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
change() {
|
||||||
|
return this.$emit("change", this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$dotShadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
|
||||||
|
$dotShadowFocus: 0px 4px 10px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
.custom-tooltip {
|
||||||
|
transform: translateY(50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prune-slider .vue-slider-rail {
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(to right, #f6b900, #00cd98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prune-slider .vue-slider-process {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prune-slider .vue-slider-disabled {
|
||||||
|
.vue-slider-rail {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -24,6 +24,27 @@
|
|||||||
<span class="text-muted" style="margin-left: 0.5rem;">{{
|
<span class="text-muted" style="margin-left: 0.5rem;">{{
|
||||||
suffix
|
suffix
|
||||||
}}</span>
|
}}</span>
|
||||||
|
|
||||||
|
<div class="ml-1">
|
||||||
|
<b-popover
|
||||||
|
v-if="showPopover"
|
||||||
|
:target="popoverId"
|
||||||
|
placement="bottom"
|
||||||
|
triggers="hover focus"
|
||||||
|
>
|
||||||
|
<p v-for="content in popoverContent" :key="content" class="m-0">{{ content }}</p>
|
||||||
|
</b-popover>
|
||||||
|
|
||||||
|
<b-icon
|
||||||
|
v-if="showPopover"
|
||||||
|
:id="popoverId"
|
||||||
|
icon="info-circle"
|
||||||
|
size="lg"
|
||||||
|
style="cursor: pointer"
|
||||||
|
class="text-muted"
|
||||||
|
></b-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -100,6 +121,18 @@ export default {
|
|||||||
showPercentChange: {
|
showPercentChange: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
showPopover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
popoverId: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
popoverContent: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
101
ui/src/components/Utility/ToggleSwitch.vue
Normal file
101
ui/src/components/Utility/ToggleSwitch.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<!-- div wrapper with v-b-tooltip to allow tooltip to show when toggle is disabled -->
|
||||||
|
<div v-b-tooltip.hover.left :title="tooltip">
|
||||||
|
<div
|
||||||
|
class="toggle"
|
||||||
|
:class="{
|
||||||
|
'toggle-off': !on,
|
||||||
|
'toggle-on': on,
|
||||||
|
'toggle-disabled': disabled,
|
||||||
|
'toggle-loading': loading
|
||||||
|
}"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="toggle"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="toggle-switch justify-items-center"
|
||||||
|
:class="{
|
||||||
|
'toggle-switch-off': !on,
|
||||||
|
'toggle-switch-on': on
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
toggle() {
|
||||||
|
if (this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.$emit("toggle", !this.on);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["toggle"]
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
$toggle-width: 50px;
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
border-radius: calc($toggle-width * 0.5);
|
||||||
|
width: $toggle-width;
|
||||||
|
height: calc($toggle-width * 0.6);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
background: linear-gradient(346.78deg, #f7fcfc 0%, #fafcfa 100%);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||||
|
// TODO - may want to calc box-shadow px values to scale correctly with $toggle-width
|
||||||
|
box-shadow: inset 0px 5px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
&.toggle-on {
|
||||||
|
background: var(--success);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
&.toggle-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
&.toggle-loading {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggle-switch {
|
||||||
|
margin: 0;
|
||||||
|
height: calc($toggle-width * 0.5);
|
||||||
|
width: calc($toggle-width * 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
transition: 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
}
|
||||||
|
.toggle-switch-off {
|
||||||
|
transform: translateX(10%);
|
||||||
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.toggle-switch-on {
|
||||||
|
transform: translateX(90%);
|
||||||
|
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,6 +4,7 @@ import Vuex from "vuex";
|
|||||||
//Modules
|
//Modules
|
||||||
import system from "./modules/system";
|
import system from "./modules/system";
|
||||||
import bitcoin from "./modules/bitcoin";
|
import bitcoin from "./modules/bitcoin";
|
||||||
|
import user from "./modules/user";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ export default new Vuex.Store({
|
|||||||
getters,
|
getters,
|
||||||
modules: {
|
modules: {
|
||||||
system,
|
system,
|
||||||
bitcoin
|
bitcoin,
|
||||||
|
user
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import API from "@/helpers/api";
|
import API from "@/helpers/api";
|
||||||
import { toPrecision } from "@/helpers/units";
|
import { toPrecision } from "@/helpers/units";
|
||||||
|
|
||||||
|
const BYTES_PER_GB = 1000000000;
|
||||||
|
|
||||||
// Initial state
|
// Initial state
|
||||||
const state = () => ({
|
const state = () => ({
|
||||||
operational: false,
|
operational: false,
|
||||||
@ -24,6 +26,8 @@ const state = () => ({
|
|||||||
},
|
},
|
||||||
currentBlock: 0,
|
currentBlock: 0,
|
||||||
chain: "",
|
chain: "",
|
||||||
|
pruned: false,
|
||||||
|
pruneTargetSizeGB: 0,
|
||||||
blockHeight: 0,
|
blockHeight: 0,
|
||||||
blocks: [],
|
blocks: [],
|
||||||
percent: -1, //for loading state
|
percent: -1, //for loading state
|
||||||
@ -37,7 +41,10 @@ const state = () => ({
|
|||||||
peers: {
|
peers: {
|
||||||
total: 0,
|
total: 0,
|
||||||
inbound: 0,
|
inbound: 0,
|
||||||
outbound: 0
|
outbound: 0,
|
||||||
|
clearnet: 0,
|
||||||
|
tor: 0,
|
||||||
|
i2p: 0
|
||||||
},
|
},
|
||||||
chartData: []
|
chartData: []
|
||||||
});
|
});
|
||||||
@ -53,6 +60,8 @@ const mutations = {
|
|||||||
state.currentBlock = sync.currentBlock;
|
state.currentBlock = sync.currentBlock;
|
||||||
state.blockHeight = sync.headerCount;
|
state.blockHeight = sync.headerCount;
|
||||||
state.chain = sync.chain;
|
state.chain = sync.chain;
|
||||||
|
state.pruned = sync.pruned;
|
||||||
|
state.pruneTargetSizeGB = Math.round(sync.pruneTargetSize / BYTES_PER_GB);
|
||||||
|
|
||||||
if (sync.status === "calibrating") {
|
if (sync.status === "calibrating") {
|
||||||
state.calibrating = true;
|
state.calibrating = true;
|
||||||
@ -104,6 +113,9 @@ const mutations = {
|
|||||||
state.peers.total = peers.total || 0;
|
state.peers.total = peers.total || 0;
|
||||||
state.peers.inbound = peers.inbound || 0;
|
state.peers.inbound = peers.inbound || 0;
|
||||||
state.peers.outbound = peers.outbound || 0;
|
state.peers.outbound = peers.outbound || 0;
|
||||||
|
state.peers.clearnet = peers.clearnet || 0;
|
||||||
|
state.peers.tor = peers.tor || 0;
|
||||||
|
state.peers.i2p = peers.i2p || 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
setChartData(state, chartData) {
|
setChartData(state, chartData) {
|
||||||
|
38
ui/src/store/modules/user.js
Normal file
38
ui/src/store/modules/user.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import API from "@/helpers/api";
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
const state = () => ({
|
||||||
|
bitcoinConfig: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Functions to update the state directly
|
||||||
|
const mutations = {
|
||||||
|
setBitcoinConfig(state, bitcoinConfig) {
|
||||||
|
state.bitcoinConfig = bitcoinConfig;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
async getBitcoinConfig({ commit }) {
|
||||||
|
const existingConfig = await API.get(
|
||||||
|
`${process.env.VUE_APP_API_BASE_URL}/v1/bitcoind/system/bitcoin-config`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingConfig) {
|
||||||
|
commit("setBitcoinConfig", existingConfig);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateBitcoinConfig({ commit }, bitcoinConfig) {
|
||||||
|
commit("setBitcoinConfig", bitcoinConfig);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
};
|
@ -15,9 +15,10 @@
|
|||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<circle cx="4" cy="4" r="4" fill="#00CD98" />
|
<circle cx="4" cy="4" r="4" :fill="`${isBitcoinCoreOperational ? '#00CD98' : '#F6B900'}`" />
|
||||||
</svg>
|
</svg>
|
||||||
<small class="ml-1 text-success">Running</small>
|
<small v-if="isBitcoinCoreOperational" class="ml-1 text-success">Running</small>
|
||||||
|
<small v-else class="ml-1 text-warning">Starting</small>
|
||||||
<h3 class="d-block font-weight-bold mb-1">Bitcoin Node</h3>
|
<h3 class="d-block font-weight-bold mb-1">Bitcoin Node</h3>
|
||||||
<span class="d-block text-muted">{{
|
<span class="d-block text-muted">{{
|
||||||
version ? `Bitcoin Core ${version}` : "..."
|
version ? `Bitcoin Core ${version}` : "..."
|
||||||
@ -25,6 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex col-12 col-md-auto justify-content-start align-items-center p-0">
|
<div class="d-flex col-12 col-md-auto justify-content-start align-items-center p-0">
|
||||||
|
<!-- TODO - work on responsiveness of connect + settings button -->
|
||||||
<b-button
|
<b-button
|
||||||
type="button"
|
type="button"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@ -34,8 +36,53 @@
|
|||||||
<b-icon icon="plus" aria-hidden="true"></b-icon>
|
<b-icon icon="plus" aria-hidden="true"></b-icon>
|
||||||
Connect
|
Connect
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|
||||||
|
<b-dropdown
|
||||||
|
class="ml-3"
|
||||||
|
variant="link"
|
||||||
|
toggle-class="text-decoration-none p-0"
|
||||||
|
no-caret
|
||||||
|
right
|
||||||
|
>
|
||||||
|
<template v-slot:button-content>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="4"
|
||||||
|
viewBox="0 0 18 4"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M2 4C3.10457 4 4 3.10457 4 2C4 0.89543 3.10457 0 2 0C0.89543 0 0 0.89543 0 2C0 3.10457 0.89543 4 2 4Z"
|
||||||
|
fill="#6c757d"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M9 4C10.1046 4 11 3.10457 11 2C11 0.89543 10.1046 0 9 0C7.89543 0 7 0.89543 7 2C7 3.10457 7.89543 4 9 4Z"
|
||||||
|
fill="#6c757d"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M16 4C17.1046 4 18 3.10457 18 2C18 0.89543 17.1046 0 16 0C14.8954 0 14 0.89543 14 2C14 3.10457 14.8954 4 16 4Z"
|
||||||
|
fill="#6c757d"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
<b-dropdown-item href="#" v-b-modal.advanced-settings-modal><b-badge pill variant="primary" class="mr-1">New</b-badge> Advanced Settings</b-dropdown-item>
|
||||||
|
</b-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<b-alert :show="showReindexCompleteAlert" variant="warning">Reindexing is now complete. Turn off "Reindex blockchain" in <span class="open-settings" @click="() => $bvModal.show('advanced-settings-modal')">advanced settings</span> to prevent reindexing every time Bitcoin Node restarts.</b-alert>
|
||||||
|
|
||||||
|
<b-alert :show="showReindexInProgressAlert" variant="info">Reindexing in progress...</b-alert>
|
||||||
|
|
||||||
|
<b-alert :show="showRestartError" variant="danger" dismissible @dismissed="showRestartError=false">
|
||||||
|
Something went wrong while attempting to change the configuration of Bitcoin Node.
|
||||||
|
</b-alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<b-row class="row-eq-height">
|
<b-row class="row-eq-height">
|
||||||
@ -84,7 +131,7 @@
|
|||||||
</card-widget>
|
</card-widget>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col col cols="12" md="7" lg="8">
|
<b-col col cols="12" md="7" lg="8">
|
||||||
<card-widget class="overflow-x" header="Network">
|
<card-widget class="overflow-x" :header="networkWidgetHeader">
|
||||||
<div class>
|
<div class>
|
||||||
<div class="px-3 px-lg-4">
|
<div class="px-3 px-lg-4">
|
||||||
<b-row>
|
<b-row>
|
||||||
@ -92,8 +139,11 @@
|
|||||||
<stat
|
<stat
|
||||||
title="Connections"
|
title="Connections"
|
||||||
:value="stats.peers"
|
:value="stats.peers"
|
||||||
suffix="Peers"
|
:suffix="`${stats.peers === 1 ? 'Peer' : 'Peers'}`"
|
||||||
showNumericChange
|
showPercentChange
|
||||||
|
:showPopover="true"
|
||||||
|
popoverId="connections-popover"
|
||||||
|
:popoverContent="[`Clearnet${torProxy ? ' (over Tor)': ''}: ${peers.clearnet}`, `Tor: ${peers.tor}`, `I2P: ${peers.i2p}`]"
|
||||||
></stat>
|
></stat>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col col cols="6" md="3">
|
<b-col col cols="6" md="3">
|
||||||
@ -118,6 +168,9 @@
|
|||||||
:value="abbreviateSize(stats.blockchainSize)[0]"
|
:value="abbreviateSize(stats.blockchainSize)[0]"
|
||||||
:suffix="abbreviateSize(stats.blockchainSize)[1]"
|
:suffix="abbreviateSize(stats.blockchainSize)[1]"
|
||||||
showPercentChange
|
showPercentChange
|
||||||
|
:showPopover="pruned"
|
||||||
|
popoverId="blockchain-size-popover"
|
||||||
|
:popoverContent='[`Your "Prune Old Blocks" setting has set the max blockchain size to ${pruneTargetSizeGB}GB.`]'
|
||||||
></stat>
|
></stat>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
@ -127,10 +180,14 @@
|
|||||||
</card-widget>
|
</card-widget>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
|
|
||||||
<b-modal id="connect-modal" size="lg" centered hide-footer>
|
<b-modal id="connect-modal" size="lg" centered hide-footer>
|
||||||
<connection-modal></connection-modal>
|
<connection-modal></connection-modal>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
|
||||||
|
<b-modal id="advanced-settings-modal" size="lg" centered hide-footer scrollable>
|
||||||
|
<advanced-settings-modal :isSettingsDisabled="isRestartPending" @submit="saveSettingsAndRestartBitcoin" @clickRestoreDefaults="restoreDefaultSettingsAndRestartBitcoin"></advanced-settings-modal>
|
||||||
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -138,27 +195,52 @@
|
|||||||
// import Vue from "vue";
|
// import Vue from "vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
|
|
||||||
|
import API from "@/helpers/api";
|
||||||
|
import delay from "@/helpers/delay";
|
||||||
|
|
||||||
import CardWidget from "@/components/CardWidget";
|
import CardWidget from "@/components/CardWidget";
|
||||||
import Blockchain from "@/components/Blockchain";
|
import Blockchain from "@/components/Blockchain";
|
||||||
import Stat from "@/components/Utility/Stat";
|
import Stat from "@/components/Utility/Stat";
|
||||||
import ConnectionModal from "@/components/ConnectionModal";
|
import ConnectionModal from "@/components/ConnectionModal";
|
||||||
|
import AdvancedSettingsModal from "@/components/AdvancedSettingsModal";
|
||||||
import ChartWrapper from "@/components/ChartWrapper.vue";
|
import ChartWrapper from "@/components/ChartWrapper.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {
|
||||||
|
isRestartPending: false,
|
||||||
|
showRestartError: false
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
|
isBitcoinCoreOperational: state => state.bitcoin.operational,
|
||||||
syncPercent: state => state.bitcoin.percent,
|
syncPercent: state => state.bitcoin.percent,
|
||||||
blocks: state => state.bitcoin.blocks,
|
blocks: state => state.bitcoin.blocks,
|
||||||
version: state => state.bitcoin.version,
|
version: state => state.bitcoin.version,
|
||||||
currentBlock: state => state.bitcoin.currentBlock,
|
currentBlock: state => state.bitcoin.currentBlock,
|
||||||
blockHeight: state => state.bitcoin.blockHeight,
|
blockHeight: state => state.bitcoin.blockHeight,
|
||||||
stats: state => state.bitcoin.stats,
|
stats: state => state.bitcoin.stats,
|
||||||
|
peers: state => state.bitcoin.peers,
|
||||||
rpc: state => state.bitcoin.rpc,
|
rpc: state => state.bitcoin.rpc,
|
||||||
p2p: state => state.bitcoin.p2p
|
p2p: state => state.bitcoin.p2p,
|
||||||
})
|
reindex: state => state.user.bitcoinConfig.reindex,
|
||||||
|
network: state => state.user.bitcoinConfig.network,
|
||||||
|
pruned: state => state.bitcoin.pruned,
|
||||||
|
pruneTargetSizeGB: state => state.bitcoin.pruneTargetSizeGB,
|
||||||
|
torProxy: state => state.user.bitcoinConfig.torProxyForClearnet
|
||||||
|
}),
|
||||||
|
showReindexInProgressAlert() {
|
||||||
|
return this.reindex && this.syncPercent !== 100 && !this.isRestartPending;
|
||||||
|
},
|
||||||
|
showReindexCompleteAlert() {
|
||||||
|
return this.reindex && this.syncPercent === 100 && !this.isRestartPending;
|
||||||
|
},
|
||||||
|
networkWidgetHeader() {
|
||||||
|
if (!this.network || this.network === "main") return "Network";
|
||||||
|
if (this.network === "test") return "Network (testnet)";
|
||||||
|
return `Network (${this.network})`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
random(min, max) {
|
random(min, max) {
|
||||||
@ -185,33 +267,113 @@ export default {
|
|||||||
fetchStats() {
|
fetchStats() {
|
||||||
this.$store.dispatch("bitcoin/getStats");
|
this.$store.dispatch("bitcoin/getStats");
|
||||||
},
|
},
|
||||||
|
fetchPeers() {
|
||||||
|
this.$store.dispatch("bitcoin/getPeers");
|
||||||
|
},
|
||||||
fetchConnectionDetails() {
|
fetchConnectionDetails() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this.$store.dispatch("bitcoin/getP2PInfo"),
|
this.$store.dispatch("bitcoin/getP2PInfo"),
|
||||||
this.$store.dispatch("bitcoin/getRpcInfo")
|
this.$store.dispatch("bitcoin/getRpcInfo")
|
||||||
]);
|
]);
|
||||||
|
},
|
||||||
|
fetchBitcoinConfigSettings() {
|
||||||
|
this.$store.dispatch("user/getBitcoinConfig");
|
||||||
|
},
|
||||||
|
async saveSettingsAndRestartBitcoin(bitcoinConfig) {
|
||||||
|
try {
|
||||||
|
this.isRestartPending = true;
|
||||||
|
this.$store.dispatch("user/updateBitcoinConfig", bitcoinConfig);
|
||||||
|
|
||||||
|
const response = await API.post(
|
||||||
|
`${process.env.VUE_APP_API_BASE_URL}/v1/bitcoind/system/update-bitcoin-config`,
|
||||||
|
{ bitcoinConfig }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
// reload the page to reset all state and show loading view while bitcoin core restarts.
|
||||||
|
this.$router.push({ query: { restart: "1" } });
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
this.fetchBitcoinConfigSettings();
|
||||||
|
this.showRestartError = true;
|
||||||
|
this.isRestartPending = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.fetchBitcoinConfigSettings();
|
||||||
|
this.showRestartError = true;
|
||||||
|
this.$bvModal.hide("advanced-settings-modal");
|
||||||
|
this.isRestartPending = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restoreDefaultSettingsAndRestartBitcoin() {
|
||||||
|
try {
|
||||||
|
this.isRestartPending = true;
|
||||||
|
|
||||||
|
const response = await API.post(
|
||||||
|
`${process.env.VUE_APP_API_BASE_URL}/v1/bitcoind/system/restore-default-bitcoin-config`
|
||||||
|
);
|
||||||
|
|
||||||
|
// dispatch getBitcoinConfig after post request to avoid referencing default values in the store.
|
||||||
|
this.$store.dispatch("user/getBitcoinConfig");
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
// reload the page to reset all state and show loading view while bitcoin core restarts.
|
||||||
|
this.$router.push({ query: { restart: "1" } });
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
this.fetchBitcoinConfigSettings();
|
||||||
|
this.showRestartError = true;
|
||||||
|
this.isRestartPending = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.fetchBitcoinConfigSettings();
|
||||||
|
this.showRestartError = true;
|
||||||
|
this.$bvModal.hide("advanced-settings-modal");
|
||||||
|
this.isRestartPending = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
|
// fetch settings first because bitcoin core
|
||||||
|
// is not operational if pruning is in progress
|
||||||
|
this.fetchBitcoinConfigSettings();
|
||||||
|
|
||||||
|
// wait until bitcoin core is operational
|
||||||
|
while (true) { /* eslint-disable-line */
|
||||||
|
await this.$store.dispatch("bitcoin/getStatus");
|
||||||
|
if (this.isBitcoinCoreOperational) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await delay(1000);
|
||||||
|
}
|
||||||
this.$store.dispatch("bitcoin/getVersion");
|
this.$store.dispatch("bitcoin/getVersion");
|
||||||
this.fetchStats();
|
this.fetchStats();
|
||||||
|
this.fetchPeers();
|
||||||
this.fetchConnectionDetails();
|
this.fetchConnectionDetails();
|
||||||
this.interval = window.setInterval(this.fetchStats, 5000);
|
this.interval = window.setInterval(() => {
|
||||||
|
this.fetchStats();
|
||||||
|
this.fetchPeers();
|
||||||
|
}, 5000);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
window.clearInterval(this.interval);
|
if (this.interval) {
|
||||||
|
window.clearInterval(this.interval);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
CardWidget,
|
CardWidget,
|
||||||
Blockchain,
|
Blockchain,
|
||||||
Stat,
|
Stat,
|
||||||
ConnectionModal,
|
ConnectionModal,
|
||||||
ChartWrapper,
|
AdvancedSettingsModal,
|
||||||
|
ChartWrapper
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.app-icon {
|
.app-icon {
|
||||||
height: 120px;
|
height: 120px;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
@ -220,4 +382,23 @@ export default {
|
|||||||
.overflow-x {
|
.overflow-x {
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.dropdown-menu {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-settings {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-settings:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
14297
ui/yarn.lock
14297
ui/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -4,10 +4,22 @@ module.exports = {
|
|||||||
REQUEST_CORRELATION_ID_KEY: 'reqId',
|
REQUEST_CORRELATION_ID_KEY: 'reqId',
|
||||||
DEVICE_HOSTNAME: process.env.DEVICE_HOSTNAME || 'umbrel.local',
|
DEVICE_HOSTNAME: process.env.DEVICE_HOSTNAME || 'umbrel.local',
|
||||||
BITCOIN_P2P_HIDDEN_SERVICE: process.env.BITCOIN_P2P_HIDDEN_SERVICE,
|
BITCOIN_P2P_HIDDEN_SERVICE: process.env.BITCOIN_P2P_HIDDEN_SERVICE,
|
||||||
BITCOIN_P2P_PORT: process.env.BITCOIN_P2P_PORT || 8333,
|
|
||||||
BITCOIN_RPC_HIDDEN_SERVICE: process.env.BITCOIN_RPC_HIDDEN_SERVICE,
|
BITCOIN_RPC_HIDDEN_SERVICE: process.env.BITCOIN_RPC_HIDDEN_SERVICE,
|
||||||
BITCOIN_RPC_PORT: process.env.BITCOIN_RPC_PORT || 8332,
|
|
||||||
BITCOIN_RPC_USER: process.env.BITCOIN_RPC_USER || 'umbrel',
|
BITCOIN_RPC_USER: process.env.BITCOIN_RPC_USER || 'umbrel',
|
||||||
BITCOIN_RPC_PASSWORD: process.env.BITCOIN_RPC_PASSWORD || 'moneyprintergobrrr',
|
BITCOIN_RPC_PASSWORD: process.env.BITCOIN_RPC_PASSWORD || 'moneyprintergobrrr',
|
||||||
DEVICE_DOMAIN_NAME: process.env.DEVICE_DOMAIN_NAME
|
DEVICE_DOMAIN_NAME: process.env.DEVICE_DOMAIN_NAME,
|
||||||
};
|
JSON_STORE_FILE: process.env.JSON_STORE_FILE || "/data/bitcoin-config.json",
|
||||||
|
UMBREL_BITCOIN_CONF_FILEPATH: process.env.UMBREL_BITCOIN_CONF_FILE || "/bitcoin/.bitcoin/umbrel-bitcoin.conf",
|
||||||
|
BITCOIN_CONF_FILEPATH: process.env.BITCOIN_CONF_FILE || "/bitcoin/.bitcoin/bitcoin.conf",
|
||||||
|
BITCOIN_INITIALIZE_WITH_CLEARNET_OVER_TOR: process.env.BITCOIN_INITIALIZE_WITH_CLEARNET_OVER_TOR === 'true',
|
||||||
|
BITCOIN_P2P_PORT: process.env.BITCOIN_P2P_PORT || 8333,
|
||||||
|
BITCOIN_RPC_PORT: process.env.BITCOIN_RPC_PORT || 8332,
|
||||||
|
BITCOIN_DEFAULT_NETWORK: process.env.BITCOIN_DEFAULT_NETWORK || "mainnet",
|
||||||
|
BITCOIND_IP: process.env.BITCOIND_IP,
|
||||||
|
TOR_PROXY_IP: process.env.TOR_PROXY_IP,
|
||||||
|
TOR_PROXY_PORT: process.env.TOR_PROXY_PORT,
|
||||||
|
TOR_PROXY_CONTROL_PORT: process.env.TOR_PROXY_CONTROL_PORT,
|
||||||
|
TOR_PROXY_CONTROL_PASSWORD: process.env.TOR_PROXY_CONTROL_PASSWORD,
|
||||||
|
I2P_DAEMON_IP: process.env.I2P_DAEMON_IP,
|
||||||
|
I2P_DAEMON_PORT: process.env.I2P_DAEMON_PORT
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user