diff --git a/static/css/retro-refresh.css b/static/css/retro-refresh.css deleted file mode 100644 index 9e987ff..0000000 --- a/static/css/retro-refresh.css +++ /dev/null @@ -1,369 +0,0 @@ -/* Retro Floating Refresh Bar Styles */ -:root { - --terminal-bg: #000000; - --terminal-border: #f7931a; - --terminal-text: #f7931a; - --terminal-glow: rgba(247, 147, 26, 0.7); - --terminal-width: 300px; -} - -/* Adjust width for desktop */ -@media (min-width: 768px) { - :root { - --terminal-width: 340px; - } -} - -/* Remove the existing refresh timer container styles */ -#refreshUptime { - visibility: hidden !important; - height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; -} - -/* Add padding to the bottom of the page to prevent floating bar from covering content -body { - padding-bottom: 100px !important; -} -*/ -/* Floating Retro Terminal Container */ -#retro-terminal-bar { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - width: var(--terminal-width); - background-color: var(--terminal-bg); - border: 2px solid var(--terminal-border); - /* box-shadow: 0 0 15px var(--terminal-glow); */ - z-index: 1000; - font-family: 'VT323', monospace; - overflow: hidden; - padding: 5px; -} - -/* Desktop positioning (bottom right) */ -@media (min-width: 768px) { - #retro-terminal-bar { - left: auto; - right: 20px; - transform: none; - } -} - -/* Terminal header with control buttons */ -/* Update the terminal title to match card headers */ -.terminal-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 5px; - border-bottom: 1px solid var(--terminal-border); - padding-bottom: 3px; - background-color: #000; /* Match card header background */ -} - -.terminal-title { - color: var(--primary-color); - font-weight: bold; - font-size: 1.1rem; /* Match card header font size */ - border-bottom: none; - text-shadow: 0 0 5px var(--primary-color); - animation: flicker 4s infinite; /* Add flicker animation from card headers */ - font-family: var(--header-font); /* Use the same font variable */ - padding: 0.3rem 0; /* Match card header padding */ - letter-spacing: 1px; -} - -/* Make sure we're using the flicker animation defined in the main CSS */ -@keyframes flicker { - 0% { opacity: 0.97; } - 5% { opacity: 0.95; } - 10% { opacity: 0.97; } - 15% { opacity: 0.94; } - 20% { opacity: 0.98; } - 50% { opacity: 0.95; } - 80% { opacity: 0.96; } - 90% { opacity: 0.94; } - 100% { opacity: 0.98; } -} - -.terminal-controls { - display: flex; - gap: 5px; -} - -.terminal-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: #555; - transition: background-color 0.3s; -} - -.terminal-dot:hover { - background-color: #999; - cursor: pointer; -} - -.terminal-dot.minimize:hover { - background-color: #ffcc00; -} - -.terminal-dot.close:hover { - background-color: #ff3b30; -} - -/* Terminal content area */ -.terminal-content { - position: relative; - color: #ffffff; - padding: 5px 0; -} - -/* Scanline effect for authentic CRT look */ -.terminal-content::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: repeating-linear-gradient( - 0deg, - rgba(0, 0, 0, 0.15), - rgba(0, 0, 0, 0.15) 1px, - transparent 1px, - transparent 2px - ); - pointer-events: none; - z-index: 1; - animation: flicker 0.15s infinite; -} - -@keyframes flicker { - 0% { opacity: 1.0; } - 50% { opacity: 0.98; } - 100% { opacity: 1.0; } -} - -/* Enhanced Progress Bar with tick marks */ -#retro-terminal-bar .bitcoin-progress-container { - width: 100%; - height: 20px; - background-color: #111; - border: 1px solid var(--terminal-border); - margin-bottom: 10px; - position: relative; - overflow: hidden; - box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8); -} - -/* Tick marks on progress bar */ -.progress-ticks { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - justify-content: space-between; - padding: 0 5px; - color: rgba(255, 255, 255, 0.6); - font-size: 10px; - pointer-events: none; - z-index: 3; -} - -.progress-ticks span { - display: flex; - align-items: flex-end; - height: 100%; - padding-bottom: 2px; -} - -.tick-mark { - position: absolute; - top: 0; - width: 1px; - height: 5px; - background-color: rgba(255, 255, 255, 0.4); -} - -.tick-mark.major { - height: 8px; - background-color: rgba(255, 255, 255, 0.6); -} - -/* The actual progress bar */ -#retro-terminal-bar #bitcoin-progress-inner { - height: 100%; - width: 0; - background: linear-gradient(90deg, #f7931a, #ffa500); - position: relative; - transition: width 1s linear; -} - -/* Position the original inner container correctly */ -#retro-terminal-bar #refreshContainer { - display: block; - width: 100%; -} - -/* Blinking scan line animation */ -.scan-line { - position: absolute; - height: 2px; - width: 100%; - background-color: rgba(255, 255, 255, 0.7); - animation: scan 3s linear infinite; - box-shadow: 0 0 8px 1px rgba(255, 255, 255, 0.5); - z-index: 2; -} - -@keyframes scan { - 0% { top: -2px; } - 100% { top: 22px; } -} - -/* Text styling */ -#retro-terminal-bar #progress-text { - font-size: 16px; - color: var(--terminal-text); - text-shadow: 0 0 5px var(--terminal-text); - margin-top: 5px; - text-align: center; - position: relative; - z-index: 2; -} - -#retro-terminal-bar #uptimeTimer { - font-size: 16px; - color: var(--terminal-text); - text-shadow: 0 0 5px var(--terminal-text); - text-align: center; - position: relative; - z-index: 2; - border-top: 1px solid rgba(247, 147, 26, 0.3); - padding-top: 5px; - margin-top: 5px; -} - -/* Terminal cursor */ -#retro-terminal-bar #terminal-cursor { - display: inline-block; - width: 8px; - height: 14px; - background-color: var(--terminal-text); - margin-left: 2px; - animation: blink 1s step-end infinite; - box-shadow: 0 0 8px var(--terminal-text); -} - -/* Glowing effect during the last few seconds */ -#retro-terminal-bar #bitcoin-progress-inner.glow-effect { - box-shadow: 0 0 15px #f7931a, 0 0 25px #f7931a; -} - -#retro-terminal-bar .waiting-for-update { - animation: waitingPulse 2s infinite !important; -} - -@keyframes waitingPulse { - 0%, 100% { box-shadow: 0 0 10px #f7931a, 0 0 15px #f7931a; opacity: 0.8; } - 50% { box-shadow: 0 0 20px #f7931a, 0 0 35px #f7931a; opacity: 1; } -} - -/* Status indicators */ -.status-indicators { - display: flex; - justify-content: space-between; - margin-bottom: 5px; - font-size: 12px; - color: #aaa; -} - -.status-indicator { - display: flex; - align-items: center; -} - -.status-dot { - width: 6px; - height: 6px; - border-radius: 50%; - margin-right: 4px; -} - -.status-dot.connected { - background-color: #32CD32; - box-shadow: 0 0 5px #32CD32; - animation: pulse 2s infinite; -} - -@keyframes pulse { - 0% { opacity: 0.8; } - 50% { opacity: 1; } - 100% { opacity: 0.8; } -} - -/* Collapse/expand functionality */ -#retro-terminal-bar.collapsed .terminal-content { - display: none; -} - -#retro-terminal-bar.collapsed { - width: 180px; -} - -/* On desktop, move the collapsed bar to bottom right */ -@media (min-width: 768px) { - #retro-terminal-bar.collapsed { - right: 20px; - transform: none; - } -} - -/* Show button */ -#show-terminal-button { - position: fixed; - bottom: 10px; - right: 10px; - z-index: 1000; - background-color: #f7931a; - color: #000; - border: none; - padding: 8px 12px; - cursor: pointer; - font-family: 'VT323', monospace; - font-size: 14px; - box-shadow: 0 0 10px rgba(247, 147, 26, 0.5); -} - -#show-terminal-button:hover { - background-color: #ffaa33; -} - -/* Mobile responsiveness */ -@media (max-width: 576px) { - #retro-terminal-bar { - width: 280px; - bottom: 10px; - } - - .terminal-title { - font-size: 14px; - } - - .terminal-dot { - width: 6px; - height: 6px; - } - - #show-terminal-button { - padding: 6px 10px; - font-size: 12px; - } -} \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index 3ddad24..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,801 +0,0 @@ -"use strict"; - -// Global variables -let previousMetrics = {}; -let persistentArrows = {}; -let serverTimeOffset = 0; -let serverStartTime = null; -let latestMetrics = null; -let initialLoad = true; -let trendData = []; -let trendLabels = []; -let trendChart = null; -let connectionRetryCount = 0; -let maxRetryCount = 10; -let reconnectionDelay = 1000; // Start with 1 second -let pingInterval = null; -let lastPingTime = Date.now(); -let connectionLostTimeout = null; - -// Bitcoin-themed progress bar functionality -let progressInterval; -let currentProgress = 0; -let lastUpdateTime = Date.now(); -let expectedUpdateInterval = 60000; // Expected server update interval (60 seconds) -const PROGRESS_MAX = 60; // 60 seconds for a complete cycle - -// Initialize the progress bar and start the animation -function initProgressBar() { - // Clear any existing interval - if (progressInterval) { - clearInterval(progressInterval); - } - - // Set last update time to now - lastUpdateTime = Date.now(); - - // Reset progress with initial offset - currentProgress = 1; // Start at 1 instead of 0 for offset - updateProgressBar(currentProgress); - - // Start the interval - progressInterval = setInterval(function() { - // Calculate elapsed time since last update - const elapsedTime = Date.now() - lastUpdateTime; - - // Calculate progress percentage based on elapsed time with +1 second offset - const secondsElapsed = Math.floor(elapsedTime / 1000) + 1; // Add 1 second offset - - // If we've gone past the expected update time - if (secondsElapsed >= PROGRESS_MAX) { - // Keep the progress bar full but show waiting state - currentProgress = PROGRESS_MAX; - } else { - // Normal progress with offset - currentProgress = secondsElapsed; - } - - updateProgressBar(currentProgress); - }, 1000); -} - -// Update the progress bar display -function updateProgressBar(seconds) { - const progressPercent = (seconds / PROGRESS_MAX) * 100; - $("#bitcoin-progress-inner").css("width", progressPercent + "%"); - - // Add glowing effect when close to completion - if (progressPercent > 80) { - $("#bitcoin-progress-inner").addClass("glow-effect"); - } else { - $("#bitcoin-progress-inner").removeClass("glow-effect"); - } - - // Update remaining seconds text - more precise calculation - let remainingSeconds = PROGRESS_MAX - seconds; - - // When we're past the expected time, show "Waiting for update..." - if (remainingSeconds <= 0) { - $("#progress-text").text("Waiting for update..."); - $("#bitcoin-progress-inner").addClass("waiting-for-update"); - } else { - $("#progress-text").text(remainingSeconds + "s to next update"); - $("#bitcoin-progress-inner").removeClass("waiting-for-update"); - } -} - -// Register Chart.js annotation plugin if available -if (window['chartjs-plugin-annotation']) { - Chart.register(window['chartjs-plugin-annotation']); -} - -// SSE Connection with Error Handling and Reconnection Logic -function setupEventSource() { - console.log("Setting up EventSource connection..."); - - if (window.eventSource) { - console.log("Closing existing EventSource connection"); - window.eventSource.close(); - window.eventSource = null; - } - - // Always use absolute URL with origin to ensure it works from any path - const baseUrl = window.location.origin; - const streamUrl = `${baseUrl}/stream`; - - console.log("Current path:", window.location.pathname); - console.log("Using stream URL:", streamUrl); - - // Clear any existing ping interval - if (pingInterval) { - clearInterval(pingInterval); - pingInterval = null; - } - - // Clear any connection lost timeout - if (connectionLostTimeout) { - clearTimeout(connectionLostTimeout); - connectionLostTimeout = null; - } - - try { - const eventSource = new EventSource(streamUrl); - - eventSource.onopen = function(e) { - console.log("EventSource connection opened successfully"); - connectionRetryCount = 0; // Reset retry count on successful connection - reconnectionDelay = 1000; // Reset reconnection delay - hideConnectionIssue(); - - // Start ping interval to detect dead connections - lastPingTime = Date.now(); - pingInterval = setInterval(function() { - const now = Date.now(); - if (now - lastPingTime > 60000) { // 60 seconds without data - console.warn("No data received for 60 seconds, reconnecting..."); - showConnectionIssue("Connection stalled"); - eventSource.close(); - setupEventSource(); - } - }, 10000); // Check every 10 seconds - }; - - eventSource.onmessage = function(e) { - console.log("SSE message received"); - lastPingTime = Date.now(); // Update ping time on any message - - try { - const data = JSON.parse(e.data); - - // Handle different message types - if (data.type === "ping") { - console.log("Ping received:", data); - // Update connection count if available - if (data.connections !== undefined) { - console.log(`Active connections: ${data.connections}`); - } - return; - } - - if (data.type === "timeout_warning") { - console.log(`Connection timeout warning: ${data.remaining}s remaining`); - // If less than 30 seconds remaining, prepare for reconnection - if (data.remaining < 30) { - console.log("Preparing for reconnection due to upcoming timeout"); - } - return; - } - - if (data.type === "timeout") { - console.log("Connection timeout from server:", data.message); - eventSource.close(); - // If reconnect flag is true, reconnect immediately - if (data.reconnect) { - console.log("Server requested reconnection"); - setTimeout(setupEventSource, 500); - } else { - setupEventSource(); - } - return; - } - - if (data.error) { - console.error("Server reported error:", data.error); - showConnectionIssue(data.error); - - // If retry time provided, use it, otherwise use default - const retryTime = data.retry || 5000; - setTimeout(function() { - manualRefresh(); - }, retryTime); - return; - } - - // Process regular data update - latestMetrics = data; - updateUI(); - hideConnectionIssue(); - - // Also explicitly trigger a data refresh event - $(document).trigger('dataRefreshed'); - } catch (err) { - console.error("Error processing SSE data:", err); - showConnectionIssue("Data processing error"); - } - }; - - eventSource.onerror = function(e) { - console.error("SSE connection error", e); - showConnectionIssue("Connection lost"); - - eventSource.close(); - - // Implement exponential backoff for reconnection - connectionRetryCount++; - - if (connectionRetryCount > maxRetryCount) { - console.log("Maximum retry attempts reached, switching to polling mode"); - if (pingInterval) { - clearInterval(pingInterval); - pingInterval = null; - } - - // Switch to regular polling - showConnectionIssue("Using polling mode"); - setInterval(manualRefresh, 30000); // Poll every 30 seconds - manualRefresh(); // Do an immediate refresh - return; - } - - // Exponential backoff with jitter - const jitter = Math.random() * 0.3 + 0.85; // 0.85-1.15 - reconnectionDelay = Math.min(30000, reconnectionDelay * 1.5 * jitter); - - console.log(`Reconnecting in ${(reconnectionDelay/1000).toFixed(1)} seconds... (attempt ${connectionRetryCount}/${maxRetryCount})`); - setTimeout(setupEventSource, reconnectionDelay); - }; - - window.eventSource = eventSource; - console.log("EventSource setup complete"); - - // Set a timeout to detect if connection is established - connectionLostTimeout = setTimeout(function() { - if (eventSource.readyState !== 1) { // 1 = OPEN - console.warn("Connection not established within timeout, switching to manual refresh"); - showConnectionIssue("Connection timeout"); - eventSource.close(); - manualRefresh(); - } - }, 10000); // 10 seconds timeout to establish connection - - } catch (error) { - console.error("Failed to create EventSource:", error); - showConnectionIssue("Connection setup failed"); - setTimeout(setupEventSource, 5000); // Try again in 5 seconds - } - - // Add page visibility change listener - // This helps reconnect when user returns to the tab after it's been inactive - document.removeEventListener("visibilitychange", handleVisibilityChange); - document.addEventListener("visibilitychange", handleVisibilityChange); -} - -// Handle page visibility changes -function handleVisibilityChange() { - if (!document.hidden) { - console.log("Page became visible, checking connection"); - if (!window.eventSource || window.eventSource.readyState !== 1) { - console.log("Connection not active, reestablishing"); - setupEventSource(); - } - manualRefresh(); // Always refresh data when page becomes visible - } -} - -// Helper function to show connection issues to the user -function showConnectionIssue(message) { - let $connectionStatus = $("#connectionStatus"); - if (!$connectionStatus.length) { - $("body").append('
'); - $connectionStatus = $("#connectionStatus"); - } - $connectionStatus.html(` ${message}`).show(); - - // Show manual refresh button when there are connection issues - $("#refreshButton").show(); -} - -// Helper function to hide connection issue message -function hideConnectionIssue() { - $("#connectionStatus").hide(); - $("#refreshButton").hide(); -} - -// Improved manual refresh function as fallback -function manualRefresh() { - console.log("Manually refreshing data..."); - - $.ajax({ - url: '/api/metrics', - method: 'GET', - dataType: 'json', - timeout: 15000, // 15 second timeout - success: function(data) { - console.log("Manual refresh successful"); - lastPingTime = Date.now(); // Update ping time - latestMetrics = data; - updateUI(); - hideConnectionIssue(); - - // Explicitly trigger data refresh event - $(document).trigger('dataRefreshed'); - }, - error: function(xhr, status, error) { - console.error("Manual refresh failed:", error); - showConnectionIssue("Manual refresh failed"); - - // Try again with exponential backoff - const retryDelay = Math.min(30000, 1000 * Math.pow(1.5, Math.min(5, connectionRetryCount))); - connectionRetryCount++; - setTimeout(manualRefresh, retryDelay); - } - }); -} - -// Initialize Chart.js with Error Handling -function initializeChart() { - try { - const ctx = document.getElementById('trendGraph').getContext('2d'); - if (!ctx) { - console.error("Could not find trend graph canvas"); - return null; - } - - if (!window.Chart) { - console.error("Chart.js not loaded"); - return null; - } - - // Check if Chart.js plugin is available - const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined; - - return new Chart(ctx, { - type: 'line', - data: { - labels: [], - datasets: [{ - label: '60s Hashrate Trend (TH/s)', - data: [], - borderColor: '#f7931a', - backgroundColor: 'rgba(247,147,26,0.1)', - fill: true, - tension: 0.2, - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - animation: { - duration: 0 // Disable animations for better performance - }, - scales: { - x: { display: false }, - y: { - ticks: { color: 'white' }, - grid: { color: '#333' } - } - }, - plugins: { - legend: { display: false }, - annotation: hasAnnotationPlugin ? { - annotations: { - averageLine: { - type: 'line', - yMin: 0, - yMax: 0, - borderColor: '#f7931a', - borderWidth: 2, - borderDash: [6, 6], - label: { - enabled: true, - content: '24hr Avg: 0 TH/s', - backgroundColor: 'rgba(0,0,0,0.7)', - color: '#f7931a', - font: { weight: 'bold', size: 13 }, - position: 'start' - } - } - } - } : {} - } - } - }); - } catch (error) { - console.error("Error initializing chart:", error); - return null; - } -} - -// Helper function to safely format numbers with commas -function numberWithCommas(x) { - if (x == null) return "N/A"; - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - -// Server time update via polling -function updateServerTime() { - $.ajax({ - url: "/api/time", - method: "GET", - timeout: 5000, - success: function(data) { - serverTimeOffset = new Date(data.server_timestamp).getTime() - Date.now(); - serverStartTime = new Date(data.server_start_time).getTime(); - }, - error: function(jqXHR, textStatus, errorThrown) { - console.error("Error fetching server time:", textStatus, errorThrown); - } - }); -} - -// Update uptime display -function updateUptime() { - if (serverStartTime) { - const currentServerTime = Date.now() + serverTimeOffset; - const diff = currentServerTime - serverStartTime; - const hours = Math.floor(diff / (1000 * 60 * 60)); - const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((diff % (1000 * 60)) / 1000); - $("#uptimeTimer").html("Uptime: " + hours + "h " + minutes + "m " + seconds + "s"); - } -} - -// Update UI indicators (arrows) -function updateIndicators(newMetrics) { - const keys = [ - "pool_total_hashrate", "hashrate_24hr", "hashrate_3hr", "hashrate_10min", - "hashrate_60sec", "block_number", "btc_price", "network_hashrate", - "difficulty", "daily_revenue", "daily_power_cost", "daily_profit_usd", - "monthly_profit_usd", "daily_mined_sats", "monthly_mined_sats", "unpaid_earnings", - "estimated_earnings_per_day_sats", "estimated_earnings_next_block_sats", "estimated_rewards_in_window_sats", - "workers_hashing" - ]; - - keys.forEach(function(key) { - const newVal = parseFloat(newMetrics[key]); - if (isNaN(newVal)) return; - - const oldVal = parseFloat(previousMetrics[key]); - if (!isNaN(oldVal)) { - if (newVal > oldVal) { - persistentArrows[key] = ""; - } else if (newVal < oldVal) { - persistentArrows[key] = ""; - } - } else { - if (newMetrics.arrow_history && newMetrics.arrow_history[key] && newMetrics.arrow_history[key].length > 0) { - const historyArr = newMetrics.arrow_history[key]; - for (let i = historyArr.length - 1; i >= 0; i--) { - if (historyArr[i].arrow !== "") { - if (historyArr[i].arrow === "↑") { - persistentArrows[key] = ""; - } else if (historyArr[i].arrow === "↓") { - persistentArrows[key] = ""; - } - break; - } - } - } - } - - const indicator = document.getElementById("indicator_" + key); - if (indicator) { - indicator.innerHTML = persistentArrows[key] || ""; - } - }); - - previousMetrics = { ...newMetrics }; -} - -// Helper function to safely update element text content -function updateElementText(elementId, text) { - const element = document.getElementById(elementId); - if (element) { - element.textContent = text; - } -} - -// Helper function to safely update element HTML content -function updateElementHTML(elementId, html) { - const element = document.getElementById(elementId); - if (element) { - element.innerHTML = html; - } -} - -// Update workers_hashing value from metrics, but don't try to access worker details -function updateWorkersCount() { - if (latestMetrics && latestMetrics.workers_hashing !== undefined) { - $("#workers_hashing").text(latestMetrics.workers_hashing || 0); - - // Update miner status with online/offline indicator based on worker count - if (latestMetrics.workers_hashing > 0) { - updateElementHTML("miner_status", "ONLINE "); - } else { - updateElementHTML("miner_status", "OFFLINE "); - } - } -} - -// Check for block updates and show congratulatory messages -function checkForBlockUpdates(data) { - if (previousMetrics.last_block_height !== undefined && - data.last_block_height !== previousMetrics.last_block_height) { - showCongrats("Congrats! New Block Found: " + data.last_block_height); - } - - if (previousMetrics.blocks_found !== undefined && - data.blocks_found !== previousMetrics.blocks_found) { - showCongrats("Congrats! Blocks Found updated: " + data.blocks_found); - } -} - -// Helper function to show congratulatory messages -function showCongrats(message) { - const $congrats = $("#congratsMessage"); - $congrats.text(message).fadeIn(500, function() { - setTimeout(function() { - $congrats.fadeOut(500); - }, 3000); - }); -} - -// Main UI update function -function updateUI() { - if (!latestMetrics) { - console.warn("No metrics data available"); - return; - } - - try { - const data = latestMetrics; - - // If there's execution time data, log it - if (data.execution_time) { - console.log(`Server metrics fetch took ${data.execution_time.toFixed(2)}s`); - } - - // Cache jQuery selectors for performance and use safe update methods - updateElementText("pool_total_hashrate", - (data.pool_total_hashrate != null ? data.pool_total_hashrate : "N/A") + " " + - (data.pool_total_hashrate_unit ? data.pool_total_hashrate_unit.slice(0,-2).toUpperCase() + data.pool_total_hashrate_unit.slice(-2) : "") - ); - - updateElementText("hashrate_24hr", - (data.hashrate_24hr != null ? data.hashrate_24hr : "N/A") + " " + - (data.hashrate_24hr_unit ? data.hashrate_24hr_unit.slice(0,-2).toUpperCase() + data.hashrate_24hr_unit.slice(-2) : "") - ); - - updateElementText("hashrate_3hr", - (data.hashrate_3hr != null ? data.hashrate_3hr : "N/A") + " " + - (data.hashrate_3hr_unit ? data.hashrate_3hr_unit.slice(0,-2).toUpperCase() + data.hashrate_3hr_unit.slice(-2) : "") - ); - - updateElementText("hashrate_10min", - (data.hashrate_10min != null ? data.hashrate_10min : "N/A") + " " + - (data.hashrate_10min_unit ? data.hashrate_10min_unit.slice(0,-2).toUpperCase() + data.hashrate_10min_unit.slice(-2) : "") - ); - - updateElementText("hashrate_60sec", - (data.hashrate_60sec != null ? data.hashrate_60sec : "N/A") + " " + - (data.hashrate_60sec_unit ? data.hashrate_60sec_unit.slice(0,-2).toUpperCase() + data.hashrate_60sec_unit.slice(-2) : "") - ); - - updateElementText("block_number", numberWithCommas(data.block_number)); - - updateElementText("btc_price", - data.btc_price != null ? "$" + numberWithCommas(parseFloat(data.btc_price).toFixed(2)) : "N/A" - ); - - updateElementText("network_hashrate", numberWithCommas(Math.round(data.network_hashrate)) + " EH/s"); - updateElementText("difficulty", numberWithCommas(Math.round(data.difficulty))); - updateElementText("daily_revenue", "$" + numberWithCommas(data.daily_revenue.toFixed(2))); - updateElementText("daily_power_cost", "$" + numberWithCommas(data.daily_power_cost.toFixed(2))); - updateElementText("daily_profit_usd", "$" + numberWithCommas(data.daily_profit_usd.toFixed(2))); - updateElementText("monthly_profit_usd", "$" + numberWithCommas(data.monthly_profit_usd.toFixed(2))); - updateElementText("daily_mined_sats", numberWithCommas(data.daily_mined_sats) + " sats"); - updateElementText("monthly_mined_sats", numberWithCommas(data.monthly_mined_sats) + " sats"); - - // Update worker count from metrics (just the number, not full worker data) - updateWorkersCount(); - - updateElementText("unpaid_earnings", data.unpaid_earnings + " BTC"); - - // Update payout estimation with color coding - const payoutText = data.est_time_to_payout; - updateElementText("est_time_to_payout", payoutText); - - if (payoutText && payoutText.toLowerCase().includes("next block")) { - $("#est_time_to_payout").css({ - "color": "#32CD32", - "animation": "glowPulse 1s infinite" - }); - } else { - const days = parseFloat(payoutText); - if (!isNaN(days)) { - if (days < 4) { - $("#est_time_to_payout").css({"color": "#32CD32", "animation": "none"}); - } else if (days > 20) { - $("#est_time_to_payout").css({"color": "red", "animation": "none"}); - } else { - $("#est_time_to_payout").css({"color": "#ffd700", "animation": "none"}); - } - } else { - $("#est_time_to_payout").css({"color": "#ffd700", "animation": "none"}); - } - } - - updateElementText("last_block_height", data.last_block_height || ""); - updateElementText("last_block_time", data.last_block_time || ""); - updateElementText("blocks_found", data.blocks_found || "0"); - updateElementText("last_share", data.total_last_share || ""); - - // Update Estimated Earnings metrics - updateElementText("estimated_earnings_per_day_sats", numberWithCommas(data.estimated_earnings_per_day_sats) + " sats"); - updateElementText("estimated_earnings_next_block_sats", numberWithCommas(data.estimated_earnings_next_block_sats) + " sats"); - updateElementText("estimated_rewards_in_window_sats", numberWithCommas(data.estimated_rewards_in_window_sats) + " sats"); - - // Update last updated timestamp - const now = new Date(Date.now() + serverTimeOffset); - updateElementHTML("lastUpdated", "Last Updated: " + now.toLocaleString() + ""); - - // Update chart if it exists - if (trendChart) { - try { - // Always update the 24hr average line even if we don't have data points yet - const avg24hr = parseFloat(data.hashrate_24hr || 0); - if (!isNaN(avg24hr) && - trendChart.options.plugins.annotation && - trendChart.options.plugins.annotation.annotations && - trendChart.options.plugins.annotation.annotations.averageLine) { - const annotation = trendChart.options.plugins.annotation.annotations.averageLine; - annotation.yMin = avg24hr; - annotation.yMax = avg24hr; - annotation.label.content = '24hr Avg: ' + avg24hr + ' TH/s'; - } - - // Update data points if we have any (removed minimum length requirement) - if (data.arrow_history && data.arrow_history.hashrate_60sec) { - const historyData = data.arrow_history.hashrate_60sec; - if (historyData && historyData.length > 0) { - console.log(`Updating chart with ${historyData.length} data points`); - trendChart.data.labels = historyData.map(item => item.time); - trendChart.data.datasets[0].data = historyData.map(item => { - const val = parseFloat(item.value); - return isNaN(val) ? 0 : val; - }); - } else { - console.log("No history data points available yet"); - } - } else { - console.log("No hashrate_60sec history available yet"); - - // If there's no history data, create a starting point using current hashrate - if (data.hashrate_60sec) { - const currentTime = new Date().toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute: '2-digit'}); - trendChart.data.labels = [currentTime]; - trendChart.data.datasets[0].data = [parseFloat(data.hashrate_60sec) || 0]; - console.log("Created initial data point with current hashrate"); - } - } - - // Always update the chart, even if we just updated the average line - trendChart.update('none'); - } catch (chartError) { - console.error("Error updating chart:", chartError); - } - } - - // Update indicators and check for block updates - updateIndicators(data); - checkForBlockUpdates(data); - - } catch (error) { - console.error("Error updating UI:", error); - } -} - -// Set up refresh synchronization -function setupRefreshSync() { - // Listen for the dataRefreshed event - $(document).on('dataRefreshed', function() { - // Broadcast to any other open tabs/pages that the data has been refreshed - try { - // Store the current timestamp to localStorage - localStorage.setItem('dashboardRefreshTime', Date.now().toString()); - - // Create a custom event that can be detected across tabs/pages - localStorage.setItem('dashboardRefreshEvent', 'refresh-' + Date.now()); - - console.log("Dashboard refresh synchronized"); - } catch (e) { - console.error("Error synchronizing refresh:", e); - } - }); -} - -// Document ready initialization -$(document).ready(function() { - // Initialize the chart - trendChart = initializeChart(); - - // Initialize the progress bar - initProgressBar(); - - // Set up direct monitoring of data refreshes - $(document).on('dataRefreshed', function() { - console.log("Data refresh event detected, resetting progress bar"); - lastUpdateTime = Date.now(); - currentProgress = 0; - updateProgressBar(currentProgress); - }); - - // Wrap the updateUI function to detect changes and trigger events - const originalUpdateUI = updateUI; - updateUI = function() { - const previousMetricsTimestamp = latestMetrics ? latestMetrics.server_timestamp : null; - - // Call the original function - originalUpdateUI.apply(this, arguments); - - // Check if we got new data by comparing timestamps - if (latestMetrics && latestMetrics.server_timestamp !== previousMetricsTimestamp) { - console.log("New data detected, triggering refresh event"); - $(document).trigger('dataRefreshed'); - } - }; - - // Set up event source for SSE - setupEventSource(); - - // Start server time polling - updateServerTime(); - setInterval(updateServerTime, 30000); - - // Start uptime timer - setInterval(updateUptime, 1000); - updateUptime(); - - // Set up refresh synchronization with workers page - setupRefreshSync(); - - // Add a manual refresh button for fallback - $("body").append(''); - - $("#refreshButton").on("click", function() { - $(this).text("Refreshing..."); - $(this).prop("disabled", true); - manualRefresh(); - setTimeout(function() { - $("#refreshButton").text("Refresh Data"); - $("#refreshButton").prop("disabled", false); - }, 5000); - }); - - // Force a data refresh when the page loads - manualRefresh(); - - // Add emergency refresh button functionality - $("#forceRefreshBtn").show().on("click", function() { - $(this).text("Refreshing..."); - $(this).prop("disabled", true); - - $.ajax({ - url: '/api/force-refresh', - method: 'POST', - timeout: 15000, - success: function(data) { - console.log("Force refresh successful:", data); - manualRefresh(); // Immediately get the new data - $("#forceRefreshBtn").text("Force Refresh").prop("disabled", false); - }, - error: function(xhr, status, error) { - console.error("Force refresh failed:", error); - $("#forceRefreshBtn").text("Force Refresh").prop("disabled", false); - alert("Refresh failed: " + error); - } - }); - }); - - // Add stale data detection - setInterval(function() { - if (latestMetrics && latestMetrics.server_timestamp) { - const lastUpdate = new Date(latestMetrics.server_timestamp); - const timeSinceUpdate = Math.floor((Date.now() - lastUpdate.getTime()) / 1000); - if (timeSinceUpdate > 120) { // More than 2 minutes - showConnectionIssue(`Data stale (${timeSinceUpdate}s old). Use Force Refresh.`); - $("#forceRefreshBtn").show(); - } - } - }, 30000); // Check every 30 seconds -}); diff --git a/static/js/retro-refresh.js b/static/js/retro-refresh.js deleted file mode 100644 index af5b2e4..0000000 --- a/static/js/retro-refresh.js +++ /dev/null @@ -1,238 +0,0 @@ -// This script integrates the retro floating refresh bar -// with the existing dashboard and workers page functionality - -(function() { - // Wait for DOM to be ready - document.addEventListener('DOMContentLoaded', function() { - // Create the retro terminal bar if it doesn't exist yet - if (!document.getElementById('retro-terminal-bar')) { - createRetroTerminalBar(); - } - - // Hide the original refresh container - const originalRefreshUptime = document.getElementById('refreshUptime'); - if (originalRefreshUptime) { - originalRefreshUptime.style.visibility = 'hidden'; - originalRefreshUptime.style.height = '0'; - originalRefreshUptime.style.overflow = 'hidden'; - - // Important: We keep the original elements and just hide them - // This ensures all existing JavaScript functions still work - } - - // Add extra space at the bottom of the page to prevent the floating bar from covering content - const extraSpace = document.createElement('div'); - extraSpace.style.height = '100px'; - document.body.appendChild(extraSpace); - }); - - // Function to create the retro terminal bar - function createRetroTerminalBar() { - // Get the HTML content from the shared CSS/HTML - const html = ` -
-
-
SYSTEM MONITOR v0.1
-
-
-
-
-
-
-
-
-
- LIVE -
-
- 00:00:00 -
-
- -
- -
-
-
-
-
- 0s - 15s - 30s - 45s - 60s -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
60s to next update
-
Uptime: 0h 0m 0s
-
-
- `; - - // Create a container for the HTML - const container = document.createElement('div'); - container.innerHTML = html; - - // Append to the body - document.body.appendChild(container.firstElementChild); - - // Start the clock update - updateTerminalClock(); - setInterval(updateTerminalClock, 1000); - - // Check if terminal should be collapsed based on previous state - const isCollapsed = localStorage.getItem('terminalCollapsed') === 'true'; - if (isCollapsed) { - document.getElementById('retro-terminal-bar').classList.add('collapsed'); - } - } - - // Function to update the terminal clock - function updateTerminalClock() { - const clockElement = document.getElementById('data-refresh-time'); - if (clockElement) { - const now = new Date(); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - clockElement.textContent = `${hours}:${minutes}:${seconds}`; - } - } - - // Expose these functions globally for the onclick handlers - window.toggleTerminal = function() { - const terminal = document.getElementById('retro-terminal-bar'); - terminal.classList.toggle('collapsed'); - - // Store state in localStorage - localStorage.setItem('terminalCollapsed', terminal.classList.contains('collapsed')); - }; - - window.hideTerminal = function() { - document.getElementById('retro-terminal-bar').style.display = 'none'; - - // Create a show button that appears at the bottom right - const showButton = document.createElement('button'); - showButton.id = 'show-terminal-button'; - showButton.textContent = 'Show Monitor'; - showButton.style.position = 'fixed'; - showButton.style.bottom = '10px'; - showButton.style.right = '10px'; - showButton.style.zIndex = '1000'; - showButton.style.backgroundColor = '#f7931a'; - showButton.style.color = '#000'; - showButton.style.border = 'none'; - showButton.style.padding = '8px 12px'; - showButton.style.cursor = 'pointer'; - showButton.style.fontFamily = "'VT323', monospace"; - showButton.style.fontSize = '14px'; - showButton.onclick = function() { - document.getElementById('retro-terminal-bar').style.display = 'block'; - this.remove(); - }; - document.body.appendChild(showButton); - }; - - // Redirect original progress bar updates to our new floating bar - // This Observer will listen for changes to the original #bitcoin-progress-inner - // and replicate them to our new floating bar version - const initProgressObserver = function() { - // Setup a MutationObserver to watch for style changes on the original progress bar - const originalProgressBar = document.querySelector('#refreshUptime #bitcoin-progress-inner'); - const newProgressBar = document.querySelector('#retro-terminal-bar #bitcoin-progress-inner'); - - if (originalProgressBar && newProgressBar) { - const observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.attributeName === 'style') { - // Get the width from the original progress bar - const width = originalProgressBar.style.width; - if (width) { - // Apply it to our new progress bar - newProgressBar.style.width = width; - - // Also copy any classes (like glow-effect) - if (originalProgressBar.classList.contains('glow-effect') && - !newProgressBar.classList.contains('glow-effect')) { - newProgressBar.classList.add('glow-effect'); - } else if (!originalProgressBar.classList.contains('glow-effect') && - newProgressBar.classList.contains('glow-effect')) { - newProgressBar.classList.remove('glow-effect'); - } - - // Copy waiting-for-update class - if (originalProgressBar.classList.contains('waiting-for-update') && - !newProgressBar.classList.contains('waiting-for-update')) { - newProgressBar.classList.add('waiting-for-update'); - } else if (!originalProgressBar.classList.contains('waiting-for-update') && - newProgressBar.classList.contains('waiting-for-update')) { - newProgressBar.classList.remove('waiting-for-update'); - } - } - } - }); - }); - - // Start observing - observer.observe(originalProgressBar, { attributes: true }); - } - - // Also watch for changes to the progress text - const originalProgressText = document.querySelector('#refreshUptime #progress-text'); - const newProgressText = document.querySelector('#retro-terminal-bar #progress-text'); - - if (originalProgressText && newProgressText) { - const textObserver = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.type === 'childList') { - // Update the text in our new bar - newProgressText.textContent = originalProgressText.textContent; - } - }); - }); - - // Start observing - textObserver.observe(originalProgressText, { childList: true, subtree: true }); - } - - // Watch for changes to the uptime timer - const originalUptimeTimer = document.querySelector('#refreshUptime #uptimeTimer'); - const newUptimeTimer = document.querySelector('#retro-terminal-bar #uptimeTimer'); - - if (originalUptimeTimer && newUptimeTimer) { - const uptimeObserver = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.type === 'childList') { - // Update the text in our new bar - newUptimeTimer.innerHTML = originalUptimeTimer.innerHTML; - } - }); - }); - - // Start observing - uptimeObserver.observe(originalUptimeTimer, { childList: true, subtree: true }); - } - }; - - // Start the observer once the page is fully loaded - window.addEventListener('load', function() { - // Give a short delay to ensure all elements are rendered - setTimeout(initProgressObserver, 500); - }); -})(); \ No newline at end of file diff --git a/static/js/workers.js b/static/js/workers.js deleted file mode 100644 index 7c4feec..0000000 --- a/static/js/workers.js +++ /dev/null @@ -1,641 +0,0 @@ -"use strict"; - -// Global variables for workers dashboard -let workerData = null; -let refreshTimer; -let pageLoadTime = Date.now(); -let currentProgress = 0; -const PROGRESS_MAX = 60; // 60 seconds for a complete cycle -let lastUpdateTime = Date.now(); -let filterState = { - currentFilter: 'all', - searchTerm: '' -}; -let miniChart = null; -let connectionRetryCount = 0; - -// Server time variables for uptime calculation - synced with main dashboard -let serverTimeOffset = 0; -let serverStartTime = null; - -// New variable to track custom refresh timing -let lastManualRefreshTime = 0; -const MIN_REFRESH_INTERVAL = 10000; // Minimum 10 seconds between refreshes - -// Initialize the page -$(document).ready(function() { - // Set up initial UI - initializePage(); - - // Get server time for uptime calculation - updateServerTime(); - - // Set up refresh synchronization with main dashboard - setupRefreshSync(); - - // Fetch worker data immediately on page load - fetchWorkerData(); - - // Set up refresh timer - setInterval(updateProgressBar, 1000); - - // Set up uptime timer - synced with main dashboard - setInterval(updateUptime, 1000); - - // Start server time polling - same as main dashboard - setInterval(updateServerTime, 30000); - - // Auto-refresh worker data - aligned with main dashboard if possible - setInterval(function() { - // Check if it's been at least PROGRESS_MAX seconds since last update - const timeSinceLastUpdate = Date.now() - lastUpdateTime; - if (timeSinceLastUpdate >= PROGRESS_MAX * 1000) { - // Check if there was a recent manual refresh - const timeSinceManualRefresh = Date.now() - lastManualRefreshTime; - if (timeSinceManualRefresh >= MIN_REFRESH_INTERVAL) { - console.log("Auto-refresh triggered after time interval"); - fetchWorkerData(); - } - } - }, 10000); // Check every 10 seconds to align better with main dashboard - - // Set up filter button click handlers - $('.filter-button').click(function() { - $('.filter-button').removeClass('active'); - $(this).addClass('active'); - filterState.currentFilter = $(this).data('filter'); - filterWorkers(); - }); - - // Set up search input handler - $('#worker-search').on('input', function() { - filterState.searchTerm = $(this).val().toLowerCase(); - filterWorkers(); - }); -}); - -// Set up refresh synchronization with main dashboard -function setupRefreshSync() { - // Listen for storage events (triggered by main dashboard) - window.addEventListener('storage', function(event) { - // Check if this is our dashboard refresh event - if (event.key === 'dashboardRefreshEvent') { - console.log("Detected dashboard refresh event"); - - // Prevent too frequent refreshes - const now = Date.now(); - const timeSinceLastRefresh = now - lastUpdateTime; - - if (timeSinceLastRefresh >= MIN_REFRESH_INTERVAL) { - console.log("Syncing refresh with main dashboard"); - // Reset progress bar and immediately fetch - resetProgressBar(); - // Refresh the worker data - fetchWorkerData(); - } else { - console.log("Skipping too-frequent refresh", timeSinceLastRefresh); - // Just reset the progress bar to match main dashboard - resetProgressBar(); - } - } - }); - - // On page load, check if we should align with main dashboard timing - try { - const lastDashboardRefresh = localStorage.getItem('dashboardRefreshTime'); - if (lastDashboardRefresh) { - const lastRefreshTime = parseInt(lastDashboardRefresh); - const timeSinceLastDashboardRefresh = Date.now() - lastRefreshTime; - - // If main dashboard refreshed recently, adjust our timer - if (timeSinceLastDashboardRefresh < PROGRESS_MAX * 1000) { - console.log("Adjusting timer to align with main dashboard"); - currentProgress = Math.floor(timeSinceLastDashboardRefresh / 1000); - updateProgressBar(currentProgress); - - // Calculate when next update will happen (roughly 60 seconds from last dashboard refresh) - const timeUntilNextRefresh = (PROGRESS_MAX * 1000) - timeSinceLastDashboardRefresh; - - // Schedule a one-time check near the expected refresh time - if (timeUntilNextRefresh > 0) { - console.log(`Scheduling coordinated refresh in ${Math.floor(timeUntilNextRefresh/1000)} seconds`); - setTimeout(function() { - // Check if a refresh happened in the last few seconds via localStorage event - const newLastRefresh = parseInt(localStorage.getItem('dashboardRefreshTime') || '0'); - const secondsSinceLastRefresh = (Date.now() - newLastRefresh) / 1000; - - // If dashboard hasn't refreshed in the last 5 seconds, do our own refresh - if (secondsSinceLastRefresh > 5) { - console.log("Coordinated refresh time reached, fetching data"); - fetchWorkerData(); - } else { - console.log("Dashboard already refreshed recently, skipping coordinated refresh"); - } - }, timeUntilNextRefresh); - } - } - } - } catch (e) { - console.error("Error reading dashboard refresh time:", e); - } - - // Check for dashboard refresh periodically - setInterval(function() { - try { - const lastDashboardRefresh = parseInt(localStorage.getItem('dashboardRefreshTime') || '0'); - const now = Date.now(); - const timeSinceLastRefresh = (now - lastUpdateTime) / 1000; - const timeSinceDashboardRefresh = (now - lastDashboardRefresh) / 1000; - - // If dashboard refreshed more recently than we did and we haven't refreshed in at least 10 seconds - if (lastDashboardRefresh > lastUpdateTime && timeSinceLastRefresh > 10) { - console.log("Catching up with dashboard refresh"); - resetProgressBar(); - fetchWorkerData(); - } - } catch (e) { - console.error("Error in periodic dashboard check:", e); - } - }, 5000); // Check every 5 seconds -} - -// Server time update via polling - same as main.js -function updateServerTime() { - $.ajax({ - url: "/api/time", - method: "GET", - timeout: 5000, - success: function(data) { - serverTimeOffset = new Date(data.server_timestamp).getTime() - Date.now(); - serverStartTime = new Date(data.server_start_time).getTime(); - }, - error: function(jqXHR, textStatus, errorThrown) { - console.error("Error fetching server time:", textStatus, errorThrown); - } - }); -} - -// Update uptime display - synced with main dashboard -function updateUptime() { - if (serverStartTime) { - const currentServerTime = Date.now() + serverTimeOffset; - const diff = currentServerTime - serverStartTime; - const hours = Math.floor(diff / (1000 * 60 * 60)); - const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((diff % (1000 * 60)) / 1000); - $("#uptimeTimer").html("Uptime: " + hours + "h " + minutes + "m " + seconds + "s"); - } -} - -// Initialize page elements -function initializePage() { - // Initialize mini chart for total hashrate if the element exists - if (document.getElementById('total-hashrate-chart')) { - initializeMiniChart(); - } - - // Show loading state - $('#worker-grid').html('
Loading worker data...
'); - - // Add retry button (hidden by default) - if (!$('#retry-button').length) { - $('body').append(''); - - $('#retry-button').on('click', function() { - $(this).text('Retrying...').prop('disabled', true); - fetchWorkerData(true); - setTimeout(() => { - $('#retry-button').text('Retry Loading Data').prop('disabled', false); - }, 3000); - }); - } -} - -// Fetch worker data from API -function fetchWorkerData(forceRefresh = false) { - // Track this as a manual refresh for throttling purposes - lastManualRefreshTime = Date.now(); - - $('#worker-grid').addClass('loading-fade'); - - // Update progress bar to show data is being fetched - resetProgressBar(); - - // Choose API URL based on whether we're forcing a refresh - const apiUrl = `/api/workers${forceRefresh ? '?force=true' : ''}`; - - $.ajax({ - url: apiUrl, - method: 'GET', - dataType: 'json', - timeout: 15000, // 15 second timeout - success: function(data) { - workerData = data; - lastUpdateTime = Date.now(); - - // Update UI with new data - updateWorkerGrid(); - updateSummaryStats(); - updateMiniChart(); - updateLastUpdated(); - - // Hide retry button - $('#retry-button').hide(); - - // Reset connection retry count - connectionRetryCount = 0; - - console.log("Worker data updated successfully"); - }, - error: function(xhr, status, error) { - console.error("Error fetching worker data:", error); - - // Show error in worker grid - $('#worker-grid').html(` -
- -

Error loading worker data: ${error || 'Unknown error'}

-
- `); - - // Show retry button - $('#retry-button').show(); - - // Implement exponential backoff for automatic retry - connectionRetryCount++; - const delay = Math.min(30000, 1000 * Math.pow(1.5, Math.min(5, connectionRetryCount))); - console.log(`Will retry in ${delay/1000} seconds (attempt ${connectionRetryCount})`); - - setTimeout(() => { - fetchWorkerData(true); // Force refresh on retry - }, delay); - }, - complete: function() { - $('#worker-grid').removeClass('loading-fade'); - } - }); -} - -// Update the worker grid with data -// UPDATED FUNCTION -function updateWorkerGrid() { - if (!workerData || !workerData.workers) { - console.error("No worker data available"); - return; - } - - const workerGrid = $('#worker-grid'); - workerGrid.empty(); - - // Apply current filters before rendering - const filteredWorkers = filterWorkersData(workerData.workers); - - if (filteredWorkers.length === 0) { - workerGrid.html(` -
- -

No workers match your filter criteria

-
- `); - return; - } - - // Calculate total unpaid earnings (from the dashboard) - const totalUnpaidEarnings = workerData.total_earnings || 0; - - // Sum up hashrates of online workers to calculate share percentages - const totalHashrate = workerData.workers - .filter(w => w.status === 'online') - .reduce((sum, w) => sum + parseFloat(w.hashrate_3hr || 0), 0); - - // Calculate share percentage for each worker - const onlineWorkers = workerData.workers.filter(w => w.status === 'online'); - const offlineWorkers = workerData.workers.filter(w => w.status === 'offline'); - - // Allocate 95% to online workers, 5% to offline workers - const onlinePool = totalUnpaidEarnings * 0.95; - const offlinePool = totalUnpaidEarnings * 0.05; - - // Generate worker cards - filteredWorkers.forEach(worker => { - // Calculate earnings share based on hashrate proportion - let earningsDisplay = worker.earnings; - - // Explicitly recalculate earnings share for display consistency - if (worker.status === 'online' && totalHashrate > 0) { - const hashrateShare = parseFloat(worker.hashrate_3hr || 0) / totalHashrate; - earningsDisplay = (onlinePool * hashrateShare).toFixed(8); - } else if (worker.status === 'offline' && offlineWorkers.length > 0) { - earningsDisplay = (offlinePool / offlineWorkers.length).toFixed(8); - } - - // Create worker card - const card = $('
'); - - // Add class based on status - if (worker.status === 'online') { - card.addClass('worker-card-online'); - } else { - card.addClass('worker-card-offline'); - } - - // Add worker type badge - card.append(`
${worker.type}
`); - - // Add worker name - card.append(`
${worker.name}
`); - - // Add status badge - if (worker.status === 'online') { - card.append('
ONLINE
'); - } else { - card.append('
OFFLINE
'); - } - - // Add hashrate bar - const maxHashrate = 200; // TH/s - adjust based on your fleet - const hashratePercent = Math.min(100, (worker.hashrate_3hr / maxHashrate) * 100); - card.append(` -
-
Hashrate (3hr):
-
${worker.hashrate_3hr} ${worker.hashrate_3hr_unit}
-
-
-
-
- `); - - // Add additional stats - NOTE: Using recalculated earnings - card.append(` -
-
-
Last Share:
-
${worker.last_share.split(' ')[1]}
-
-
-
Earnings:
-
${earningsDisplay}
-
-
-
Accept Rate:
-
${worker.acceptance_rate}%
-
-
-
Temp:
-
${worker.temperature > 0 ? worker.temperature + '°C' : 'N/A'}
-
-
- `); - - // Add card to grid - workerGrid.append(card); - }); - - // Verify the sum of displayed earnings equals the total - console.log(`Total unpaid earnings: ${totalUnpaidEarnings} BTC`); - console.log(`Sum of worker displayed earnings: ${ - filteredWorkers.reduce((sum, w) => { - if (w.status === 'online' && totalHashrate > 0) { - const hashrateShare = parseFloat(w.hashrate_3hr || 0) / totalHashrate; - return sum + parseFloat((onlinePool * hashrateShare).toFixed(8)); - } else if (w.status === 'offline' && offlineWorkers.length > 0) { - return sum + parseFloat((offlinePool / offlineWorkers.length).toFixed(8)); - } - return sum; - }, 0) - } BTC`); -} - -// Filter worker data based on current filter state -function filterWorkersData(workers) { - if (!workers) return []; - - return workers.filter(worker => { - const workerName = worker.name.toLowerCase(); - const isOnline = worker.status === 'online'; - const workerType = worker.type.toLowerCase(); - - // Check if worker matches filter - let matchesFilter = false; - if (filterState.currentFilter === 'all') { - matchesFilter = true; - } else if (filterState.currentFilter === 'online' && isOnline) { - matchesFilter = true; - } else if (filterState.currentFilter === 'offline' && !isOnline) { - matchesFilter = true; - } else if (filterState.currentFilter === 'asic' && workerType === 'asic') { - matchesFilter = true; - } else if (filterState.currentFilter === 'fpga' && workerType === 'fpga') { - matchesFilter = true; - } - - // Check if worker matches search term - const matchesSearch = workerName.includes(filterState.searchTerm); - - return matchesFilter && matchesSearch; - }); -} - -// Apply filter to rendered worker cards -function filterWorkers() { - if (!workerData || !workerData.workers) return; - - // Re-render the worker grid with current filters - updateWorkerGrid(); -} - -// Modified updateSummaryStats function for workers.js -function updateSummaryStats() { - if (!workerData) return; - - // Update worker counts - $('#workers-count').text(workerData.workers_total || 0); - $('#workers-online').text(workerData.workers_online || 0); - $('#workers-offline').text(workerData.workers_offline || 0); - - // Update worker ring percentage - const onlinePercent = workerData.workers_total > 0 ? - workerData.workers_online / workerData.workers_total : 0; - $('.worker-ring').css('--online-percent', onlinePercent); - - // IMPORTANT: Update total hashrate using EXACT format matching main dashboard - // This ensures the displayed value matches exactly what's on the main page - if (workerData.total_hashrate !== undefined) { - // Format with exactly 1 decimal place - matches main dashboard format - const formattedHashrate = Number(workerData.total_hashrate).toFixed(1); - $('#total-hashrate').text(`${formattedHashrate} ${workerData.hashrate_unit || 'TH/s'}`); - } else { - $('#total-hashrate').text(`0.0 ${workerData.hashrate_unit || 'TH/s'}`); - } - - // Update other summary stats - $('#total-earnings').text(`${(workerData.total_earnings || 0).toFixed(8)} BTC`); - $('#daily-sats').text(`${numberWithCommas(workerData.daily_sats || 0)} sats`); - $('#avg-acceptance-rate').text(`${(workerData.avg_acceptance_rate || 0).toFixed(2)}%`); -} - -// Initialize mini chart -function initializeMiniChart() { - const ctx = document.getElementById('total-hashrate-chart').getContext('2d'); - - // Generate some sample data to start - const labels = Array(24).fill('').map((_, i) => i); - const data = [750, 760, 755, 770, 780, 775, 760, 765, 770, 775, 780, 790, 785, 775, 770, 765, 780, 785, 775, 770, 775, 780, 775, 774.8]; - - miniChart = new Chart(ctx, { - type: 'line', - data: { - labels: labels, - datasets: [{ - data: data, - borderColor: '#1137F5', - backgroundColor: 'rgba(57, 255, 20, 0.1)', - fill: true, - tension: 0.3, - borderWidth: 1.5, - pointRadius: 0 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - scales: { - x: { display: false }, - y: { - display: false, - min: Math.min(...data) * 0.9, - max: Math.max(...data) * 1.1 - } - }, - plugins: { - legend: { display: false }, - tooltip: { enabled: false } - }, - animation: false, - elements: { - line: { - tension: 0.4 - } - } - } - }); -} - -// Update mini chart with real data -function updateMiniChart() { - if (!miniChart || !workerData || !workerData.hashrate_history) return; - - // Extract hashrate data from history - const historyData = workerData.hashrate_history; - if (!historyData || historyData.length === 0) return; - - // Get the values for the chart - const values = historyData.map(item => parseFloat(item.value) || 0); - const labels = historyData.map(item => item.time); - - // Update chart data - miniChart.data.labels = labels; - miniChart.data.datasets[0].data = values; - - // Update y-axis range - const min = Math.min(...values); - const max = Math.max(...values); - miniChart.options.scales.y.min = min * 0.9; - miniChart.options.scales.y.max = max * 1.1; - - // Update the chart - miniChart.update('none'); -} - -// Update progress bar -function updateProgressBar() { - if (currentProgress < PROGRESS_MAX) { - currentProgress++; - const progressPercent = (currentProgress / PROGRESS_MAX) * 100; - $("#bitcoin-progress-inner").css("width", progressPercent + "%"); - - // Add glowing effect when close to completion - if (progressPercent > 80) { - $("#bitcoin-progress-inner").addClass("glow-effect"); - } else { - $("#bitcoin-progress-inner").removeClass("glow-effect"); - } - - // Update remaining seconds text - let remainingSeconds = PROGRESS_MAX - currentProgress; - if (remainingSeconds <= 0) { - $("#progress-text").text("Waiting for update..."); - $("#bitcoin-progress-inner").addClass("waiting-for-update"); - } else { - $("#progress-text").text(remainingSeconds + "s to next update"); - $("#bitcoin-progress-inner").removeClass("waiting-for-update"); - } - - // Check for main dashboard refresh near the end to ensure sync - if (currentProgress >= 55) { // When we're getting close to refresh time - try { - const lastDashboardRefresh = parseInt(localStorage.getItem('dashboardRefreshTime') || '0'); - const secondsSinceDashboardRefresh = (Date.now() - lastDashboardRefresh) / 1000; - - // If main dashboard just refreshed (within last 5 seconds) - if (secondsSinceDashboardRefresh <= 5) { - console.log("Detected recent dashboard refresh, syncing now"); - resetProgressBar(); - fetchWorkerData(); - return; - } - } catch (e) { - console.error("Error checking dashboard refresh status:", e); - } - } - } else { - // Reset progress bar if it's time to refresh - // But first check if the main dashboard refreshed recently - try { - const lastDashboardRefresh = parseInt(localStorage.getItem('dashboardRefreshTime') || '0'); - const secondsSinceDashboardRefresh = (Date.now() - lastDashboardRefresh) / 1000; - - // If dashboard refreshed in the last 10 seconds, wait for it instead of refreshing ourselves - if (secondsSinceDashboardRefresh < 10) { - console.log("Waiting for dashboard refresh event instead of refreshing independently"); - return; - } - } catch (e) { - console.error("Error checking dashboard refresh status:", e); - } - - // If main dashboard hasn't refreshed recently, do our own refresh - if (Date.now() - lastUpdateTime > PROGRESS_MAX * 1000) { - console.log("Progress bar expired, fetching data"); - fetchWorkerData(); - } - } -} - -// Reset progress bar -function resetProgressBar() { - currentProgress = 0; - $("#bitcoin-progress-inner").css("width", "0%"); - $("#bitcoin-progress-inner").removeClass("glow-effect"); - $("#bitcoin-progress-inner").removeClass("waiting-for-update"); - $("#progress-text").text(PROGRESS_MAX + "s to next update"); -} - -// Update the last updated timestamp -function updateLastUpdated() { - if (!workerData || !workerData.timestamp) return; - - try { - const timestamp = new Date(workerData.timestamp); - $("#lastUpdated").html("Last Updated: " + - timestamp.toLocaleString() + ""); - } catch (e) { - console.error("Error formatting timestamp:", e); - } -} - -// Format numbers with commas -function numberWithCommas(x) { - if (x == null) return "N/A"; - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -}