diff --git a/installer b/installer index 5618b59..4f6477d 100755 --- a/installer +++ b/installer @@ -405,6 +405,10 @@ $stratum = 'stratum+tcp://' . $host . ':3333'; const syncTracker = { samples: [] }; const WINDOW_SECONDS = 900; // 15 minutes +// 30-second heartbeat +let lastStatusOk = Date.now(); // timestamp of last good fetch +const STALE_MS = 30000; // 30 seconds + function formatEta(seconds) { if (seconds == null || !isFinite(seconds)) return 'estimating…'; if (seconds < 60) return seconds.toFixed(0) + 's'; @@ -457,13 +461,15 @@ async function loadStatus() { const res = await fetch('/status.php', {cache: 'no-store'}); const data = await res.json(); const box = document.getElementById('node-status'); + const rawIbd = data.initialblockdownload; + const hasRealIbd = (typeof rawIbd === 'boolean'); if (data.error) { box.innerHTML = '

Status

' + data.error + '

' + renderServices(data.services); return; } - const ibd = !!data.initialblockdownload; + const ibd = hasRealIbd ? rawIbd : true; // default to "syncing/problem" if unknown const blocks = data.blocks ?? '–'; const pruned = data.pruned ? '(pruned)' : ''; let progress = 0; @@ -474,15 +480,32 @@ async function loadStatus() { } // Update favicon dynamically - setFaviconAndTitle(ibd, progress); + lastStatusOk = Date.now(); + const bitcoindActive = data.services && data.services.bitcoind === 'active'; + if (!bitcoindActive) { + setFaviconAndTitle(false, 0, true); // show Disconnected + } else { + setFaviconAndTitle(ibd, progress, false); + } // update window + estimate updateSamples(progress); const etaSeconds = ibd ? estimateEta(progress) : null; let html = '

Status

'; - - if (ibd) { + if (!hasRealIbd) { + if (data.services && data.services.bitcoind === 'active') { + html += '

❌ Cannot read Bitcoin Core status. If this persists, try refreshing your browser.

'; + } else { + html += '

❌ Bitcoin service is not running.

'; + } + } else if (!bitcoindActive) { + // technically covered above, but keep explicit + html += '

❌ Bitcoin service is not running.

'; + } else if (ibd && progress < 0.1) { + // active but no real progress yet + html += '

⚠ Bitcoin Core is starting up and loading blockchain data. Please wait...

'; + } else if (ibd) { html += '

⚠ Bitcoin Core is syncing. Your miners will not be able to submit shares or receive work until the node is fully synced.

'; } else { html += '

✔ Bitcoin Core is in sync.

'; @@ -519,27 +542,44 @@ async function loadStatus() { } catch (e) { const box = document.getElementById('node-status'); box.innerHTML = '

Status

Failed to fetch status.

'; + setFaviconAndTitle(false, 0, true); } } // Inline SVG favicons const FAV_SYNC = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Crect width='64' height='64' fill='%23222'/%3E%3Ctext x='50%25' y='55%25' text-anchor='middle' font-size='32' fill='%23ffb347'%3E%E2%9A%A0%3C/text%3E%3C/svg%3E"; const FAV_READY = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Crect width='64' height='64' fill='%23161717'/%3E%3Ctext x='50%25' y='55%25' text-anchor='middle' font-size='34' fill='%238f8'%3E%E2%9C%94%3C/text%3E%3C/svg%3E"; +const FAV_ERR = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Crect width='64' height='64' fill='%23222222'/%3E%3Ctext x='50%25' y='55%25' text-anchor='middle' font-size='34' fill='%23ff6b6b'%3E!%3C/text%3E%3C/svg%3E"; -function setFaviconAndTitle(isSyncing, progress) { +function setFaviconAndTitle(isSyncing, progress, isError = false) { const link = document.getElementById('app-favicon'); if (!link) return; + if (isError) { + link.href = FAV_ERR; + document.title = "Disconnected • Bitcoin Solo Miner"; + return; + } if (isSyncing) { link.href = FAV_SYNC; - document.title = "Syncing… " + progress.toFixed(1) + "% | Bitcoin Node"; + document.title = "Syncing… " + progress.toFixed(1) + "% | Bitcoin Solo Miner"; } else { link.href = FAV_READY; - document.title = "Ready • Bitcoin Node"; + document.title = "Ready • Bitcoin Solo Miner"; } } loadStatus(); setInterval(loadStatus, 5000); +setInterval(() => { + if (Date.now() - lastStatusOk > STALE_MS) { + // no successful poll in 30s -> error state + setFaviconAndTitle(false, 0, true); + const box = document.getElementById('node-status'); + if (box && !box.innerHTML.includes('Failed to fetch')) { + box.innerHTML = '

Status

Status information unavailable (stale data > 30s). If this persists, try refreshing your browser.

'; + } + } +}, 3000);