mirror of
https://github.com/Retropex/umbrel-bitcoin.git
synced 2025-05-12 03:00:49 +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
|
||||
coverage
|
||||
.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 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.
|
||||
*/
|
||||
@ -29,6 +34,34 @@ server.listen(port);
|
||||
server.on('error', onError);
|
||||
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.
|
||||
*/
|
||||
@ -81,11 +114,16 @@ function onError(error) {
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
async function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('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"
|
||||
|
||||
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:
|
||||
image: lncm/bitcoind:v22.0@sha256:37a1adb29b3abc9f972f0d981f45e41e5fca2e22816a023faa9fdc0084aa4507
|
||||
command: -regtest -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=umbrel:5071d8b3ba93e53e414446ff9f1b7d7b$$375e9731abd2cd2c2c44d2327ec19f4f2644256fdeaf4fc5229bf98b778aafec
|
||||
image: lncm/bitcoind:v24.0@sha256:db19fe46f30acd3854f4f0d239278137d828ce3728f925c8d92faaab1ba8556a
|
||||
user: "1000:1000"
|
||||
command: -port=8333 -rpcport=8332 -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcauth=umbrel:5071d8b3ba93e53e414446ff9f1b7d7b$$375e9731abd2cd2c2c44d2327ec19f4f2644256fdeaf4fc5229bf98b778aafec
|
||||
volumes:
|
||||
- ${PWD}/data/bitcoin:/data/.bitcoin
|
||||
restart: on-failure
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 15m30s
|
||||
ports:
|
||||
- "18443:18443" # regtest
|
||||
- "8332:8332" # rpc
|
||||
- "8333:8333" # p2p
|
||||
networks:
|
||||
default:
|
||||
ipv4_address: "10.21.0.4"
|
||||
|
||||
server:
|
||||
build: .
|
||||
depends_on: [bitcoind]
|
||||
@ -17,12 +50,35 @@ services:
|
||||
restart: on-failure
|
||||
ports:
|
||||
- "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:
|
||||
PORT: "3005"
|
||||
BITCOIN_HOST: "bitcoind"
|
||||
RPC_PORT: "18443" # - regtest
|
||||
BITCOIN_P2P_PORT: 8333
|
||||
BITCOIN_RPC_PORT: 8332
|
||||
BITCOIN_DEFAULT_NETWORK: "regtest"
|
||||
RPC_USER: "umbrel"
|
||||
RPC_PASSWORD: "moneyprintergobrrr"
|
||||
BITCOIN_RPC_HIDDEN_SERVICE: "somehiddenservice.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 inBoundConnections = 0;
|
||||
var clearnetConnections = 0;
|
||||
var torConnections = 0;
|
||||
var i2pConnections = 0;
|
||||
|
||||
peerInfo.result.forEach(function(peer) {
|
||||
if (peer.inbound === false) {
|
||||
outBoundConnections++;
|
||||
|
||||
return;
|
||||
} else {
|
||||
inBoundConnections++;
|
||||
}
|
||||
inBoundConnections++;
|
||||
|
||||
if (peer.network === "onion") {
|
||||
torConnections++;
|
||||
} else if (peer.network === "i2p") {
|
||||
i2pConnections++;
|
||||
} else {
|
||||
// ipv4 and ipv6 are clearnet
|
||||
clearnetConnections++;
|
||||
}
|
||||
});
|
||||
|
||||
const connections = {
|
||||
total: inBoundConnections + outBoundConnections,
|
||||
inbound: inBoundConnections,
|
||||
outbound: outBoundConnections
|
||||
outbound: outBoundConnections,
|
||||
clearnet: clearnetConnections,
|
||||
tor: torConnections,
|
||||
i2p: i2pConnections
|
||||
};
|
||||
|
||||
return connections;
|
||||
@ -72,12 +86,16 @@ async function getLocalSyncInfo() {
|
||||
var blockCount = blockChainInfo.blocks;
|
||||
var headerCount = blockChainInfo.headers;
|
||||
var percent = blockChainInfo.verificationprogress;
|
||||
var pruned = blockChainInfo.pruned;
|
||||
var pruneTargetSize = blockChainInfo.pruneTargetSize;
|
||||
|
||||
return {
|
||||
chain,
|
||||
percent,
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO - consider using getNetworkInfo for info on proxy for ipv4 and ipv6
|
||||
async function getVersion() {
|
||||
const networkInfo = await bitcoindService.getNetworkInfo();
|
||||
const unformattedVersion = networkInfo.result.subversion;
|
||||
@ -259,6 +278,11 @@ async function nodeStatusSummary() {
|
||||
};
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
const stopResponse = await bitcoindService.stop();
|
||||
return {stopResponse};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getBlockHash,
|
||||
getTransaction,
|
||||
@ -273,5 +297,6 @@ module.exports = {
|
||||
getSyncStatus,
|
||||
getVersion,
|
||||
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;
|
||||
|
||||
function getBitcoinP2PConnectionDetails() {
|
||||
const torAddress = constants.BITCOIN_P2P_HIDDEN_SERVICE;
|
||||
const port = constants.BITCOIN_P2P_PORT;
|
||||
const torConnectionString = `${torAddress}:${port}`;
|
||||
const localAddress = constants.DEVICE_DOMAIN_NAME;
|
||||
const localConnectionString = `${localAddress}:${port}`;
|
||||
const torAddress = constants.BITCOIN_P2P_HIDDEN_SERVICE;
|
||||
const port = constants.BITCOIN_P2P_PORT;
|
||||
const torConnectionString = `${torAddress}:${port}`;
|
||||
const localAddress = constants.DEVICE_DOMAIN_NAME;
|
||||
const localConnectionString = `${localAddress}:${port}`;
|
||||
|
||||
return {
|
||||
torAddress,
|
||||
port,
|
||||
torConnectionString,
|
||||
localAddress,
|
||||
localConnectionString
|
||||
};
|
||||
return {
|
||||
torAddress,
|
||||
port,
|
||||
torConnectionString,
|
||||
localAddress,
|
||||
localConnectionString
|
||||
};
|
||||
}
|
||||
|
||||
function getBitcoinRPCConnectionDetails() {
|
||||
|
@ -2,6 +2,8 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const systemLogic = require('logic/system.js');
|
||||
const diskLogic = require('logic/disk');
|
||||
const bitcoindLogic = require('logic/bitcoind.js');
|
||||
const safeHandler = require('utils/safeHandler');
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
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;
|
@ -1,6 +1,5 @@
|
||||
const RpcClient = require('bitcoind-rpc');
|
||||
const camelizeKeys = require('camelize-keys');
|
||||
|
||||
const BitcoindError = require('models/errors.js').BitcoindError;
|
||||
|
||||
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
|
||||
pass: BITCOIND_RPC_PASSWORD, // eslint-disable-line object-shorthand
|
||||
host: BITCOIND_HOST,
|
||||
port: BITCOIND_RPC_PORT,
|
||||
port: BITCOIND_RPC_PORT
|
||||
});
|
||||
|
||||
function promiseify(rpcObj, rpcFn, what) {
|
||||
@ -115,6 +114,10 @@ function help() {
|
||||
return promiseify(rpcClient, rpcClient.help, 'help data');
|
||||
}
|
||||
|
||||
function stop() {
|
||||
return promiseify(rpcClient, rpcClient.stop, 'stop');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMiningInfo,
|
||||
getBestBlockHash,
|
||||
@ -127,4 +130,5 @@ module.exports = {
|
||||
getMempoolInfo,
|
||||
getNetworkInfo,
|
||||
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",
|
||||
"highcharts": "^9.3.2",
|
||||
"highcharts-vue": "^1.4.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"moment": "^2.24.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"vue": "^2.6.10",
|
||||
@ -9609,6 +9610,11 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@ -15216,10 +15222,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-slider-component": {
|
||||
"version": "3.2.15",
|
||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.15.tgz",
|
||||
"integrity": "sha512-FpmMsQ6MQFn22B6boDcEjRmuawdaHwjHRVZiuv5w37jijHra6+HogjSrh3mb42jE+PUIFFagXi36oFEzpDbadg==",
|
||||
"license": "MIT",
|
||||
"version": "3.2.20",
|
||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.20.tgz",
|
||||
"integrity": "sha512-S5+4d6zdL+/ClpDQoIgImIdXRv2b+75PIy3cDGsZsakhroJD6cSFA0juY/AblGqhvIkNcBIU354eOw6T26DWbA==",
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue-property-decorator": "^8.0.0"
|
||||
@ -22998,6 +23003,11 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"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": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@ -27180,9 +27190,9 @@
|
||||
"integrity": "sha512-FUlILrW3DGitS2h+Xaw8aRNvGTwtuaxrRkNSHWTizOfLUie7wuYwezeZ50iflRn8YPV5kxmU2LQuu3nM/b3Zsg=="
|
||||
},
|
||||
"vue-slider-component": {
|
||||
"version": "3.2.15",
|
||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.15.tgz",
|
||||
"integrity": "sha512-FpmMsQ6MQFn22B6boDcEjRmuawdaHwjHRVZiuv5w37jijHra6+HogjSrh3mb42jE+PUIFFagXi36oFEzpDbadg==",
|
||||
"version": "3.2.20",
|
||||
"resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-3.2.20.tgz",
|
||||
"integrity": "sha512-S5+4d6zdL+/ClpDQoIgImIdXRv2b+75PIy3cDGsZsakhroJD6cSFA0juY/AblGqhvIkNcBIU354eOw6T26DWbA==",
|
||||
"requires": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue-property-decorator": "^8.0.0"
|
||||
|
@ -16,6 +16,7 @@
|
||||
"countup.js": "^2.0.4",
|
||||
"highcharts": "^9.3.2",
|
||||
"highcharts-vue": "^1.4.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"moment": "^2.24.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"vue": "^2.6.10",
|
||||
|
@ -62,7 +62,7 @@ export default {
|
||||
|
||||
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) {
|
||||
this.loadingProgress = 40;
|
||||
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: {
|
||||
...mapState({
|
||||
isBitcoinCoreOperational: state => state.bitcoin.operational,
|
||||
syncPercent: state => state.bitcoin.percent,
|
||||
blocks: state => state.bitcoin.blocks
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
if (this.pollInProgress) {
|
||||
return;
|
||||
|
@ -5,6 +5,7 @@
|
||||
<img alt="Umbrel" src="@/assets/icon.svg" class="mb-5 logo" />
|
||||
|
||||
<!-- <b-spinner class="my-4" variant="primary"></b-spinner> -->
|
||||
<small v-if="isRestarting" class="text-muted mb-3">Restarting...</small>
|
||||
<b-progress
|
||||
:value="progress"
|
||||
class="mb-2 w-75"
|
||||
@ -25,10 +26,17 @@
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
isRestarting: false
|
||||
};
|
||||
},
|
||||
props: { progress: Number },
|
||||
created() {},
|
||||
created() {
|
||||
if (this.$route.query.hasOwnProperty("restart")) {
|
||||
this.isRestarting = true;
|
||||
this.$router.replace({ query: {} });
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
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;">{{
|
||||
suffix
|
||||
}}</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
|
||||
@ -100,6 +121,18 @@ export default {
|
||||
showPercentChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showPopover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
popoverId: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
popoverContent: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
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
|
||||
import system from "./modules/system";
|
||||
import bitcoin from "./modules/bitcoin";
|
||||
import user from "./modules/user";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -47,6 +48,7 @@ export default new Vuex.Store({
|
||||
getters,
|
||||
modules: {
|
||||
system,
|
||||
bitcoin
|
||||
bitcoin,
|
||||
user
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,8 @@
|
||||
import API from "@/helpers/api";
|
||||
import { toPrecision } from "@/helpers/units";
|
||||
|
||||
const BYTES_PER_GB = 1000000000;
|
||||
|
||||
// Initial state
|
||||
const state = () => ({
|
||||
operational: false,
|
||||
@ -24,6 +26,8 @@ const state = () => ({
|
||||
},
|
||||
currentBlock: 0,
|
||||
chain: "",
|
||||
pruned: false,
|
||||
pruneTargetSizeGB: 0,
|
||||
blockHeight: 0,
|
||||
blocks: [],
|
||||
percent: -1, //for loading state
|
||||
@ -37,7 +41,10 @@ const state = () => ({
|
||||
peers: {
|
||||
total: 0,
|
||||
inbound: 0,
|
||||
outbound: 0
|
||||
outbound: 0,
|
||||
clearnet: 0,
|
||||
tor: 0,
|
||||
i2p: 0
|
||||
},
|
||||
chartData: []
|
||||
});
|
||||
@ -53,6 +60,8 @@ const mutations = {
|
||||
state.currentBlock = sync.currentBlock;
|
||||
state.blockHeight = sync.headerCount;
|
||||
state.chain = sync.chain;
|
||||
state.pruned = sync.pruned;
|
||||
state.pruneTargetSizeGB = Math.round(sync.pruneTargetSize / BYTES_PER_GB);
|
||||
|
||||
if (sync.status === "calibrating") {
|
||||
state.calibrating = true;
|
||||
@ -104,6 +113,9 @@ const mutations = {
|
||||
state.peers.total = peers.total || 0;
|
||||
state.peers.inbound = peers.inbound || 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) {
|
||||
|
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"
|
||||
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>
|
||||
<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>
|
||||
<span class="d-block text-muted">{{
|
||||
version ? `Bitcoin Core ${version}` : "..."
|
||||
@ -25,6 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
type="button"
|
||||
variant="primary"
|
||||
@ -34,8 +36,53 @@
|
||||
<b-icon icon="plus" aria-hidden="true"></b-icon>
|
||||
Connect
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<b-row class="row-eq-height">
|
||||
@ -84,7 +131,7 @@
|
||||
</card-widget>
|
||||
</b-col>
|
||||
<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="px-3 px-lg-4">
|
||||
<b-row>
|
||||
@ -92,8 +139,11 @@
|
||||
<stat
|
||||
title="Connections"
|
||||
:value="stats.peers"
|
||||
suffix="Peers"
|
||||
showNumericChange
|
||||
:suffix="`${stats.peers === 1 ? 'Peer' : 'Peers'}`"
|
||||
showPercentChange
|
||||
:showPopover="true"
|
||||
popoverId="connections-popover"
|
||||
:popoverContent="[`Clearnet${torProxy ? ' (over Tor)': ''}: ${peers.clearnet}`, `Tor: ${peers.tor}`, `I2P: ${peers.i2p}`]"
|
||||
></stat>
|
||||
</b-col>
|
||||
<b-col col cols="6" md="3">
|
||||
@ -118,6 +168,9 @@
|
||||
:value="abbreviateSize(stats.blockchainSize)[0]"
|
||||
:suffix="abbreviateSize(stats.blockchainSize)[1]"
|
||||
showPercentChange
|
||||
:showPopover="pruned"
|
||||
popoverId="blockchain-size-popover"
|
||||
:popoverContent='[`Your "Prune Old Blocks" setting has set the max blockchain size to ${pruneTargetSizeGB}GB.`]'
|
||||
></stat>
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -127,10 +180,14 @@
|
||||
</card-widget>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
|
||||
<b-modal id="connect-modal" size="lg" centered hide-footer>
|
||||
<connection-modal></connection-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>
|
||||
</template>
|
||||
|
||||
@ -138,27 +195,52 @@
|
||||
// import Vue from "vue";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
import API from "@/helpers/api";
|
||||
import delay from "@/helpers/delay";
|
||||
|
||||
import CardWidget from "@/components/CardWidget";
|
||||
import Blockchain from "@/components/Blockchain";
|
||||
import Stat from "@/components/Utility/Stat";
|
||||
import ConnectionModal from "@/components/ConnectionModal";
|
||||
import AdvancedSettingsModal from "@/components/AdvancedSettingsModal";
|
||||
import ChartWrapper from "@/components/ChartWrapper.vue";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
isRestartPending: false,
|
||||
showRestartError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isBitcoinCoreOperational: state => state.bitcoin.operational,
|
||||
syncPercent: state => state.bitcoin.percent,
|
||||
blocks: state => state.bitcoin.blocks,
|
||||
version: state => state.bitcoin.version,
|
||||
currentBlock: state => state.bitcoin.currentBlock,
|
||||
blockHeight: state => state.bitcoin.blockHeight,
|
||||
stats: state => state.bitcoin.stats,
|
||||
peers: state => state.bitcoin.peers,
|
||||
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: {
|
||||
random(min, max) {
|
||||
@ -185,33 +267,113 @@ export default {
|
||||
fetchStats() {
|
||||
this.$store.dispatch("bitcoin/getStats");
|
||||
},
|
||||
fetchPeers() {
|
||||
this.$store.dispatch("bitcoin/getPeers");
|
||||
},
|
||||
fetchConnectionDetails() {
|
||||
return Promise.all([
|
||||
this.$store.dispatch("bitcoin/getP2PInfo"),
|
||||
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.fetchStats();
|
||||
this.fetchPeers();
|
||||
this.fetchConnectionDetails();
|
||||
this.interval = window.setInterval(this.fetchStats, 5000);
|
||||
this.interval = window.setInterval(() => {
|
||||
this.fetchStats();
|
||||
this.fetchPeers();
|
||||
}, 5000);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.clearInterval(this.interval);
|
||||
if (this.interval) {
|
||||
window.clearInterval(this.interval);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CardWidget,
|
||||
Blockchain,
|
||||
Stat,
|
||||
ConnectionModal,
|
||||
ChartWrapper,
|
||||
AdvancedSettingsModal,
|
||||
ChartWrapper
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.app-icon {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
@ -220,4 +382,23 @@ export default {
|
||||
.overflow-x {
|
||||
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',
|
||||
DEVICE_HOSTNAME: process.env.DEVICE_HOSTNAME || 'umbrel.local',
|
||||
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_PORT: process.env.BITCOIN_RPC_PORT || 8332,
|
||||
BITCOIN_RPC_USER: process.env.BITCOIN_RPC_USER || 'umbrel',
|
||||
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