chore: release v1.7.49-alpha
This commit is contained in:
+121
-43
@@ -606,7 +606,8 @@
|
||||
console.log('[Bitcoin UI] Script loaded, initializing...');
|
||||
|
||||
// RPC Configuration - Use local Nginx proxy within container
|
||||
const RPC_ENDPOINT = '/bitcoin-rpc/';
|
||||
const RPC_ENDPOINT = 'bitcoin-rpc/';
|
||||
const STATUS_ENDPOINT = 'bitcoin-status';
|
||||
console.log('[Bitcoin UI] RPC Endpoint:', RPC_ENDPOINT);
|
||||
|
||||
// Make RPC call to Bitcoin node via local proxy
|
||||
@@ -645,6 +646,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchBitcoinStatus() {
|
||||
const response = await fetch(STATUS_ENDPOINT, { cache: 'no-store' });
|
||||
if (!response.ok) {
|
||||
throw new Error(`status HTTP ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Implementation branding — detected from getnetworkinfo.subversion.
|
||||
// Bitcoin Knots identifies as "/Satoshi:<ver>/Knots:<date>/", Bitcoin Core as "/Satoshi:<ver>/".
|
||||
let brandingApplied = false;
|
||||
@@ -672,22 +681,62 @@
|
||||
|
||||
// Track last block count for animations
|
||||
let lastBlockCount = 0;
|
||||
let consecutiveRpcFailures = 0;
|
||||
let lastSuccessfulUpdateAt = 0;
|
||||
|
||||
function formatPercent(value) {
|
||||
if (!Number.isFinite(value) || value <= 0) return '0.00';
|
||||
if (value < 0.01) return '<0.01';
|
||||
return value.toFixed(2);
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return null;
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let value = bytes;
|
||||
let unit = 0;
|
||||
while (value >= 1000 && unit < units.length - 1) {
|
||||
value /= 1000;
|
||||
unit += 1;
|
||||
}
|
||||
return `${value.toFixed(unit >= 3 ? 1 : 0)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
// Update blockchain info
|
||||
async function updateBlockchainInfo() {
|
||||
console.log('[Bitcoin UI] updateBlockchainInfo() called');
|
||||
try {
|
||||
const blockchainInfo = await callRPC('getblockchaininfo');
|
||||
const status = await fetchBitcoinStatus();
|
||||
const blockchainInfo = status.blockchain_info;
|
||||
console.log('[Bitcoin UI] blockchainInfo:', blockchainInfo);
|
||||
|
||||
if (!blockchainInfo) {
|
||||
console.error('[Bitcoin UI] No blockchain info received');
|
||||
document.getElementById('syncStatusText').textContent = 'Unable to connect to Bitcoin node';
|
||||
document.getElementById('syncStatusText').className = 'text-red-400 text-sm';
|
||||
consecutiveRpcFailures += 1;
|
||||
const syncStatusText = document.getElementById('syncStatusText');
|
||||
const syncIcon = document.getElementById('syncIcon');
|
||||
if (syncStatusText) {
|
||||
if (status.stale) {
|
||||
syncStatusText.textContent = status.error || 'Bitcoin node is reconnecting... showing last known values';
|
||||
syncStatusText.className = 'text-yellow-300 text-sm font-medium';
|
||||
} else if (consecutiveRpcFailures < 6) {
|
||||
syncStatusText.textContent = status.error || 'Connecting to Bitcoin node...';
|
||||
syncStatusText.className = 'text-yellow-300 text-sm font-medium';
|
||||
} else {
|
||||
syncStatusText.textContent = status.error || 'Bitcoin node is not responding yet';
|
||||
syncStatusText.className = 'text-red-400 text-sm font-medium';
|
||||
}
|
||||
}
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
syncIcon.classList.remove('text-green-500');
|
||||
}
|
||||
return;
|
||||
}
|
||||
consecutiveRpcFailures = 0;
|
||||
lastSuccessfulUpdateAt = Date.now();
|
||||
|
||||
const networkInfo = await callRPC('getnetworkinfo');
|
||||
const networkInfo = status.network_info;
|
||||
|
||||
applyImplBranding(networkInfo && networkInfo.subversion);
|
||||
|
||||
@@ -743,44 +792,51 @@
|
||||
}
|
||||
|
||||
// Populate Settings — Transaction Index, ZMQ, RPC (fire-and-forget)
|
||||
(async () => {
|
||||
const txIndexEl = document.getElementById('settingsTxIndex');
|
||||
if (txIndexEl) {
|
||||
const idx = await callRPC('getindexinfo');
|
||||
if (idx && typeof idx === 'object') {
|
||||
const names = Object.keys(idx);
|
||||
txIndexEl.textContent = names.length
|
||||
? `Enabled: ${names.join(', ')}`
|
||||
: 'Disabled';
|
||||
} else {
|
||||
txIndexEl.textContent = 'Disabled';
|
||||
}
|
||||
const txIndexEl = document.getElementById('settingsTxIndex');
|
||||
if (txIndexEl) {
|
||||
const idx = status.index_info;
|
||||
if (idx && typeof idx === 'object') {
|
||||
const names = Object.keys(idx);
|
||||
txIndexEl.textContent = names.length
|
||||
? `Enabled: ${names.join(', ')}`
|
||||
: 'Disabled';
|
||||
} else {
|
||||
txIndexEl.textContent = 'Unavailable while node starts';
|
||||
}
|
||||
const zmqEl = document.getElementById('settingsZmq');
|
||||
if (zmqEl) {
|
||||
const zmq = await callRPC('getzmqnotifications');
|
||||
if (Array.isArray(zmq) && zmq.length) {
|
||||
zmqEl.textContent = zmq.map(z => `${z.type}@${z.address}`).join('; ');
|
||||
} else {
|
||||
zmqEl.textContent = 'Not enabled';
|
||||
}
|
||||
}
|
||||
const zmqEl = document.getElementById('settingsZmq');
|
||||
if (zmqEl) {
|
||||
const zmq = status.zmq_notifications;
|
||||
if (Array.isArray(zmq) && zmq.length) {
|
||||
zmqEl.textContent = zmq.map(z => `${z.type}@${z.address}`).join('; ');
|
||||
} else if (Array.isArray(zmq)) {
|
||||
zmqEl.textContent = 'Not enabled';
|
||||
} else {
|
||||
zmqEl.textContent = 'Unavailable while node starts';
|
||||
}
|
||||
const rpcEl = document.getElementById('settingsRpc');
|
||||
if (rpcEl && networkInfo) {
|
||||
const port = chain === 'main' ? 8332 : (chain === 'test' ? 18332 : (chain === 'signet' ? 38332 : 18443));
|
||||
rpcEl.textContent = `Reachable on port ${port}`;
|
||||
}
|
||||
})();
|
||||
}
|
||||
const rpcEl = document.getElementById('settingsRpc');
|
||||
if (rpcEl) {
|
||||
const port = chain === 'main' ? 8332 : (chain === 'test' ? 18332 : (chain === 'signet' ? 38332 : 18443));
|
||||
rpcEl.textContent = status.stale
|
||||
? `Reconnecting on port ${port}`
|
||||
: `Reachable on port ${port}`;
|
||||
}
|
||||
|
||||
// Update sync status
|
||||
const blocks = blockchainInfo.blocks || 0;
|
||||
const headers = blockchainInfo.headers || 0;
|
||||
const verificationProgress = blockchainInfo.verificationprogress || 0;
|
||||
const isSynced = blocks >= headers - 1;
|
||||
const initialBlockDownload = blockchainInfo.initialblockdownload === true;
|
||||
const isSynced = headers > 0 && blocks >= headers - 1 && !initialBlockDownload;
|
||||
const diskSize = formatBytes(blockchainInfo.size_on_disk || 0);
|
||||
const appearsToBeReindexing = initialBlockDownload && blocks === 0 && headers > 0 && (blockchainInfo.size_on_disk || 0) > 1024 * 1024 * 1024;
|
||||
|
||||
// Calculate actual sync percentage based on blocks/headers
|
||||
const actualSyncPercentage = headers > 0 ? ((blocks / headers) * 100).toFixed(2) : '0.00';
|
||||
const verificationPercentage = (verificationProgress * 100).toFixed(2);
|
||||
const actualSyncValue = headers > 0 ? (blocks / headers) * 100 : 0;
|
||||
const actualSyncPercentage = formatPercent(actualSyncValue);
|
||||
const progressWidth = Math.max(0, Math.min(100, actualSyncValue));
|
||||
const verificationPercentage = formatPercent(verificationProgress * 100);
|
||||
|
||||
// Animate block count if it changed
|
||||
const currentHeightElem = document.getElementById('currentHeight');
|
||||
@@ -795,16 +851,27 @@
|
||||
document.getElementById('headers').textContent = headers.toLocaleString();
|
||||
document.getElementById('verificationProgress').textContent = `${verificationPercentage}%`;
|
||||
document.getElementById('syncPercentage').textContent = `${actualSyncPercentage}%`;
|
||||
document.getElementById('currentBlock').textContent = `Block ${blocks.toLocaleString()}`;
|
||||
document.getElementById('syncProgressBar').style.width = `${actualSyncPercentage}%`;
|
||||
document.getElementById('currentBlock').textContent = appearsToBeReindexing
|
||||
? 'Reindexing from disk'
|
||||
: `Block ${blocks.toLocaleString()}`;
|
||||
document.getElementById('syncProgressBar').style.width = `${progressWidth}%`;
|
||||
|
||||
// Update sync status text and icon
|
||||
const syncStatusText = document.getElementById('syncStatusText');
|
||||
const syncIcon = document.getElementById('syncIcon');
|
||||
|
||||
if (isSynced) {
|
||||
syncStatusText.textContent = '✓ Fully synchronized with the network';
|
||||
syncStatusText.className = 'text-green-400 text-sm font-medium';
|
||||
if (appearsToBeReindexing) {
|
||||
syncStatusText.textContent = `Reindexing local block files${diskSize ? ` (${diskSize} on disk)` : ''}`;
|
||||
syncStatusText.className = 'text-orange-400 text-sm font-medium';
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
syncIcon.classList.remove('text-green-500');
|
||||
}
|
||||
} else if (isSynced) {
|
||||
syncStatusText.textContent = status.stale
|
||||
? 'Bitcoin node is reconnecting... showing last known synchronized state'
|
||||
: '✓ Fully synchronized with the network';
|
||||
syncStatusText.className = status.stale ? 'text-yellow-300 text-sm font-medium' : 'text-green-400 text-sm font-medium';
|
||||
// Stop spinning when synced
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.remove('animate-spin-slow');
|
||||
@@ -812,8 +879,12 @@
|
||||
}
|
||||
} else {
|
||||
const remaining = headers - blocks;
|
||||
syncStatusText.textContent = `Syncing... ${remaining.toLocaleString()} blocks remaining`;
|
||||
syncStatusText.className = 'text-orange-400 text-sm font-medium';
|
||||
syncStatusText.textContent = status.stale
|
||||
? 'Bitcoin node is reconnecting... showing last known sync state'
|
||||
: initialBlockDownload
|
||||
? `Initial block download... ${remaining.toLocaleString()} blocks remaining`
|
||||
: `Syncing... ${remaining.toLocaleString()} blocks remaining`;
|
||||
syncStatusText.className = status.stale ? 'text-yellow-300 text-sm font-medium' : 'text-orange-400 text-sm font-medium';
|
||||
// Keep spinning while syncing
|
||||
if (syncIcon) {
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
@@ -834,8 +905,15 @@
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to update blockchain info:', error);
|
||||
document.getElementById('syncStatusText').textContent = 'Unable to fetch blockchain data';
|
||||
document.getElementById('syncStatusText').className = 'text-red-400 text-sm';
|
||||
consecutiveRpcFailures += 1;
|
||||
const syncStatusText = document.getElementById('syncStatusText');
|
||||
if (syncStatusText) {
|
||||
const hasRecentData = lastSuccessfulUpdateAt > 0 && Date.now() - lastSuccessfulUpdateAt < 120000;
|
||||
syncStatusText.textContent = hasRecentData
|
||||
? 'Bitcoin status bridge is reconnecting... keeping last known values'
|
||||
: 'Connecting to Bitcoin status bridge...';
|
||||
syncStatusText.className = 'text-yellow-300 text-sm font-medium';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
@media (min-width: 768px) {
|
||||
.md-flex-row { flex-direction: row; }
|
||||
.md-grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
|
||||
.md-grid-cols-5 { grid-template-columns: repeat(5, 1fr); }
|
||||
}
|
||||
|
||||
/* Connection details */
|
||||
@@ -147,13 +148,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md-grid-cols-4 gap-3">
|
||||
<div class="grid grid-cols-2 md-grid-cols-5 gap-3">
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white-60 mb-1">Indexed Height</p>
|
||||
<p class="text-xs text-white-60 mb-1">Electrum Indexed</p>
|
||||
<p class="text-lg font-semibold text-white" id="indexedHeight">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white-60 mb-1">Network Height</p>
|
||||
<p class="text-xs text-white-60 mb-1">Bitcoin Node</p>
|
||||
<p class="text-lg font-semibold text-white" id="bitcoinHeight">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<p class="text-xs text-white-60 mb-1">Known Headers</p>
|
||||
<p class="text-lg font-semibold text-white" id="networkHeight">-</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
@@ -370,15 +375,36 @@
|
||||
}
|
||||
|
||||
var indexedH = data.indexed_height || 0;
|
||||
var networkH = data.network_height || 0;
|
||||
var bitcoinH = data.bitcoin_height || 0;
|
||||
var reportedNetworkH = data.network_height || 0;
|
||||
var knownHeaderH = Math.max(reportedNetworkH, indexedH, bitcoinH);
|
||||
var targetH = bitcoinH > 0 ? bitcoinH : knownHeaderH;
|
||||
var pct = data.progress_pct || 0;
|
||||
var hasIndexedHeight = indexedH > 0 || data.stale;
|
||||
var indexedLabel = hasIndexedHeight
|
||||
? indexedH.toLocaleString()
|
||||
: (data.status === 'indexing' ? 'Pending' : '-');
|
||||
var currentBlockLabel;
|
||||
if (hasIndexedHeight && bitcoinH > 0 && indexedH > bitcoinH) {
|
||||
currentBlockLabel = 'Bitcoin node ' + bitcoinH.toLocaleString()
|
||||
+ (knownHeaderH > 0 ? ' of known headers ' + knownHeaderH.toLocaleString() : '')
|
||||
+ '; Electrum index ' + indexedH.toLocaleString();
|
||||
} else if (hasIndexedHeight) {
|
||||
currentBlockLabel = 'Indexed ' + indexedH.toLocaleString() + ' of '
|
||||
+ (targetH > 0 ? targetH.toLocaleString() : 'Bitcoin node height');
|
||||
} else {
|
||||
currentBlockLabel = data.index_size
|
||||
? 'Index building from disk (' + data.index_size + ')'
|
||||
: 'Waiting for Electrum index height';
|
||||
}
|
||||
|
||||
document.getElementById('indexedHeight').textContent = indexedH > 0 ? indexedH.toLocaleString() : (data.status === 'indexing' ? 'Building...' : '-');
|
||||
document.getElementById('networkHeight').textContent = networkH > 0 ? networkH.toLocaleString() : '-';
|
||||
document.getElementById('indexedHeight').textContent = indexedLabel;
|
||||
document.getElementById('bitcoinHeight').textContent = bitcoinH > 0 ? bitcoinH.toLocaleString() : 'Checking...';
|
||||
document.getElementById('networkHeight').textContent = knownHeaderH > 0 ? knownHeaderH.toLocaleString() : 'Checking...';
|
||||
document.getElementById('indexSize').textContent = data.index_size || '-';
|
||||
document.getElementById('progressPct').textContent = pct > 0 ? pct.toFixed(1) + '%' : '-';
|
||||
document.getElementById('currentBlock').textContent = indexedH > 0 ? 'Block ' + indexedH.toLocaleString() : (data.index_size ? 'Index: ' + data.index_size : 'Block 0');
|
||||
document.getElementById('syncPercentage').textContent = pct > 0 ? pct.toFixed(1) + '%' : '0%';
|
||||
document.getElementById('progressPct').textContent = (knownHeaderH > 0 || pct > 0) ? pct.toFixed(1) + '%' : '-';
|
||||
document.getElementById('currentBlock').textContent = currentBlockLabel;
|
||||
document.getElementById('syncPercentage').textContent = (knownHeaderH > 0 || pct > 0) ? pct.toFixed(1) + '%' : '0%';
|
||||
document.getElementById('syncProgressBar').style.width = Math.max(pct, 0.5) + '%';
|
||||
|
||||
var statusTextEl = document.getElementById('syncStatusText');
|
||||
@@ -389,14 +415,16 @@
|
||||
statusTextEl.textContent = data.error || 'Starting up...';
|
||||
statusTextEl.style.color = '#fbbf24';
|
||||
statusDot.className = 'status-dot bg-yellow animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Starting';
|
||||
document.getElementById('statusText').textContent = data.status === 'waiting' ? 'Waiting' : 'Starting';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'indexing') {
|
||||
statusTextEl.textContent = data.error || 'Building index...';
|
||||
statusTextEl.textContent = data.stale
|
||||
? (data.error || 'ElectrumX is reconnecting; showing last known indexed height.')
|
||||
: (data.error || 'Building index. Indexed height will appear when Electrum RPC is ready.');
|
||||
statusTextEl.style.color = '#fbbf24';
|
||||
statusDot.className = 'status-dot bg-amber animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Indexing';
|
||||
document.getElementById('statusText').textContent = data.stale ? 'Reconnecting' : 'Indexing';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'error') {
|
||||
@@ -414,8 +442,10 @@
|
||||
syncIcon.style.color = '#4ade80';
|
||||
document.getElementById('connSubtitle').textContent = 'Use the following details to connect your wallet or application to ElectrumX.';
|
||||
} else {
|
||||
var remaining = networkH - indexedH;
|
||||
statusTextEl.textContent = 'Syncing... ' + remaining.toLocaleString() + ' blocks remaining';
|
||||
var remaining = Math.max(targetH - indexedH, 0);
|
||||
statusTextEl.textContent = data.error || (targetH > 0
|
||||
? 'Syncing... ' + remaining.toLocaleString() + ' blocks remaining'
|
||||
: 'Waiting for Bitcoin network height...');
|
||||
statusTextEl.style.color = '#fb923c';
|
||||
statusDot.className = 'status-dot bg-yellow';
|
||||
document.getElementById('statusText').textContent = 'Syncing';
|
||||
|
||||
Reference in New Issue
Block a user