diff --git a/static/js/workers.js b/static/js/workers.js index d193184..89b2ec7 100644 --- a/static/js/workers.js +++ b/static/js/workers.js @@ -3,9 +3,9 @@ // Global variables for workers dashboard let workerData = null; let refreshTimer; -let pageLoadTime = Date.now(); +const pageLoadTime = Date.now(); let lastManualRefreshTime = 0; -let filterState = { +const filterState = { currentFilter: 'all', searchTerm: '' }; @@ -21,70 +21,54 @@ const MIN_REFRESH_INTERVAL = 10000; // Minimum 10 seconds between refreshes // Hashrate Normalization Utilities // Helper function to normalize hashrate to TH/s for consistent graphing -function normalizeHashrate(value, unit) { +function normalizeHashrate(value, unit = 'th/s') { if (!value || isNaN(value)) return 0; - unit = (unit || 'th/s').toLowerCase(); - if (unit.includes('ph/s')) { - return value * 1000; // Convert PH/s to TH/s - } else if (unit.includes('eh/s')) { - return value * 1000000; // Convert EH/s to TH/s - } else if (unit.includes('gh/s')) { - return value / 1000; // Convert GH/s to TH/s - } else if (unit.includes('mh/s')) { - return value / 1000000; // Convert MH/s to TH/s - } else if (unit.includes('kh/s')) { - return value / 1000000000; // Convert KH/s to TH/s - } else if (unit.includes('h/s') && !unit.includes('th/s') && !unit.includes('ph/s') && - !unit.includes('eh/s') && !unit.includes('gh/s') && !unit.includes('mh/s') && - !unit.includes('kh/s')) { - return value / 1000000000000; // Convert H/s to TH/s - } else { - // Assume TH/s if unit is not recognized - return value; - } + unit = unit.toLowerCase(); + const unitConversion = { + 'ph/s': 1000, + 'eh/s': 1000000, + 'gh/s': 1 / 1000, + 'mh/s': 1 / 1000000, + 'kh/s': 1 / 1000000000, + 'h/s': 1 / 1000000000000 + }; + + return unitConversion[unit] !== undefined ? value * unitConversion[unit] : value; } // Helper function to format hashrate values for display function formatHashrateForDisplay(value, unit) { if (isNaN(value) || value === null || value === undefined) return "N/A"; - // Always normalize to TH/s first if unit is provided - let normalizedValue = unit ? normalizeHashrate(value, unit) : value; + const normalizedValue = unit ? normalizeHashrate(value, unit) : value; + const unitRanges = [ + { threshold: 1000000, unit: 'EH/s', divisor: 1000000 }, + { threshold: 1000, unit: 'PH/s', divisor: 1000 }, + { threshold: 1, unit: 'TH/s', divisor: 1 }, + { threshold: 0.001, unit: 'GH/s', divisor: 1 / 1000 }, + { threshold: 0, unit: 'MH/s', divisor: 1 / 1000000 } + ]; - // Select appropriate unit based on magnitude - if (normalizedValue >= 1000000) { // EH/s range - return (normalizedValue / 1000000).toFixed(2) + ' EH/s'; - } else if (normalizedValue >= 1000) { // PH/s range - return (normalizedValue / 1000).toFixed(2) + ' PH/s'; - } else if (normalizedValue >= 1) { // TH/s range - return normalizedValue.toFixed(2) + ' TH/s'; - } else if (normalizedValue >= 0.001) { // GH/s range - return (normalizedValue * 1000).toFixed(2) + ' GH/s'; - } else { // MH/s range or smaller - return (normalizedValue * 1000000).toFixed(2) + ' MH/s'; + for (const range of unitRanges) { + if (normalizedValue >= range.threshold) { + return (normalizedValue / range.divisor).toFixed(2) + ' ' + range.unit; + } } + return (normalizedValue * 1000000).toFixed(2) + ' MH/s'; } // Initialize the page $(document).ready(function () { console.log("Worker page initializing..."); - // Initialize notification badge initNotificationBadge(); - - // Set up initial UI initializePage(); - - // Get server time for uptime calculation updateServerTime(); - // Define global refresh function for BitcoinMinuteRefresh window.manualRefresh = fetchWorkerData; - // Wait before initializing BitcoinMinuteRefresh to ensure DOM is ready - setTimeout(function () { - // Initialize BitcoinMinuteRefresh with our refresh function + setTimeout(() => { if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.initialize) { BitcoinMinuteRefresh.initialize(window.manualRefresh); console.log("BitcoinMinuteRefresh initialized with refresh function"); @@ -93,10 +77,8 @@ $(document).ready(function () { } }, 500); - // Fetch worker data immediately on page load fetchWorkerData(); - // Set up filter button click handlers $('.filter-button').click(function () { $('.filter-button').removeClass('active'); $(this).addClass('active'); @@ -104,7 +86,6 @@ $(document).ready(function () { filterWorkers(); }); - // Set up search input handler $('#worker-search').on('input', function () { filterState.searchTerm = $(this).val().toLowerCase(); filterWorkers(); @@ -115,15 +96,12 @@ $(document).ready(function () { function initializePage() { console.log("Initializing page elements..."); - // 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(''); @@ -157,10 +135,7 @@ function updateNotificationBadge() { // Initialize notification badge checking function initNotificationBadge() { - // Update immediately updateNotificationBadge(); - - // Update every 60 seconds setInterval(updateNotificationBadge, 60000); } @@ -168,7 +143,6 @@ function initNotificationBadge() { function updateServerTime() { console.log("Updating server time..."); - // First try to get stored values try { const storedOffset = localStorage.getItem('serverTimeOffset'); const storedStartTime = localStorage.getItem('serverStartTime'); @@ -178,31 +152,26 @@ function updateServerTime() { serverStartTime = parseFloat(storedStartTime); console.log("Using stored server time offset:", serverTimeOffset, "ms"); - // Only update BitcoinMinuteRefresh if it's initialized if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) { BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime); } - return; // Don't fetch if we have valid values + return; } } catch (e) { console.error("Error reading stored server time:", e); } - // Fetch from API if needed $.ajax({ url: "/api/time", method: "GET", timeout: 5000, success: function (data) { - // Calculate the offset between server time and local time serverTimeOffset = new Date(data.server_timestamp).getTime() - Date.now(); serverStartTime = new Date(data.server_start_time).getTime(); - // Store in localStorage for cross-page sharing localStorage.setItem('serverTimeOffset', serverTimeOffset.toString()); localStorage.setItem('serverStartTime', serverStartTime.toString()); - // Only update BitcoinMinuteRefresh if it's initialized if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.updateServerTime) { BitcoinMinuteRefresh.updateServerTime(serverTimeOffset, serverStartTime); } @@ -219,19 +188,16 @@ function updateServerTime() { function fetchWorkerData(forceRefresh = false) { console.log("Fetching worker data..."); - // Track this as a manual refresh for throttling purposes lastManualRefreshTime = Date.now(); - $('#worker-grid').addClass('loading-fade'); - // 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 + timeout: 15000, success: function (data) { if (!data || !data.workers || data.workers.length === 0) { console.warn("No workers found in data response"); @@ -245,21 +211,16 @@ function fetchWorkerData(forceRefresh = false) { workerData = data; - // Notify BitcoinMinuteRefresh that we've refreshed the data if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.notifyRefresh) { BitcoinMinuteRefresh.notifyRefresh(); } - // 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"); @@ -267,7 +228,6 @@ function fetchWorkerData(forceRefresh = false) { error: function (xhr, status, error) { console.error("Error fetching worker data:", error); - // Show error in worker grid $('#worker-grid').html(`
@@ -275,16 +235,14 @@ function fetchWorkerData(forceRefresh = false) {
`); - // 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 + fetchWorkerData(true); }, delay); }, complete: function () { @@ -305,7 +263,6 @@ function updateWorkerGrid() { const workerGrid = $('#worker-grid'); workerGrid.empty(); - // Apply current filters before rendering const filteredWorkers = filterWorkersData(workerData.workers); if (filteredWorkers.length === 0) { @@ -318,107 +275,75 @@ function updateWorkerGrid() { return; } - // Generate worker cards filteredWorkers.forEach(worker => { - // 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 with normalized values for consistent display - const maxHashrate = 200; // TH/s - adjust based on your fleet - const normalizedHashrate = normalizeHashrate( - worker.hashrate_3hr, - worker.hashrate_3hr_unit || 'th/s' - ); - const hashratePercent = Math.min(100, (normalizedHashrate / maxHashrate) * 100); - - // Format hashrate for display with appropriate unit - const formattedHashrate = formatHashrateForDisplay( - worker.hashrate_3hr, - worker.hashrate_3hr_unit || 'th/s' - ); - - card.append(` -
-
Hashrate (3hr):
-
${formattedHashrate}
-
-
-
-
- `); - - // Add additional stats - card.append(` -
-
-
Last Share:
-
${typeof worker.last_share === 'string' ? worker.last_share.split(' ')[1] || worker.last_share : 'N/A'}
-
-
-
Earnings:
-
${worker.earnings.toFixed(8)}
-
-
-
Accept Rate:
-
${worker.acceptance_rate}%
-
-
-
Temp:
-
${worker.temperature > 0 ? worker.temperature + '°C' : 'N/A'}
-
-
- `); - - // Add card to grid + const card = createWorkerCard(worker); workerGrid.append(card); }); } +// Create worker card element +function createWorkerCard(worker) { + const card = $('
'); + + card.addClass(worker.status === 'online' ? 'worker-card-online' : 'worker-card-offline'); + card.append(`
${worker.type}
`); + card.append(`
${worker.name}
`); + card.append(`
${worker.status.toUpperCase()}
`); + + const maxHashrate = 125; // TH/s - adjust based on your fleet + const normalizedHashrate = normalizeHashrate(worker.hashrate_3hr, worker.hashrate_3hr_unit || 'th/s'); + const hashratePercent = Math.min(100, (normalizedHashrate / maxHashrate) * 100); + const formattedHashrate = formatHashrateForDisplay(worker.hashrate_3hr, worker.hashrate_3hr_unit || 'th/s'); + + card.append(` +
+
Hashrate (3hr):
+
${formattedHashrate}
+
+
+
+
+ `); + + card.append(` +
+
+
Last Share:
+
${typeof worker.last_share === 'string' ? worker.last_share.split(' ')[1] || worker.last_share : 'N/A'}
+
+
+
Earnings:
+
${worker.earnings.toFixed(8)}
+
+
+
Accept Rate:
+
${worker.acceptance_rate}%
+
+
+
Temp:
+
${worker.temperature > 0 ? worker.temperature + '°C' : 'N/A'}
+
+
+ `); + + return card; +} + // Filter worker data based on current filter state function filterWorkersData(workers) { if (!workers) return []; return workers.filter(worker => { - // Default to empty string if name is undefined 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; - } + const matchesFilter = filterState.currentFilter === 'all' || + (filterState.currentFilter === 'online' && isOnline) || + (filterState.currentFilter === 'offline' && !isOnline) || + (filterState.currentFilter === 'asic' && workerType === 'asic') || + (filterState.currentFilter === 'fpga' && workerType === 'fpga'); - // Check if worker matches search term const matchesSearch = filterState.searchTerm === '' || workerName.includes(filterState.searchTerm); return matchesFilter && matchesSearch; @@ -428,38 +353,25 @@ function filterWorkersData(workers) { // 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 with normalized hashrate display +// Update summary stats with normalized hashrate display 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; + const onlinePercent = workerData.workers_total > 0 ? workerData.workers_online / workerData.workers_total : 0; $('.worker-ring').css('--online-percent', onlinePercent); - // Display normalized hashrate with appropriate unit - if (workerData.total_hashrate !== undefined) { - // Format with proper unit conversion - const formattedHashrate = formatHashrateForDisplay( - workerData.total_hashrate, - workerData.hashrate_unit || 'TH/s' - ); - $('#total-hashrate').text(formattedHashrate); - } else { - $('#total-hashrate').text(`0.0 TH/s`); - } + const formattedHashrate = workerData.total_hashrate !== undefined ? + formatHashrateForDisplay(workerData.total_hashrate, workerData.hashrate_unit || 'TH/s') : + '0.0 TH/s'; + $('#total-hashrate').text(formattedHashrate); - // 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)}%`); @@ -475,7 +387,6 @@ function initializeMiniChart() { return; } - // Generate some sample data to start const labels = Array(24).fill('').map((_, i) => i); const data = Array(24).fill(0).map(() => Math.random() * 100 + 700); @@ -525,32 +436,23 @@ function updateMiniChart() { return; } - // Extract hashrate data from history const historyData = workerData.hashrate_history; if (!historyData || historyData.length === 0) { console.log("No hashrate history data available"); return; } - // Get the normalized values for the chart - const values = historyData.map(item => { - const val = parseFloat(item.value) || 0; - const unit = item.unit || workerData.hashrate_unit || 'th/s'; - return normalizeHashrate(val, unit); - }); + const values = historyData.map(item => normalizeHashrate(parseFloat(item.value) || 0, item.unit || workerData.hashrate_unit || 'th/s')); 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.filter(v => v > 0)) || 0; const max = Math.max(...values) || 1; miniChart.options.scales.y.min = min * 0.9; miniChart.options.scales.y.max = max * 1.1; - // Update the chart miniChart.update('none'); }