Enhanced state handling

This commit is contained in:
2025-11-09 12:36:57 -05:00
parent 7424a2654f
commit e637ff5809

View File

@ -405,6 +405,10 @@ $stratum = 'stratum+tcp://' . $host . ':3333';
const syncTracker = { samples: [] }; const syncTracker = { samples: [] };
const WINDOW_SECONDS = 900; // 15 minutes 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) { function formatEta(seconds) {
if (seconds == null || !isFinite(seconds)) return 'estimating…'; if (seconds == null || !isFinite(seconds)) return 'estimating…';
if (seconds < 60) return seconds.toFixed(0) + 's'; 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 res = await fetch('/status.php', {cache: 'no-store'});
const data = await res.json(); const data = await res.json();
const box = document.getElementById('node-status'); const box = document.getElementById('node-status');
const rawIbd = data.initialblockdownload;
const hasRealIbd = (typeof rawIbd === 'boolean');
if (data.error) { if (data.error) {
box.innerHTML = '<h2>Status</h2><p class="status-err">' + data.error + '</p>' + renderServices(data.services); box.innerHTML = '<h2>Status</h2><p class="status-err">' + data.error + '</p>' + renderServices(data.services);
return; return;
} }
const ibd = !!data.initialblockdownload; const ibd = hasRealIbd ? rawIbd : true; // default to "syncing/problem" if unknown
const blocks = data.blocks ?? ''; const blocks = data.blocks ?? '';
const pruned = data.pruned ? '(pruned)' : ''; const pruned = data.pruned ? '(pruned)' : '';
let progress = 0; let progress = 0;
@ -474,15 +480,32 @@ async function loadStatus() {
} }
// Update favicon dynamically // 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 // update window + estimate
updateSamples(progress); updateSamples(progress);
const etaSeconds = ibd ? estimateEta(progress) : null; const etaSeconds = ibd ? estimateEta(progress) : null;
let html = '<h2>Status</h2>'; let html = '<h2>Status</h2>';
if (!hasRealIbd) {
if (ibd) { if (data.services && data.services.bitcoind === 'active') {
html += '<p class="status-err">❌ Cannot read Bitcoin Core status. If this persists, try refreshing your browser.</p>';
} else {
html += '<p class="status-err">❌ Bitcoin service is not running.</p>';
}
} else if (!bitcoindActive) {
// technically covered above, but keep explicit
html += '<p class="status-err">❌ Bitcoin service is not running.</p>';
} else if (ibd && progress < 0.1) {
// active but no real progress yet
html += '<p class="status-warn">⚠ Bitcoin Core is starting up and loading blockchain data. Please wait...</p>';
} else if (ibd) {
html += '<p class="status-warn">⚠ Bitcoin Core is syncing. Your miners will not be able to submit shares or receive work until the node is fully synced.</p>'; html += '<p class="status-warn">⚠ Bitcoin Core is syncing. Your miners will not be able to submit shares or receive work until the node is fully synced.</p>';
} else { } else {
html += '<p class="status-ok">✔ Bitcoin Core is in sync.</p>'; html += '<p class="status-ok">✔ Bitcoin Core is in sync.</p>';
@ -519,27 +542,44 @@ async function loadStatus() {
} catch (e) { } catch (e) {
const box = document.getElementById('node-status'); const box = document.getElementById('node-status');
box.innerHTML = '<h2>Status</h2><p class="status-err">Failed to fetch status.</p>'; box.innerHTML = '<h2>Status</h2><p class="status-err">Failed to fetch status.</p>';
setFaviconAndTitle(false, 0, true);
} }
} }
// Inline SVG favicons // 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_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_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'); const link = document.getElementById('app-favicon');
if (!link) return; if (!link) return;
if (isError) {
link.href = FAV_ERR;
document.title = "Disconnected • Bitcoin Solo Miner";
return;
}
if (isSyncing) { if (isSyncing) {
link.href = FAV_SYNC; link.href = FAV_SYNC;
document.title = "Syncing… " + progress.toFixed(1) + "% | Bitcoin Node"; document.title = "Syncing… " + progress.toFixed(1) + "% | Bitcoin Solo Miner";
} else { } else {
link.href = FAV_READY; link.href = FAV_READY;
document.title = "Ready • Bitcoin Node"; document.title = "Ready • Bitcoin Solo Miner";
} }
} }
loadStatus(); loadStatus();
setInterval(loadStatus, 5000); 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 = '<h2>Status</h2><p class="status-err">Status information unavailable (stale data &gt; 30s). If this persists, try refreshing your browser.</p>';
}
}
}, 3000);
</script> </script>
</body> </body>