mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +02:00
Update main.js
This commit is contained in:
parent
1416cf2bf5
commit
c7e0af6431
@ -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
|
// SSE Connection with Error Handling and Reconnection Logic
|
||||||
function setupEventSource() {
|
function setupEventSource() {
|
||||||
console.log("Setting up EventSource connection...");
|
console.log("Setting up EventSource connection...");
|
||||||
@ -632,8 +657,6 @@ function initializeChart() {
|
|||||||
// Check if Chart.js plugin is available
|
// Check if Chart.js plugin is available
|
||||||
const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined;
|
const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined;
|
||||||
|
|
||||||
// Replace the simple color definitions with gradient definitions
|
|
||||||
|
|
||||||
// Inside the initializeChart function, modify the dataset configuration:
|
// Inside the initializeChart function, modify the dataset configuration:
|
||||||
return new Chart(ctx, {
|
return new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -680,7 +703,7 @@ function initializeChart() {
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
display: false,
|
display: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
maxTicksLimit: 8, // Limit number of x-axis labels
|
maxTicksLimit: 8, // Limit number of x-axis labels
|
||||||
maxRotation: 0, // Don't rotate labels
|
maxRotation: 0, // Don't rotate labels
|
||||||
@ -736,50 +759,73 @@ function initializeChart() {
|
|||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
// ADD THIS TOOLTIP CONFIGURATION HERE
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: function (context) {
|
label: function (context) {
|
||||||
// Format tooltip values with appropriate unit
|
// Format tooltip values with appropriate unit
|
||||||
const value = context.raw;
|
const value = context.raw;
|
||||||
return 'Hashrate: ' + formatHashrateForDisplay(value);
|
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 },
|
legend: { display: false },
|
||||||
annotation: hasAnnotationPlugin ? {
|
annotation: hasAnnotationPlugin ? {
|
||||||
annotations: {
|
annotations: {
|
||||||
averageLine: {
|
averageLine: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
yMin: 0,
|
yMin: 0,
|
||||||
yMax: 0,
|
yMax: 0,
|
||||||
borderColor: '#ffd700', // Changed to gold color for better contrast
|
borderColor: '#ffd700', // Changed to gold color for better contrast
|
||||||
borderWidth: 3, // Increased from 2 to 3
|
borderWidth: 3, // Increased from 2 to 3
|
||||||
borderDash: [8, 4], // Modified dash pattern for distinction
|
borderDash: [8, 4], // Modified dash pattern for distinction
|
||||||
// Add shadow effect
|
// Add shadow effect
|
||||||
shadowColor: 'rgba(255, 215, 0, 0.5)',
|
shadowColor: 'rgba(255, 215, 0, 0.5)',
|
||||||
shadowBlur: 8,
|
shadowBlur: 8,
|
||||||
shadowOffsetX: 0,
|
shadowOffsetX: 0,
|
||||||
shadowOffsetY: 0,
|
shadowOffsetY: 0,
|
||||||
label: {
|
label: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
content: '24hr Avg: 0 TH/s',
|
content: '24hr Avg: 0 TH/s',
|
||||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
color: '#ffd700', // Changed to match the line color
|
color: '#ffd700', // Changed to match the line color
|
||||||
font: {
|
font: {
|
||||||
weight: 'bold',
|
weight: 'bold',
|
||||||
size: 14, // Increased from 13 to 14
|
size: 14, // Increased from 13 to 14
|
||||||
family: 'var(--terminal-font)' // Use the terminal font
|
family: 'var(--terminal-font)' // Use the terminal font
|
||||||
},
|
},
|
||||||
padding: { top: 4, bottom: 4, left: 8, right: 8 },
|
padding: { top: 4, bottom: 4, left: 8, right: 8 },
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
position: 'start',
|
position: 'start',
|
||||||
// Add a subtle shadow to the label
|
// Add a subtle shadow to the label
|
||||||
textShadow: '0 0 5px rgba(255, 215, 0, 0.7)'
|
textShadow: '0 0 5px rgba(255, 215, 0, 0.7)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} : {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} : {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error initializing chart:", 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) {
|
function showCongrats(message) {
|
||||||
const $congrats = $("#congratsMessage");
|
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 () {
|
setTimeout(function () {
|
||||||
$congrats.fadeOut(500);
|
$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");
|
console.warn("Cannot update chart - chart or data is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure we preserve block found points across updates
|
// Ensure we preserve block found points across updates
|
||||||
if (!data.block_found_points && latestMetrics && latestMetrics.block_found_points) {
|
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
|
// Always update the 24hr average line even if we don't have data points yet
|
||||||
const avg24hr = parseFloat(data.hashrate_24hr || 0);
|
const avg24hr = parseFloat(data.hashrate_24hr || 0);
|
||||||
const avg24hrUnit = data.hashrate_24hr_unit ? data.hashrate_24hr_unit.toLowerCase() : 'th/s';
|
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);
|
const normalizedAvg = normalizeHashrate(avg24hr, avg24hrUnit);
|
||||||
|
|
||||||
if (!isNaN(normalizedAvg) &&
|
if (!isNaN(normalizedAvg) &&
|
||||||
@ -903,48 +960,71 @@ function updateChartWithNormalizedData(chart, data) {
|
|||||||
annotation.label.content = '24hr Avg: ' + normalizedAvg.toFixed(1) + ' TH/s';
|
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) {
|
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;
|
const historyData = data.arrow_history.hashrate_60sec;
|
||||||
if (historyData && historyData.length > 0) {
|
if (historyData && historyData.length > 0) {
|
||||||
// Store the current unit for reference
|
|
||||||
const currentUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s';
|
const currentUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s';
|
||||||
|
|
||||||
// Create normalized data points
|
|
||||||
chart.data.labels = historyData.map(item => {
|
chart.data.labels = historyData.map(item => {
|
||||||
// Simplify time format to just hours:minutes
|
|
||||||
const timeStr = item.time;
|
const timeStr = item.time;
|
||||||
// If format is HH:MM:SS, truncate seconds
|
|
||||||
if (timeStr.length === 8 && timeStr.indexOf(':') !== -1) {
|
if (timeStr.length === 8 && timeStr.indexOf(':') !== -1) {
|
||||||
return timeStr.substring(0, 5);
|
return timeStr.substring(0, 5);
|
||||||
}
|
}
|
||||||
// If already in HH:MM format or other format, return as is
|
|
||||||
return timeStr;
|
return timeStr;
|
||||||
});
|
});
|
||||||
|
|
||||||
chart.data.datasets[0].data = historyData.map(item => {
|
chart.data.datasets[0].data = historyData.map(item => {
|
||||||
const val = parseFloat(item.value);
|
const val = parseFloat(item.value);
|
||||||
|
|
||||||
// If the history has unit information
|
|
||||||
if (item.unit) {
|
if (item.unit) {
|
||||||
return normalizeHashrate(val, item.unit);
|
return normalizeHashrate(val, item.unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise use the current unit as the baseline
|
|
||||||
return normalizeHashrate(val, currentUnit);
|
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);
|
const values = chart.data.datasets[0].data.filter(v => !isNaN(v) && v !== null);
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
const max = Math.max(...values);
|
const max = Math.max(...values);
|
||||||
const min = Math.min(...values.filter(v => v > 0)) || 0;
|
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.min = min * 0.8;
|
||||||
chart.options.scales.y.max = max * 1.2;
|
chart.options.scales.y.max = max * 1.2;
|
||||||
|
|
||||||
// Use appropriate tick step based on the range
|
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
if (range > 1000) {
|
if (range > 1000) {
|
||||||
chart.options.scales.y.ticks.stepSize = 500;
|
chart.options.scales.y.ticks.stepSize = 500;
|
||||||
@ -958,51 +1038,41 @@ function updateChartWithNormalizedData(chart, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there's no history data, create a starting point using current hashrate
|
|
||||||
if (data.hashrate_60sec) {
|
if (data.hashrate_60sec) {
|
||||||
const currentTime = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });
|
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 currentUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s';
|
||||||
const normalizedValue = normalizeHashrate(parseFloat(data.hashrate_60sec) || 0, currentUnit);
|
const normalizedValue = normalizeHashrate(parseFloat(data.hashrate_60sec) || 0, currentUnit);
|
||||||
|
|
||||||
chart.data.labels = [currentTime];
|
chart.data.labels = [currentTime];
|
||||||
chart.data.datasets[0].data = [normalizedValue];
|
chart.data.datasets[0].data = [normalizedValue];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove existing block found dataset to avoid duplicates
|
// Keep only the main dataset, then add the block found dataset, if any
|
||||||
chart.data.datasets = chart.data.datasets.filter(dataset => dataset.label !== 'Block Found');
|
if (chart.data.datasets.length >= 1) {
|
||||||
|
const mainDataset = chart.data.datasets[0];
|
||||||
// Add block found points if available
|
chart.data.datasets = [mainDataset];
|
||||||
if (data.block_found_points && data.block_found_points.length > 0) {
|
}
|
||||||
|
if (blockFoundDataPoints.length > 0) {
|
||||||
const blockFoundDataset = {
|
const blockFoundDataset = {
|
||||||
label: 'Block Found',
|
label: 'Block Found',
|
||||||
data: data.block_found_points.map(point => {
|
data: blockFoundDataPoints,
|
||||||
// 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)
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
borderColor: '#32CD32',
|
borderColor: '#32CD32',
|
||||||
backgroundColor: 'green',
|
backgroundColor: 'green',
|
||||||
pointRadius: 9,
|
pointRadius: 9,
|
||||||
pointHoverRadius: 12,
|
pointHoverRadius: 15,
|
||||||
pointStyle: 'rect',
|
pointStyle: 'rectRot',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
showLine: false
|
showLine: false,
|
||||||
|
order: -10,
|
||||||
|
z: 1000
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the dataset to the chart
|
|
||||||
chart.data.datasets.push(blockFoundDataset);
|
chart.data.datasets.push(blockFoundDataset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always update the chart
|
|
||||||
chart.update('none');
|
chart.update('none');
|
||||||
} catch (chartError) {
|
} catch (chartError) {
|
||||||
console.error("Error updating chart:", chartError);
|
console.error("Error updating chart:", chartError);
|
||||||
}
|
}
|
||||||
|
forceBlockPointsVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main UI update function with hashrate normalization
|
// Main UI update function with hashrate normalization
|
||||||
@ -1015,6 +1085,10 @@ function updateUI() {
|
|||||||
try {
|
try {
|
||||||
const data = latestMetrics;
|
const data = latestMetrics;
|
||||||
|
|
||||||
|
// Add debug logging here
|
||||||
|
console.log("Current block height:", data.last_block_height,
|
||||||
|
"Previous block height:", previousBlockHeight);
|
||||||
|
|
||||||
// Check for block updates
|
// Check for block updates
|
||||||
if (previousBlockHeight !== null && data.last_block_height !== previousBlockHeight) {
|
if (previousBlockHeight !== null && data.last_block_height !== previousBlockHeight) {
|
||||||
// Block found, update the chart
|
// Block found, update the chart
|
||||||
@ -1110,21 +1184,26 @@ function updateUI() {
|
|||||||
|
|
||||||
// Check for "next block" in any case format
|
// Check for "next block" in any case format
|
||||||
if (payoutText && /next\s+block/i.test(payoutText)) {
|
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 {
|
} else {
|
||||||
// Trim any extra whitespace
|
// Trim any extra whitespace
|
||||||
const cleanText = payoutText ? payoutText.trim() : "";
|
const cleanText = payoutText ? payoutText.trim() : "";
|
||||||
// Use an improved regex that optionally captures hours after days (with or without a comma)
|
// Update your regex to handle hours-only format as well
|
||||||
const regex = /(\d+)\s*days(?:,?\s*(\d+)\s*hours?)?/i;
|
const regex = /(?:(\d+)\s*days?(?:,?\s*(\d+)\s*hours?)?)|(?:(\d+)\s*hours?)/i;
|
||||||
const match = cleanText.match(regex);
|
const match = cleanText.match(regex);
|
||||||
|
|
||||||
console.log("Payout text:", cleanText, "Match:", match); // Debug output
|
|
||||||
|
|
||||||
let totalDays = NaN;
|
let totalDays = NaN;
|
||||||
if (match) {
|
if (match) {
|
||||||
const days = parseFloat(match[1]);
|
if (match[1]) {
|
||||||
const hours = match[2] ? parseFloat(match[2]) : 0;
|
// Format: "X days" or "X days, Y hours"
|
||||||
totalDays = days + (hours / 24);
|
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
|
console.log("Total days computed:", totalDays); // Debug output
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1173,8 +1252,8 @@ function updateUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function highlightBlockFound(blockHeight) {
|
function highlightBlockFound(blockHeight) {
|
||||||
if (!trendChart) {
|
if (!trendChart || !trendChart.data.labels || trendChart.data.labels.length === 0) {
|
||||||
console.warn("Chart not initialized");
|
console.warn("Chart not initialized or no labels available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1183,12 +1262,36 @@ function highlightBlockFound(blockHeight) {
|
|||||||
return;
|
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
|
// Add the block found point to the chart data
|
||||||
const blockFoundPoint = {
|
const blockFoundPoint = {
|
||||||
time: new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }),
|
time: matchingLabel, // Use the matching label
|
||||||
value: latestMetrics.hashrate_60sec, // Use the current hashrate value
|
value: latestMetrics.hashrate_60sec,
|
||||||
unit: latestMetrics.hashrate_60sec_unit || 'th/s', // Add this to store the unit
|
unit: latestMetrics.hashrate_60sec_unit || 'th/s',
|
||||||
flash: true // Mark this point to flash/pulse
|
flash: true,
|
||||||
|
timestamp: new Date().toISOString() // Add a timestamp for reference
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!latestMetrics.block_found_points) {
|
if (!latestMetrics.block_found_points) {
|
||||||
@ -1209,29 +1312,230 @@ function highlightBlockFound(blockHeight) {
|
|||||||
|
|
||||||
// Flash the point by animating opacity
|
// Flash the point by animating opacity
|
||||||
flashBlockFoundIndicator();
|
flashBlockFoundIndicator();
|
||||||
|
|
||||||
|
// Call forceBlockPointsVisibility here
|
||||||
|
forceBlockPointsVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this function to create a flashing effect
|
|
||||||
|
// Add this function to create a continuous flashing effect
|
||||||
function flashBlockFoundIndicator() {
|
function flashBlockFoundIndicator() {
|
||||||
const flashCount = 5; // Number of flashes
|
// Remove the flash count limit to make it indefinite
|
||||||
let count = 0;
|
// Store the interval ID globally so we can reference it later if needed
|
||||||
|
if (window.blockFlashInterval) {
|
||||||
const flashInterval = setInterval(() => {
|
clearInterval(window.blockFlashInterval);
|
||||||
if (count >= flashCount * 2) {
|
}
|
||||||
clearInterval(flashInterval);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Set up continuous flashing with oscillating opacity and size
|
||||||
|
window.blockFlashInterval = setInterval(() => {
|
||||||
// Toggle visibility of the dataset
|
// Toggle visibility of the dataset
|
||||||
if (trendChart && trendChart.data.datasets.length > 1) {
|
if (trendChart && trendChart.data.datasets.length > 1) {
|
||||||
const opacity = count % 2 === 0 ? 1.0 : 0.3; // Toggle between full and partial opacity
|
// Find the block dataset
|
||||||
trendChart.data.datasets[1].pointBackgroundColor = `rgba(50, 205, 50, ${opacity})`;
|
const blockDataset = trendChart.data.datasets.find(ds => ds.label === 'Block Found');
|
||||||
trendChart.data.datasets[1].borderColor = `rgba(50, 205, 50, ${opacity})`;
|
if (!blockDataset) return;
|
||||||
trendChart.update('none');
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
// Create a pulsing effect using sine wave for smoother transition
|
||||||
}, 300); // Flash every 300ms
|
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
|
// 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 {
|
try {
|
||||||
const savedBlockFoundPoints = localStorage.getItem('blockFoundPoints');
|
const cleanedPoints = cleanupBlockFoundPoints();
|
||||||
if (savedBlockFoundPoints) {
|
if (cleanedPoints.length > 0) {
|
||||||
if (!latestMetrics) {
|
if (!latestMetrics) {
|
||||||
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) {
|
} catch (e) {
|
||||||
console.error("Error loading block found points from localStorage:", 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
|
// Initialize the chart
|
||||||
trendChart = initializeChart();
|
trendChart = initializeChart();
|
||||||
|
|
||||||
|
// After initializing trendChart
|
||||||
|
setTimeout(ensureBlockPointsDisplayed, 1000);
|
||||||
|
|
||||||
// THIRD: If we have block found points, apply them to the chart
|
// THIRD: If we have block found points, apply them to the chart
|
||||||
if (latestMetrics && latestMetrics.block_found_points && latestMetrics.block_found_points.length > 0) {
|
if (latestMetrics && latestMetrics.block_found_points && latestMetrics.block_found_points.length > 0) {
|
||||||
console.log("Initializing chart with block points:", latestMetrics.block_found_points);
|
console.log("Initializing chart with block points:", latestMetrics.block_found_points);
|
||||||
|
Loading…
Reference in New Issue
Block a user