diff --git a/static/js/main.js b/static/js/main.js index 38d4ae9..cd28deb 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -384,6 +384,31 @@ function formatHashrateForDisplay(value, unit) { } } +// Helper function: Find an exact or nearest matching label +function findMatchingLabel(timeStr, labels, toleranceMinutes = 2) { + // If exact match, return it + if (labels.includes(timeStr)) { + return timeStr; + } + // Parse timeStr into minutes from midnight + const [hours, minutes] = timeStr.split(':').map(Number); + const targetTotal = hours * 60 + minutes; + let closestLabel = null; + let smallestDiff = Infinity; + + labels.forEach(label => { + if (!label.includes(':')) return; // skip non-time labels + const [lh, lm] = label.split(':').map(Number); + const labelTotal = lh * 60 + lm; + const diff = Math.abs(targetTotal - labelTotal); + if (diff < smallestDiff && diff <= toleranceMinutes) { + smallestDiff = diff; + closestLabel = label; + } + }); + return closestLabel; // may be null if none within tolerance +} + // SSE Connection with Error Handling and Reconnection Logic function setupEventSource() { console.log("Setting up EventSource connection..."); @@ -632,8 +657,6 @@ function initializeChart() { // Check if Chart.js plugin is available const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined; - // Replace the simple color definitions with gradient definitions - // Inside the initializeChart function, modify the dataset configuration: return new Chart(ctx, { type: 'line', @@ -680,7 +703,7 @@ function initializeChart() { }, scales: { x: { - display: false, + display: true, ticks: { maxTicksLimit: 8, // Limit number of x-axis labels maxRotation: 0, // Don't rotate labels @@ -736,50 +759,73 @@ function initializeChart() { }, plugins: { tooltip: { + // ADD THIS TOOLTIP CONFIGURATION HERE callbacks: { label: function (context) { // Format tooltip values with appropriate unit const value = context.raw; return 'Hashrate: ' + formatHashrateForDisplay(value); } + }, + // New tooltip filter to prioritize block found points + filter: function (tooltipItem) { + // Show Block Found tooltips with higher priority + if (tooltipItem.dataset.label === 'Block Found') { + return true; + } + + // Check if there's a block found point at this position + const blockDataset = tooltipItem.chart.data.datasets.find(ds => ds.label === 'Block Found'); + if (blockDataset) { + const blockPoint = blockDataset.data.find(point => + point.x === tooltipItem.label); + + if (blockPoint) { + // If there's a block point at this position, don't show hashrate tooltip + return false; + } + } + + // Show other tooltips + return true; } }, legend: { display: false }, - annotation: hasAnnotationPlugin ? { - annotations: { - averageLine: { - type: 'line', - yMin: 0, - yMax: 0, - borderColor: '#ffd700', // Changed to gold color for better contrast - borderWidth: 3, // Increased from 2 to 3 - borderDash: [8, 4], // Modified dash pattern for distinction - // Add shadow effect - shadowColor: 'rgba(255, 215, 0, 0.5)', - shadowBlur: 8, - shadowOffsetX: 0, - shadowOffsetY: 0, - label: { - enabled: true, - content: '24hr Avg: 0 TH/s', - backgroundColor: 'rgba(0,0,0,0.8)', - color: '#ffd700', // Changed to match the line color - font: { - weight: 'bold', - size: 14, // Increased from 13 to 14 - family: 'var(--terminal-font)' // Use the terminal font - }, - padding: { top: 4, bottom: 4, left: 8, right: 8 }, - borderRadius: 2, - position: 'start', - // Add a subtle shadow to the label - textShadow: '0 0 5px rgba(255, 215, 0, 0.7)' + annotation: hasAnnotationPlugin ? { + annotations: { + averageLine: { + type: 'line', + yMin: 0, + yMax: 0, + borderColor: '#ffd700', // Changed to gold color for better contrast + borderWidth: 3, // Increased from 2 to 3 + borderDash: [8, 4], // Modified dash pattern for distinction + // Add shadow effect + shadowColor: 'rgba(255, 215, 0, 0.5)', + shadowBlur: 8, + shadowOffsetX: 0, + shadowOffsetY: 0, + label: { + enabled: true, + content: '24hr Avg: 0 TH/s', + backgroundColor: 'rgba(0,0,0,0.8)', + color: '#ffd700', // Changed to match the line color + font: { + weight: 'bold', + size: 14, // Increased from 13 to 14 + family: 'var(--terminal-font)' // Use the terminal font + }, + padding: { top: 4, bottom: 4, left: 8, right: 8 }, + borderRadius: 2, + position: 'start', + // Add a subtle shadow to the label + textShadow: '0 0 5px rgba(255, 215, 0, 0.7)' + } + } + } + } : {} } } - } - } : {} -} - } }); } catch (error) { console.error("Error initializing chart:", error); @@ -863,13 +909,27 @@ function checkForBlockUpdates(data) { } } -// Helper function to show congratulatory messages +// Helper function to show congratulatory messages with timestamps function showCongrats(message) { const $congrats = $("#congratsMessage"); - $congrats.text(message).fadeIn(500, function () { + + // Add timestamp to the message + const now = new Date(Date.now() + serverTimeOffset); // Use server time offset for accuracy + const timeString = now.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: true + }); + + // Format the message with the timestamp + const messageWithTimestamp = `${message} [${timeString}]`; + + // Display the message + $congrats.text(messageWithTimestamp).fadeIn(500, function () { setTimeout(function () { $congrats.fadeOut(500); - }, 900000); //15 minutes fade out + }, 900000); // 15 minutes fade out }); } @@ -879,7 +939,6 @@ function updateChartWithNormalizedData(chart, data) { console.warn("Cannot update chart - chart or data is null"); return; } - try { // Ensure we preserve block found points across updates if (!data.block_found_points && latestMetrics && latestMetrics.block_found_points) { @@ -889,8 +948,6 @@ function updateChartWithNormalizedData(chart, data) { // Always update the 24hr average line even if we don't have data points yet const avg24hr = parseFloat(data.hashrate_24hr || 0); const avg24hrUnit = data.hashrate_24hr_unit ? data.hashrate_24hr_unit.toLowerCase() : 'th/s'; - - // Normalize the average value to TH/s const normalizedAvg = normalizeHashrate(avg24hr, avg24hrUnit); if (!isNaN(normalizedAvg) && @@ -903,48 +960,71 @@ function updateChartWithNormalizedData(chart, data) { annotation.label.content = '24hr Avg: ' + normalizedAvg.toFixed(1) + ' TH/s'; } - // Update data points if we have any + // Process block found points if they exist + let blockFoundDataPoints = []; + if (data.block_found_points && data.block_found_points.length > 0) { + // Ensure block point times are formatted consistently to match chart labels + const validLabels = new Set(chart.data.labels); + + blockFoundDataPoints = data.block_found_points + .filter(point => { + const timeStr = point.time; + // If the time is in HH:MM:SS format, truncate seconds to match chart labels + const formattedTime = (timeStr.length === 8 && timeStr.indexOf(':') !== -1) + ? timeStr.substring(0, 5) + : timeStr; + const exists = validLabels.has(formattedTime); + if (!exists) { + console.warn(`Block point time ${formattedTime} not found in chart labels`); + } + return exists; + }) + .map(point => { + const val = parseFloat(point.value); + const unit = point.unit || data.hashrate_60sec_unit || 'th/s'; + const timeStr = point.time; + const formattedTime = (timeStr.length === 8 && timeStr.indexOf(':') !== -1) + ? timeStr.substring(0, 5) + : timeStr; + return { + x: formattedTime, + y: normalizeHashrate(val, unit) + }; + }); + + // Debug logging: output the current chart labels and compare each block point time + console.log("Chart labels after history update:", chart.data.labels); + blockFoundDataPoints.forEach((bp, index) => { + console.log(`Block found point ${index}: ${bp.x} vs Chart labels:`, chart.data.labels); + }); + } + + // Update data points if we have any history data if (data.arrow_history && data.arrow_history.hashrate_60sec) { + console.log("History data received:", data.arrow_history.hashrate_60sec); const historyData = data.arrow_history.hashrate_60sec; if (historyData && historyData.length > 0) { - // Store the current unit for reference const currentUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s'; - - // Create normalized data points chart.data.labels = historyData.map(item => { - // Simplify time format to just hours:minutes const timeStr = item.time; - // If format is HH:MM:SS, truncate seconds if (timeStr.length === 8 && timeStr.indexOf(':') !== -1) { return timeStr.substring(0, 5); } - // If already in HH:MM format or other format, return as is return timeStr; }); - chart.data.datasets[0].data = historyData.map(item => { const val = parseFloat(item.value); - - // If the history has unit information if (item.unit) { return normalizeHashrate(val, item.unit); } - - // Otherwise use the current unit as the baseline return normalizeHashrate(val, currentUnit); }); - - // Calculate the min and max values after normalization const values = chart.data.datasets[0].data.filter(v => !isNaN(v) && v !== null); if (values.length > 0) { const max = Math.max(...values); const min = Math.min(...values.filter(v => v > 0)) || 0; - - // Set appropriate Y-axis scale with some padding chart.options.scales.y.min = min * 0.8; chart.options.scales.y.max = max * 1.2; - - // Use appropriate tick step based on the range const range = max - min; if (range > 1000) { chart.options.scales.y.ticks.stepSize = 500; @@ -958,51 +1038,41 @@ function updateChartWithNormalizedData(chart, data) { } } } else { - // 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' }); const currentUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s'; const normalizedValue = normalizeHashrate(parseFloat(data.hashrate_60sec) || 0, currentUnit); - chart.data.labels = [currentTime]; chart.data.datasets[0].data = [normalizedValue]; } } - // Remove existing block found dataset to avoid duplicates - chart.data.datasets = chart.data.datasets.filter(dataset => dataset.label !== 'Block Found'); - - // Add block found points if available - if (data.block_found_points && data.block_found_points.length > 0) { + // Keep only the main dataset, then add the block found dataset, if any + if (chart.data.datasets.length >= 1) { + const mainDataset = chart.data.datasets[0]; + chart.data.datasets = [mainDataset]; + } + if (blockFoundDataPoints.length > 0) { const blockFoundDataset = { label: 'Block Found', - data: data.block_found_points.map(point => { - // Normalize the value using the same function as for hashrate - const val = parseFloat(point.value); - const unit = point.unit || data.hashrate_60sec_unit || 'th/s'; - return { - x: point.time, - y: normalizeHashrate(val, unit) - }; - }), + data: blockFoundDataPoints, borderColor: '#32CD32', backgroundColor: 'green', pointRadius: 9, - pointHoverRadius: 12, - pointStyle: 'rect', + pointHoverRadius: 15, + pointStyle: 'rectRot', borderWidth: 2, - showLine: false + showLine: false, + order: -10, + z: 1000 }; - - // Add the dataset to the chart chart.data.datasets.push(blockFoundDataset); } - - // Always update the chart chart.update('none'); } catch (chartError) { console.error("Error updating chart:", chartError); } + forceBlockPointsVisibility(); } // Main UI update function with hashrate normalization @@ -1015,6 +1085,10 @@ function updateUI() { try { const data = latestMetrics; + // Add debug logging here + console.log("Current block height:", data.last_block_height, + "Previous block height:", previousBlockHeight); + // Check for block updates if (previousBlockHeight !== null && data.last_block_height !== previousBlockHeight) { // Block found, update the chart @@ -1110,21 +1184,26 @@ function updateUI() { // Check for "next block" in any case format if (payoutText && /next\s+block/i.test(payoutText)) { - $("#est_time_to_payout").attr("style", "color: #32CD32 !important; text-shadow: 0 0 6px rgba(50, 205, 50, 0.6) !important; animation: glowPulse 1s infinite !important;"); + $("#est_time_to_payout").attr("style", "color: #32CD32 !important; text-shadow: 0 0 6px rgba(50, 205, 50, 0.6) !important; animation: pulse 1s infinite !important;"); } else { // Trim any extra whitespace const cleanText = payoutText ? payoutText.trim() : ""; - // Use an improved regex that optionally captures hours after days (with or without a comma) - const regex = /(\d+)\s*days(?:,?\s*(\d+)\s*hours?)?/i; + // Update your regex to handle hours-only format as well + const regex = /(?:(\d+)\s*days?(?:,?\s*(\d+)\s*hours?)?)|(?:(\d+)\s*hours?)/i; const match = cleanText.match(regex); - console.log("Payout text:", cleanText, "Match:", match); // Debug output - let totalDays = NaN; if (match) { - const days = parseFloat(match[1]); - const hours = match[2] ? parseFloat(match[2]) : 0; - totalDays = days + (hours / 24); + if (match[1]) { + // Format: "X days" or "X days, Y hours" + const days = parseFloat(match[1]); + const hours = match[2] ? parseFloat(match[2]) : 0; + totalDays = days + (hours / 24); + } else if (match[3]) { + // Format: "X hours" + const hours = parseFloat(match[3]); + totalDays = hours / 24; + } console.log("Total days computed:", totalDays); // Debug output } @@ -1173,8 +1252,8 @@ function updateUI() { } } function highlightBlockFound(blockHeight) { - if (!trendChart) { - console.warn("Chart not initialized"); + if (!trendChart || !trendChart.data.labels || trendChart.data.labels.length === 0) { + console.warn("Chart not initialized or no labels available"); return; } @@ -1183,12 +1262,36 @@ function highlightBlockFound(blockHeight) { return; } + // Get time in Los Angeles timezone + const now = new Date(Date.now() + serverTimeOffset); + const options = { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'America/Los_Angeles' }; + let currentTime = now.toLocaleTimeString('en-US', options); + + // Ensure time format matches EXACTLY with chart.data.labels format + if (trendChart.data.labels.length > 0) { + // If the chart has a specific time format, adapt to it + const sampleLabel = trendChart.data.labels[trendChart.data.labels.length - 1]; + if (sampleLabel.length === 5) { // HH:MM format + currentTime = currentTime.length > 5 ? currentTime.substring(0, 5) : currentTime; + } + } + + console.log("Adding block point at time:", currentTime); + + // Find the nearest matching label + const matchingLabel = findMatchingLabel(currentTime, trendChart.data.labels, 5); // 5 minutes tolerance + if (!matchingLabel) { + console.warn(`Time ${currentTime} not found in chart labels:`, trendChart.data.labels); + return; + } + // Add the block found point to the chart data const blockFoundPoint = { - time: new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }), - value: latestMetrics.hashrate_60sec, // Use the current hashrate value - unit: latestMetrics.hashrate_60sec_unit || 'th/s', // Add this to store the unit - flash: true // Mark this point to flash/pulse + time: matchingLabel, // Use the matching label + value: latestMetrics.hashrate_60sec, + unit: latestMetrics.hashrate_60sec_unit || 'th/s', + flash: true, + timestamp: new Date().toISOString() // Add a timestamp for reference }; if (!latestMetrics.block_found_points) { @@ -1209,29 +1312,230 @@ function highlightBlockFound(blockHeight) { // Flash the point by animating opacity flashBlockFoundIndicator(); + + // Call forceBlockPointsVisibility here + forceBlockPointsVisibility(); } -// Add this function to create a flashing effect + +// Add this function to create a continuous flashing effect function flashBlockFoundIndicator() { - const flashCount = 5; // Number of flashes - let count = 0; - - const flashInterval = setInterval(() => { - if (count >= flashCount * 2) { - clearInterval(flashInterval); - return; - } + // Remove the flash count limit to make it indefinite + // Store the interval ID globally so we can reference it later if needed + if (window.blockFlashInterval) { + clearInterval(window.blockFlashInterval); + } + // Set up continuous flashing with oscillating opacity and size + window.blockFlashInterval = setInterval(() => { // Toggle visibility of the dataset if (trendChart && trendChart.data.datasets.length > 1) { - const opacity = count % 2 === 0 ? 1.0 : 0.3; // Toggle between full and partial opacity - trendChart.data.datasets[1].pointBackgroundColor = `rgba(50, 205, 50, ${opacity})`; - trendChart.data.datasets[1].borderColor = `rgba(50, 205, 50, ${opacity})`; - trendChart.update('none'); - } + // Find the block dataset + const blockDataset = trendChart.data.datasets.find(ds => ds.label === 'Block Found'); + if (!blockDataset) return; - count++; - }, 300); // Flash every 300ms + // Create a pulsing effect using sine wave for smoother transition + const time = Date.now() / 1000; // Time in seconds + const opacity = 0.5 + (Math.sin(time * 3) + 1) / 2 * 0.5; // Oscillate between 0.5 and 1.0 + const size = 10 + Math.sin(time * 2) * 5; // Size oscillates between 5 and 15 + + // Update all properties + blockDataset.pointBackgroundColor = `rgba(50, 205, 50, ${opacity})`; + blockDataset.borderColor = `rgba(50, 205, 50, ${opacity})`; + blockDataset.pointRadius = size; + blockDataset.z = 9999; // Keep z-index high + blockDataset.order = -1; // Keep order priority high + + // Apply a glow effect by adjusting border properties + blockDataset.borderWidth = 3 + Math.sin(time * 4) * 2; // Border width oscillates + + // Force a chart update with minimal animation + trendChart.update('none'); + + // At the end of updateChartWithNormalizedData after chart.update('none'); + ensureBlockPointsDisplayed(); + } + }, 50); // Update very frequently for smoother animation +} + +// Fix the forceBlockPointsVisibility function to improve how it finds point coordinates +function forceBlockPointsVisibility() { + // Increase the delay to give Chart.js more time to render + setTimeout(() => { + try { + const canvas = document.getElementById('trendGraph'); + if (!canvas) return; + + canvas.parentElement.classList.add('chart-container-relative'); + + let overlay = document.getElementById('blockPointsOverlay'); + if (!overlay) { + overlay = document.createElement('div'); + overlay.id = 'blockPointsOverlay'; + overlay.style.position = 'absolute'; + overlay.style.top = '0'; + overlay.style.left = '0'; + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.pointerEvents = 'none'; + canvas.parentElement.appendChild(overlay); + } + + // Clear previous points + overlay.innerHTML = ''; + + if (!latestMetrics || !latestMetrics.block_found_points || + !latestMetrics.block_found_points.length) return; + + const chart = trendChart; + if (!chart || !chart.chartArea) return; + + // Add CSS animation if not already added + if (!document.getElementById('block-marker-animation')) { + const style = document.createElement('style'); + style.id = 'block-marker-animation'; + style.textContent = ` + @keyframes pulse-block-marker { + 0% { transform: translate(-50%, -50%) rotate(45deg) scale(1); opacity: 1; } + 50% { transform: translate(-50%, -50%) rotate(45deg) scale(1.3); opacity: 0.8; } + 100% { transform: translate(-50%, -50%) rotate(45deg) scale(1); opacity: 1; } + } + .chart-container-relative { + position: relative; + } + `; + document.head.appendChild(style); + } + + // Fixed approach: use canvas width/height and chartarea for positioning + const chartArea = chart.chartArea; + const xAxis = chart.scales.x; + const yAxis = chart.scales.y; + + // For debugging coordinates + console.log("Chart area:", chartArea); + console.log("Block points:", latestMetrics.block_found_points); + + // For each block point, create a visible marker + latestMetrics.block_found_points.forEach((point, index) => { + // Find the time value in the labels array + const timeLabel = point.time; + const labelIndex = chart.data.labels.indexOf(timeLabel); + + if (labelIndex === -1) { + console.warn(`Time "${timeLabel}" not found in chart labels:`, chart.data.labels); + return; // Skip if not found + } + + // Calculate position based on axes and chart area + const value = parseFloat(point.value); + const unit = point.unit || 'th/s'; + const normalizedValue = normalizeHashrate(value, unit); + + // Map data values to pixel positions + const xPercent = (labelIndex) / (chart.data.labels.length - 1); + const xPosition = chartArea.left + (xPercent * (chartArea.right - chartArea.left)); + + // Get Y position (must be within chart boundaries) + const yPosition = yAxis.getPixelForValue(normalizedValue); + + // Debug logging + console.log(`Block point ${index}: ${timeLabel}, Label index: ${labelIndex}, X: ${xPosition}, Y: ${yPosition}`); + + // Create a marker element + const marker = document.createElement('div'); + marker.className = 'block-found-marker'; + marker.style.position = 'absolute'; + marker.style.left = `${xPosition}px`; + marker.style.top = `${yPosition}px`; + marker.style.width = '20px'; // Larger for better visibility + marker.style.height = '20px'; + marker.style.backgroundColor = 'green'; + marker.style.borderRadius = '2px'; + marker.style.transform = 'translate(-50%, -50%) rotate(45deg)'; + marker.style.zIndex = '9999'; + marker.style.boxShadow = '0 0 12px rgba(50, 205, 50, 0.9)'; + marker.style.animation = 'pulse-block-marker 2s infinite'; + + // Add tooltip on hover + marker.title = `Block found at ${timeLabel} with hashrate ${formatHashrateForDisplay(value, unit)}`; + + // Add it to the overlay + overlay.appendChild(marker); + }); + + } catch (e) { + console.error("Error forcing block points visibility:", e); + } + }, 500); // Increased delay to ensure chart is fully rendered +} + +// Add this function to handle block point placement more intelligently +function ensureBlockPointsDisplayed() { + if (!trendChart || !latestMetrics || !latestMetrics.block_found_points) return; + if (latestMetrics.block_found_points.length === 0) return; + console.log("Ensuring block points displayed:", latestMetrics.block_found_points); + + const blockPoints = []; + const labels = trendChart.data.labels; + if (!labels || labels.length === 0) { + console.warn("No labels available for placement"); + return; + } + + // For each block point, try to find an exact or near match using our helper + latestMetrics.block_found_points.forEach(point => { + // Format the time like "HH:MM" if needed + const formattedTime = (point.time.trim().length === 8 && point.time.indexOf(':') !== -1) + ? point.time.trim().substring(0, 5) + : point.time.trim(); + + // Log the block point time and chart labels for debugging + const validLabels = new Set(labels); + console.log(`Comparing block point "${formattedTime}" with chart labels:`, [...validLabels].map(l => `"${l}"`)); + + // Use our helper to find a matching label (exact or within tolerance) + const match = findMatchingLabel(formattedTime, labels, 5); // 5 minutes tolerance + if (match) { + const val = parseFloat(point.value); + const unit = point.unit || 'th/s'; + blockPoints.push({ + x: match, + y: normalizeHashrate(val, unit) + }); + // Update the point's time for future lookups + point.originalTime = point.time; + point.time = match; + } else { + console.warn(`No close match found for ${formattedTime}`); + } + }); + + if (blockPoints.length > 0) { + console.log("Adding block points to chart:", blockPoints); + const blockDataset = { + label: 'Block Found', + data: blockPoints, + borderColor: 'rgba(50, 205, 50, 1)', // Fully opaque + backgroundColor: 'rgba(50, 205, 50, 1)', + pointRadius: 12, + pointHoverRadius: 15, + pointStyle: 'rectRot', + borderWidth: 3, + showLine: false, + order: -10, + z: 9999 + }; + + const existingIndex = trendChart.data.datasets.findIndex(ds => ds.label === 'Block Found'); + if (existingIndex !== -1) { + trendChart.data.datasets[existingIndex] = blockDataset; + } else { + trendChart.data.datasets.push(blockDataset); + } + trendChart.update('none'); + setTimeout(forceBlockPointsVisibility, 100); + } } // Update unread notifications badge in navigation @@ -1305,22 +1609,127 @@ $(document).ready(function () { } }; - // Load block found points from localStorage + // You might also want to modify your cleanupBlockFoundPoints function: + function cleanupBlockFoundPoints() { + try { + const savedPoints = localStorage.getItem('blockFoundPoints'); + if (savedPoints) { + const points = JSON.parse(savedPoints); + + // Only keep points from the last 2 hour - this is more appropriate for chart timespan + const now = Date.now(); + const twoHourAgo = now - (2 * 60 * 60 * 1000); // New 2 hour time limit + + // Filter out old or corrupted points + const validPoints = points.filter(point => { + // If we have a block timestamp, use it to filter by time + if (point && point.timestamp) { + const pointTime = new Date(point.timestamp).getTime(); + return pointTime > twoHourAgo; + } + + // Fallback validation for older points without timestamps + return point && point.time && typeof point.time === 'string' && + point.value && !isNaN(parseFloat(point.value)); + }); + + // Add timestamps to any remaining points for future filtering + const updatedPoints = validPoints.map(point => { + if (!point.timestamp) { + point.timestamp = new Date().toISOString(); + } + return point; + }); + + if (updatedPoints.length !== points.length) { + console.log(`Cleaned up ${points.length - updatedPoints.length} invalid or old block points`); + localStorage.setItem('blockFoundPoints', JSON.stringify(updatedPoints)); + } + + return updatedPoints; + } + } catch (e) { + console.error("Error cleaning up block found points:", e); + // If there's an error, clear the storage to start fresh + localStorage.removeItem('blockFoundPoints'); + } + return []; + } + + // Add this after your cleanupBlockFoundPoints function + function resetBlockPoints() { + // Clear out any old block points that might be causing issues + console.log("Resetting block points in localStorage"); + localStorage.removeItem('blockFoundPoints'); + + if (latestMetrics) { + latestMetrics.block_found_points = []; + } + } + + // Add a button to reset block points (for debugging) + function addResetBlockPointsButton() { + const button = document.createElement('button'); + button.id = 'resetBlockPointsBtn'; + button.innerText = 'Reset Block Points'; + button.style.position = 'fixed'; + button.style.bottom = '20px'; + button.style.right = '20px'; + button.style.zIndex = '1000'; + button.style.background = '#f7931a'; + button.style.color = 'black'; + button.style.border = 'none'; + button.style.padding = '8px 16px'; + button.style.borderRadius = '4px'; + button.style.cursor = 'pointer'; + button.style.display = 'none'; // Hidden by default + + button.onclick = function () { + resetBlockPoints(); + button.innerText = 'Block Points Reset'; + setTimeout(() => { + button.innerText = 'Reset Block Points'; + }, 2000); + + // Refresh the chart + if (trendChart) { + updateChartWithNormalizedData(trendChart, latestMetrics); + } + }; + + document.body.appendChild(button); + + // Show button on B + document.addEventListener('keydown', function (e) { + if (e.shiftKey && e.key === 'B') { + button.style.display = button.style.display === 'none' ? 'block' : 'none'; + } + }); + } + + // Replace your existing localStorage block points loading code with: try { - const savedBlockFoundPoints = localStorage.getItem('blockFoundPoints'); - if (savedBlockFoundPoints) { + const cleanedPoints = cleanupBlockFoundPoints(); + if (cleanedPoints.length > 0) { if (!latestMetrics) { latestMetrics = {}; } - latestMetrics.block_found_points = JSON.parse(savedBlockFoundPoints); + latestMetrics.block_found_points = cleanedPoints; + console.log(`Loaded ${cleanedPoints.length} block points from storage`); } } catch (e) { console.error("Error loading block found points from localStorage:", e); } + // Inside your document ready function, add this before the chart initialization + addResetBlockPointsButton(); + // Initialize the chart trendChart = initializeChart(); + // After initializing trendChart + setTimeout(ensureBlockPointsDisplayed, 1000); + // THIRD: If we have block found points, apply them to the chart if (latestMetrics && latestMetrics.block_found_points && latestMetrics.block_found_points.length > 0) { console.log("Initializing chart with block points:", latestMetrics.block_found_points);