Enhance hashrate detection and UI updates

Updated `NotificationService` to distinguish between low and normal hashrate modes, utilizing 3-hour and 10-minute averages for detection. Improved `updateChartWithNormalizedData` in `main.js` to support localStorage persistence for hashrate state and refined mode-switching logic. Introduced `showHashrateNormalizeNotice` for user notifications regarding hashrate normalization. Updated HTML files for UI consistency, including version number and structured display of pool fees and unpaid earnings. Ensured proper chart updates and annotations for 24-hour averages.
This commit is contained in:
DJObleezy 2025-04-27 11:18:42 -07:00
parent f45ce8a1e8
commit 2f1cbd3143
5 changed files with 432 additions and 155 deletions

View File

@ -447,38 +447,71 @@ class NotificationService:
return 0.0 return 0.0
def _check_hashrate_change(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]: def _check_hashrate_change(self, current: Dict[str, Any], previous: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Check for significant hashrate changes using 10-minute average.""" """Check for significant hashrate changes using appropriate time window based on mode."""
try: try:
# Get 10min hashrate values # Check if we're in low hashrate mode
current_10min = current.get("hashrate_10min", 0) # A simple threshold approach: if hashrate_3hr is below 1 TH/s, consider it low hashrate mode
previous_10min = previous.get("hashrate_10min", 0) is_low_hashrate_mode = False
if "hashrate_3hr" in current:
current_3hr = self._parse_numeric_value(current.get("hashrate_3hr", 0))
current_3hr_unit = current.get("hashrate_3hr_unit", "TH/s").lower()
# Normalize to TH/s for comparison
if "ph/s" in current_3hr_unit:
current_3hr *= 1000
elif "gh/s" in current_3hr_unit:
current_3hr /= 1000
elif "mh/s" in current_3hr_unit:
current_3hr /= 1000000
# If hashrate is less than 3 TH/s, consider it low hashrate mode
is_low_hashrate_mode = current_3hr < 3.0
logging.debug(f"[NotificationService] Low hashrate mode: {is_low_hashrate_mode}")
# Choose the appropriate hashrate metric based on mode
if is_low_hashrate_mode:
# In low hashrate mode, use 3hr averages for more stability
current_hashrate_key = "hashrate_3hr"
previous_hashrate_key = "hashrate_3hr"
timeframe = "3hr"
else:
# In normal mode, use 10min averages for faster response
current_hashrate_key = "hashrate_10min"
previous_hashrate_key = "hashrate_10min"
timeframe = "10min"
# Get hashrate values
current_hashrate = current.get(current_hashrate_key, 0)
previous_hashrate = previous.get(previous_hashrate_key, 0)
# Log what we're comparing # Log what we're comparing
logging.debug(f"[NotificationService] Comparing 10min hashrates - current: {current_10min}, previous: {previous_10min}") logging.debug(f"[NotificationService] Comparing {timeframe} hashrates - current: {current_hashrate}, previous: {previous_hashrate}")
# Skip if values are missing # Skip if values are missing
if not current_10min or not previous_10min: if not current_hashrate or not previous_hashrate:
logging.debug("[NotificationService] Skipping hashrate check - missing values") logging.debug(f"[NotificationService] Skipping hashrate check - missing {timeframe} values")
return None return None
# Parse values consistently # Parse values consistently
current_value = self._parse_numeric_value(current_10min) current_value = self._parse_numeric_value(current_hashrate)
previous_value = self._parse_numeric_value(previous_10min) previous_value = self._parse_numeric_value(previous_hashrate)
logging.debug(f"[NotificationService] Converted 10min hashrates - current: {current_value}, previous: {previous_value}") logging.debug(f"[NotificationService] Converted {timeframe} hashrates - current: {current_value}, previous: {previous_value}")
# Skip if previous was zero (prevents division by zero) # Skip if previous was zero (prevents division by zero)
if previous_value == 0: if previous_value == 0:
logging.debug("[NotificationService] Skipping hashrate check - previous was zero") logging.debug(f"[NotificationService] Skipping hashrate check - previous {timeframe} was zero")
return None return None
# Calculate percentage change # Calculate percentage change
percent_change = ((current_value - previous_value) / previous_value) * 100 percent_change = ((current_value - previous_value) / previous_value) * 100
logging.debug(f"[NotificationService] 10min hashrate change: {percent_change:.1f}%") logging.debug(f"[NotificationService] {timeframe} hashrate change: {percent_change:.1f}%")
# Significant decrease # Significant decrease
if percent_change <= -SIGNIFICANT_HASHRATE_CHANGE_PERCENT: if percent_change <= -SIGNIFICANT_HASHRATE_CHANGE_PERCENT:
message = f"Significant 10min hashrate drop detected: {abs(percent_change):.1f}% decrease" message = f"Significant {timeframe} hashrate drop detected: {abs(percent_change):.1f}% decrease"
logging.info(f"[NotificationService] Generating hashrate notification: {message}") logging.info(f"[NotificationService] Generating hashrate notification: {message}")
return self.add_notification( return self.add_notification(
message, message,
@ -488,13 +521,14 @@ class NotificationService:
"previous": previous_value, "previous": previous_value,
"current": current_value, "current": current_value,
"change": percent_change, "change": percent_change,
"timeframe": "10min" "timeframe": timeframe,
"is_low_hashrate_mode": is_low_hashrate_mode
} }
) )
# Significant increase # Significant increase
elif percent_change >= SIGNIFICANT_HASHRATE_CHANGE_PERCENT: elif percent_change >= SIGNIFICANT_HASHRATE_CHANGE_PERCENT:
message = f"10min hashrate increase detected: {percent_change:.1f}% increase" message = f"{timeframe} hashrate increase detected: {percent_change:.1f}% increase"
logging.info(f"[NotificationService] Generating hashrate notification: {message}") logging.info(f"[NotificationService] Generating hashrate notification: {message}")
return self.add_notification( return self.add_notification(
message, message,
@ -504,7 +538,8 @@ class NotificationService:
"previous": previous_value, "previous": previous_value,
"current": current_value, "current": current_value,
"change": percent_change, "change": percent_change,
"timeframe": "10min" "timeframe": timeframe,
"is_low_hashrate_mode": is_low_hashrate_mode
} }
) )

View File

@ -1130,6 +1130,7 @@ function showCongrats(message) {
// Enhanced Chart Update Function to handle temporary hashrate spikes // Enhanced Chart Update Function to handle temporary hashrate spikes
// Modified the updateChartWithNormalizedData function to ensure the 24hr avg line is visible in low hashrate mode // Modified the updateChartWithNormalizedData function to ensure the 24hr avg line is visible in low hashrate mode
// Enhanced Chart Update Function with localStorage persistence
function updateChartWithNormalizedData(chart, data) { function updateChartWithNormalizedData(chart, data) {
if (!chart || !data) { if (!chart || !data) {
console.warn("Cannot update chart - chart or data is null"); console.warn("Cannot update chart - chart or data is null");
@ -1137,143 +1138,171 @@ function updateChartWithNormalizedData(chart, data) {
} }
try { try {
// Process and validate 24hr average line data // Try to load lowHashrate state from localStorage first
let normalizedAvg = 0; const storedLowHashrateState = localStorage.getItem('lowHashrateState');
try {
const avg24hr = parseFloat(data.hashrate_24hr || 0);
const avg24hrUnit = data.hashrate_24hr_unit ? data.hashrate_24hr_unit.toLowerCase() : 'th/s';
normalizedAvg = normalizeHashrate(avg24hr, avg24hrUnit);
// Sanity check - if the value seems unreasonable, log a warning // Initialize mode state by combining stored state with defaults
if (normalizedAvg > 100000) { // Extremely large value (100,000+ TH/s)
console.warn(`WARNING: 24hr avg hashrate seems unreasonably high: ${normalizedAvg} TH/s`);
console.warn(`Original value: ${avg24hr} ${avg24hrUnit}`);
}
} catch (err) {
console.error("Error processing 24hr avg hashrate:", err);
normalizedAvg = 0;
}
// Update the 24HR AVG line annotation if available
if (!isNaN(normalizedAvg) &&
chart.options.plugins.annotation &&
chart.options.plugins.annotation.annotations &&
chart.options.plugins.annotation.annotations.averageLine) {
const annotation = chart.options.plugins.annotation.annotations.averageLine;
annotation.yMin = normalizedAvg;
annotation.yMax = normalizedAvg;
// Use the formatting function already available to ensure consistent units
const formattedAvg = formatHashrateForDisplay(normalizedAvg);
annotation.label.content = '24HR AVG: ' + formattedAvg.toUpperCase();
}
// Process and validate current hashrates with better error handling
let normalizedHashrate60sec = 0;
let normalizedHashrate3hr = 0;
try {
const hashrate60sec = parseFloat(data.hashrate_60sec || 0);
const hashrate60secUnit = data.hashrate_60sec_unit ? data.hashrate_60sec_unit.toLowerCase() : 'th/s';
normalizedHashrate60sec = normalizeHashrate(hashrate60sec, hashrate60secUnit);
const hashrate3hr = parseFloat(data.hashrate_3hr || 0);
const hashrate3hrUnit = data.hashrate_3hr_unit ? data.hashrate_3hr_unit.toLowerCase() : 'th/s';
normalizedHashrate3hr = normalizeHashrate(hashrate3hr, hashrate3hrUnit);
// Check for inconsistency between 60sec and 3hr values (could indicate unit issues)
const ratioThreshold = 100; // Maximum reasonable difference
if (normalizedHashrate60sec > 0 && normalizedHashrate3hr > 0) {
const ratio = Math.max(
normalizedHashrate60sec / normalizedHashrate3hr,
normalizedHashrate3hr / normalizedHashrate60sec
);
if (ratio > ratioThreshold) {
console.warn(`WARNING: Large discrepancy between 60sec and 3hr hashrates. Possible unit error!`);
console.warn(`60sec: ${hashrate60sec} ${hashrate60secUnit}${normalizedHashrate60sec} TH/s`);
console.warn(`3hr: ${hashrate3hr} ${hashrate3hrUnit}${normalizedHashrate3hr} TH/s`);
console.warn(`Ratio: ${ratio.toFixed(2)}x difference`);
}
}
} catch (err) {
console.error("Error processing current hashrates:", err);
}
// Add persistence for mode switching with debounce
// Initialize mode state if not already present
if (!chart.lowHashrateState) { if (!chart.lowHashrateState) {
chart.lowHashrateState = { const defaultState = {
isLowHashrateMode: false, isLowHashrateMode: false,
highHashrateSpikeTime: 0, highHashrateSpikeTime: 0,
spikeCount: 0,
lowHashrateConfirmTime: 0, lowHashrateConfirmTime: 0,
modeSwitchTimeoutId: null modeSwitchTimeoutId: null,
lastModeChange: 0,
stableModePeriod: 600000
}; };
// If we have stored state, use it
if (storedLowHashrateState) {
try {
const parsedState = JSON.parse(storedLowHashrateState);
chart.lowHashrateState = {
...defaultState,
...parsedState,
// Reset any volatile state that shouldn't persist
highHashrateSpikeTime: parsedState.highHashrateSpikeTime || 0,
modeSwitchTimeoutId: null
};
console.log("Restored low hashrate mode from localStorage:", chart.lowHashrateState.isLowHashrateMode);
} catch (e) {
console.error("Error parsing stored low hashrate state:", e);
chart.lowHashrateState = defaultState;
}
} else {
chart.lowHashrateState = defaultState;
}
} }
// Choose which hashrate average to display based on device characteristics, // Get values with enhanced stability
// with hysteresis to prevent rapid mode switching
let useHashrate3hr = false; let useHashrate3hr = false;
const currentTime = Date.now(); const currentTime = Date.now();
const LOW_HASHRATE_THRESHOLD = 0.01; // TH/s const LOW_HASHRATE_THRESHOLD = 0.01; // TH/s
const HIGH_HASHRATE_THRESHOLD = 2.0; // TH/s const HIGH_HASHRATE_THRESHOLD = 20.0; // TH/s
const MODE_SWITCH_DELAY = 60000; // 60 seconds delay before switching back to normal mode after spike const MODE_SWITCH_DELAY = 120000; // Increase to 2 minutes for more stability
const CONSECUTIVE_SPIKES_THRESHOLD = 3; // Increase to require more consistent high readings
const MIN_MODE_STABILITY_TIME = 120000; // 2 minutes minimum between mode switches
// Check if we changed modes recently - enforce a minimum stability period
const timeSinceLastModeChange = currentTime - chart.lowHashrateState.lastModeChange;
const enforceStabilityPeriod = timeSinceLastModeChange < MIN_MODE_STABILITY_TIME;
// IMPORTANT: Calculate normalized hashrate values
const normalizedHashrate60sec = normalizeHashrate(data.hashrate_60sec || 0, data.hashrate_60sec_unit || 'th/s');
const normalizedHashrate3hr = normalizeHashrate(data.hashrate_3hr || 0, data.hashrate_3hr_unit || 'th/s');
const normalizedAvg = normalizeHashrate(data.hashrate_24hr || 0, data.hashrate_24hr_unit || 'th/s');
// First check if we should use 3hr data based on the stored state
useHashrate3hr = chart.lowHashrateState.isLowHashrateMode;
// Case 1: Currently in low hashrate mode // Case 1: Currently in low hashrate mode
if (chart.lowHashrateState.isLowHashrateMode) { if (chart.lowHashrateState.isLowHashrateMode) {
// If 60sec hashrate suddenly spikes above threshold // Default to staying in low hashrate mode
if (normalizedHashrate60sec >= HIGH_HASHRATE_THRESHOLD) { useHashrate3hr = true;
// Record the spike time if not already recorded
// If we're enforcing stability, don't even check for mode change
if (!enforceStabilityPeriod && normalizedHashrate60sec >= HIGH_HASHRATE_THRESHOLD) {
// Only track spikes if we aren't in stability enforcement period
if (!chart.lowHashrateState.highHashrateSpikeTime) { if (!chart.lowHashrateState.highHashrateSpikeTime) {
chart.lowHashrateState.highHashrateSpikeTime = currentTime; chart.lowHashrateState.highHashrateSpikeTime = currentTime;
console.log("High hashrate spike detected in low hashrate mode"); console.log("High hashrate spike detected in low hashrate mode");
} }
// Don't switch modes immediately - check if spike has persisted // Increment spike counter
chart.lowHashrateState.spikeCount++;
console.log(`Spike count: ${chart.lowHashrateState.spikeCount}/${CONSECUTIVE_SPIKES_THRESHOLD}`);
// Check if spikes have persisted long enough
const spikeElapsedTime = currentTime - chart.lowHashrateState.highHashrateSpikeTime; const spikeElapsedTime = currentTime - chart.lowHashrateState.highHashrateSpikeTime;
// If the spike has persisted for longer than our delay, switch to normal mode if (chart.lowHashrateState.spikeCount >= CONSECUTIVE_SPIKES_THRESHOLD &&
if (spikeElapsedTime > MODE_SWITCH_DELAY) { spikeElapsedTime > MODE_SWITCH_DELAY) {
useHashrate3hr = false; useHashrate3hr = false;
chart.lowHashrateState.isLowHashrateMode = false; chart.lowHashrateState.isLowHashrateMode = false;
chart.lowHashrateState.highHashrateSpikeTime = 0; chart.lowHashrateState.highHashrateSpikeTime = 0;
chart.lowHashrateState.spikeCount = 0;
chart.lowHashrateState.lastModeChange = currentTime;
console.log("Exiting low hashrate mode after sustained high hashrate"); console.log("Exiting low hashrate mode after sustained high hashrate");
// Save state changes to localStorage
saveLowHashrateState(chart.lowHashrateState);
} else { } else {
// Spike not persistent enough yet, stay in low hashrate mode console.log(`Remaining in low hashrate mode despite spike (waiting: ${Math.round(spikeElapsedTime / 1000)}/${MODE_SWITCH_DELAY / 1000}s, count: ${chart.lowHashrateState.spikeCount}/${CONSECUTIVE_SPIKES_THRESHOLD})`);
useHashrate3hr = true;
console.log(`Remaining in low hashrate mode despite spike (waiting: ${Math.round(spikeElapsedTime / 1000)}/${MODE_SWITCH_DELAY / 1000}s)`);
} }
} else { } else {
// Reset spike timer if hashrate dropped back down // Don't reset counters immediately on every drop - make the counter more persistent
if (chart.lowHashrateState.highHashrateSpikeTime) { if (chart.lowHashrateState.spikeCount > 0 && normalizedHashrate60sec < HIGH_HASHRATE_THRESHOLD) {
console.log("Hashrate spike ended, resetting timer"); // Don't reset immediately, use a gradual decay approach
chart.lowHashrateState.highHashrateSpikeTime = 0; if (Math.random() < 0.2) { // 20% chance to decrement counter each update
} chart.lowHashrateState.spikeCount--;
console.log("Spike counter decayed to:", chart.lowHashrateState.spikeCount);
// Continue using 3hr average in low hashrate mode // Save state changes to localStorage
useHashrate3hr = true; saveLowHashrateState(chart.lowHashrateState);
}
}
} }
} }
// Case 2: Currently in normal mode // Case 2: Currently in normal mode
else { else {
// Switch to low hashrate mode if: // Default to staying in normal mode
// 1. 60sec average is near zero (appears offline) AND useHashrate3hr = false;
// 2. 3hr average shows actual mining activity
if (normalizedHashrate60sec < LOW_HASHRATE_THRESHOLD && normalizedHashrate3hr > LOW_HASHRATE_THRESHOLD) { // Don't switch to low hashrate mode immediately if we recently switched modes
if (!enforceStabilityPeriod && normalizedHashrate60sec < LOW_HASHRATE_THRESHOLD && normalizedHashrate3hr > LOW_HASHRATE_THRESHOLD) {
// Record when low hashrate condition was first observed // Record when low hashrate condition was first observed
if (!chart.lowHashrateState.lowHashrateConfirmTime) { if (!chart.lowHashrateState.lowHashrateConfirmTime) {
chart.lowHashrateState.lowHashrateConfirmTime = currentTime; chart.lowHashrateState.lowHashrateConfirmTime = currentTime;
console.log("Low hashrate condition detected"); console.log("Low hashrate condition detected");
// Save state changes to localStorage
saveLowHashrateState(chart.lowHashrateState);
} }
// Immediately switch to low hashrate mode // Require at least 60 seconds of low hashrate before switching modes
useHashrate3hr = true; const lowHashrateTime = currentTime - chart.lowHashrateState.lowHashrateConfirmTime;
chart.lowHashrateState.isLowHashrateMode = true; if (lowHashrateTime > 60000) { // 1 minute
console.log("Entering low hashrate mode"); useHashrate3hr = true;
chart.lowHashrateState.isLowHashrateMode = true;
chart.lowHashrateState.lastModeChange = currentTime;
console.log("Entering low hashrate mode after persistent low hashrate condition");
// Save state changes to localStorage
saveLowHashrateState(chart.lowHashrateState);
} else {
console.log(`Low hashrate detected but waiting for persistence: ${Math.round(lowHashrateTime / 1000)}/60s`);
}
} else { } else {
// Reset low hashrate confirmation timer // Only reset the confirmation timer if we've been above threshold consistently
chart.lowHashrateState.lowHashrateConfirmTime = 0; if (chart.lowHashrateState.lowHashrateConfirmTime &&
useHashrate3hr = false; currentTime - chart.lowHashrateState.lowHashrateConfirmTime > 30000) { // 30 seconds above threshold
chart.lowHashrateState.lowHashrateConfirmTime = 0;
console.log("Low hashrate condition cleared after consistent normal hashrate");
// Save state changes to localStorage
saveLowHashrateState(chart.lowHashrateState);
} else if (chart.lowHashrateState.lowHashrateConfirmTime) {
console.log("Brief hashrate spike, maintaining low hashrate detection timer");
}
}
}
// Helper function to save lowHashrateState to localStorage
function saveLowHashrateState(state) {
try {
// Create a clean copy without circular references or functions
const stateToSave = {
isLowHashrateMode: state.isLowHashrateMode,
highHashrateSpikeTime: state.highHashrateSpikeTime,
spikeCount: state.spikeCount,
lowHashrateConfirmTime: state.lowHashrateConfirmTime,
lastModeChange: state.lastModeChange,
stableModePeriod: state.stableModePeriod
};
localStorage.setItem('lowHashrateState', JSON.stringify(stateToSave));
console.log("Saved low hashrate state:", state.isLowHashrateMode);
} catch (e) {
console.error("Error saving low hashrate state to localStorage:", e);
} }
} }
@ -1281,12 +1310,22 @@ function updateChartWithNormalizedData(chart, data) {
if (data.arrow_history && data.arrow_history.hashrate_60sec) { if (data.arrow_history && data.arrow_history.hashrate_60sec) {
// Validate history data // Validate history data
try { try {
console.log("History data received:", data.arrow_history.hashrate_60sec); // Log 60sec data
console.log("60sec history data received:", data.arrow_history.hashrate_60sec);
// Also log 3hr data if available
if (data.arrow_history.hashrate_3hr) {
console.log("3hr history data received:", data.arrow_history.hashrate_3hr);
} else {
console.log("3hr history data not available in API response");
}
// If we're using 3hr average, try to use that history if available // If we're using 3hr average, try to use that history if available
const historyData = useHashrate3hr && data.arrow_history.hashrate_3hr ? const historyData = useHashrate3hr && data.arrow_history.hashrate_3hr ?
data.arrow_history.hashrate_3hr : data.arrow_history.hashrate_60sec; data.arrow_history.hashrate_3hr : data.arrow_history.hashrate_60sec;
console.log("Using history data:", useHashrate3hr ? "3hr data" : "60sec data");
if (historyData && historyData.length > 0) { if (historyData && historyData.length > 0) {
// Format time labels // Format time labels
chart.data.labels = historyData.map(item => { chart.data.labels = historyData.map(item => {
@ -1395,15 +1434,52 @@ function updateChartWithNormalizedData(chart, data) {
// Set appropriate step size based on range // Set appropriate step size based on range
const range = chart.options.scales.y.max - chart.options.scales.y.min; const range = chart.options.scales.y.max - chart.options.scales.y.min;
if (range > 1000) {
chart.options.scales.y.ticks.stepSize = 500; // Calculate an appropriate stepSize that won't exceed Chart.js tick limits
} else if (range > 100) { // Aim for approximately 5-10 ticks (Chart.js recommends ~5-6 ticks for readability)
chart.options.scales.y.ticks.stepSize = 50; let stepSize;
} else if (range > 10) { const targetTicks = 6; // Target number of ticks we want to display
chart.options.scales.y.ticks.stepSize = 5;
if (range <= 0.1) {
// For very small ranges (< 0.1 TH/s)
stepSize = 0.01;
} else if (range <= 1) {
// For small ranges (0.1 - 1 TH/s)
stepSize = 0.1;
} else if (range <= 10) {
// For medium ranges (1 - 10 TH/s)
stepSize = 1;
} else if (range <= 50) {
stepSize = 5;
} else if (range <= 100) {
stepSize = 10;
} else if (range <= 500) {
stepSize = 50;
} else if (range <= 1000) {
stepSize = 100;
} else if (range <= 5000) {
stepSize = 500;
} else { } else {
chart.options.scales.y.ticks.stepSize = 1; // For very large ranges, calculate stepSize that will produce ~targetTicks ticks
stepSize = Math.ceil(range / targetTicks);
// Round to a nice number (nearest power of 10 multiple)
const magnitude = Math.pow(10, Math.floor(Math.log10(stepSize)));
stepSize = Math.ceil(stepSize / magnitude) * magnitude;
// Safety check for extremely large ranges
if (range / stepSize > 1000) {
console.warn(`Y-axis range (${range.toFixed(2)}) requires extremely large stepSize.
Adjusting to limit ticks to 1000.`);
stepSize = Math.ceil(range / 1000);
}
} }
// Set the calculated stepSize
chart.options.scales.y.ticks.stepSize = stepSize;
// Log the chosen stepSize for debugging
console.log(`Y-axis range: ${range.toFixed(2)}, using stepSize: ${stepSize}`);
} }
} else { } else {
console.warn("No valid history data items available"); console.warn("No valid history data items available");
@ -1508,6 +1584,31 @@ function updateChartWithNormalizedData(chart, data) {
chart.lowHashrateIndicator.style.display = 'none'; chart.lowHashrateIndicator.style.display = 'none';
} }
// UPDATE THE 24HR AVERAGE LINE ANNOTATION - THIS WAS MISSING
if (chart.options && chart.options.plugins && chart.options.plugins.annotation &&
chart.options.plugins.annotation.annotations && chart.options.plugins.annotation.annotations.averageLine) {
// Get current theme for styling
const theme = getCurrentTheme();
// Update the position of the average line to match the 24hr hashrate
chart.options.plugins.annotation.annotations.averageLine.yMin = normalizedAvg;
chart.options.plugins.annotation.annotations.averageLine.yMax = normalizedAvg;
// Update the annotation label
const formattedAvg = formatHashrateForDisplay(data.hashrate_24hr, data.hashrate_24hr_unit);
chart.options.plugins.annotation.annotations.averageLine.label.content =
`24HR AVG: ${formattedAvg}`;
// Set the color based on current theme
chart.options.plugins.annotation.annotations.averageLine.borderColor = theme.CHART.ANNOTATION;
chart.options.plugins.annotation.annotations.averageLine.label.color = theme.CHART.ANNOTATION;
console.log(`Updated 24hr average line: ${normalizedAvg.toFixed(2)} TH/s`);
} else {
console.warn("Chart annotation plugin not properly configured");
}
// Finally update the chart with a safe non-animating update // Finally update the chart with a safe non-animating update
chart.update('none'); chart.update('none');
} catch (chartError) { } catch (chartError) {
@ -2304,6 +2405,111 @@ function resetWalletAddressOnly() {
}); });
} }
// Function to show a helpful notification to the user about hashrate normalization
function showHashrateNormalizeNotice() {
// Only show if the notification doesn't already exist on the page
if ($("#hashrateNormalizeNotice").length === 0) {
const theme = getCurrentTheme();
// Create notification element with theme-appropriate styling
const notice = $(`
<div id="hashrateNormalizeNotice" style="
position: fixed;
bottom: 30px;
right: 30px;
background-color: rgba(0, 0, 0, 0.85);
color: ${theme.PRIMARY};
border: 1px solid ${theme.PRIMARY};
padding: 15px 20px;
border-radius: 4px;
z-index: 9999;
max-width: 300px;
font-family: 'VT323', monospace;
font-size: 16px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
">
<div style="display: flex; align-items: flex-start;">
<div style="margin-right: 10px;">
<i class="fas fa-chart-line" style="font-size: 22px;"></i>
</div>
<div>
<div style="font-weight: bold; margin-bottom: 5px; text-transform: uppercase;">Hashrate Chart Notice</div>
<div>Please wait 2-3 minutes for the chart to collect data and normalize for your hashrate pattern.</div>
</div>
</div>
<div style="text-align: right; margin-top: 10px;">
<button id="hashrateNoticeClose" style="
background: none;
border: none;
color: ${theme.PRIMARY};
cursor: pointer;
font-family: inherit;
text-decoration: underline;
">Dismiss</button>
<label style="margin-left: 10px;">
<input type="checkbox" id="dontShowAgain" style="vertical-align: middle;">
<span style="font-size: 0.8em; vertical-align: middle;">Don't show again</span>
</label>
</div>
</div>
`);
// Add to body and handle close button
$("body").append(notice);
// Handler for the close button
$("#hashrateNoticeClose").on("click", function () {
// Check if "Don't show again" is checked
if ($("#dontShowAgain").is(":checked")) {
// Remember permanently in localStorage
localStorage.setItem('hideHashrateNotice', 'true');
console.log("User chose to permanently hide hashrate notice");
} else {
// Only remember for this session
sessionStorage.setItem('hideHashrateNoticeSession', 'true');
console.log("User dismissed hashrate notice for this session");
}
// Hide and remove the notice
$("#hashrateNormalizeNotice").fadeOut(300, function () {
$(this).remove();
});
});
// Auto-hide after 60 seconds
setTimeout(function () {
if ($("#hashrateNormalizeNotice").length) {
$("#hashrateNormalizeNotice").fadeOut(500, function () {
$(this).remove();
});
}
}, 60000); // Changed to 60 seconds for better visibility
}
}
// Helper function to check if we should show the notice (call this during page initialization)
function checkAndShowHashrateNotice() {
// Check if user has permanently hidden the notice
const permanentlyHidden = localStorage.getItem('hideHashrateNotice') === 'true';
// Check if user has hidden the notice for this session
const sessionHidden = sessionStorage.getItem('hideHashrateNoticeSession') === 'true';
// Also check low hashrate mode state (to potentially show a different message)
const inLowHashrateMode = localStorage.getItem('lowHashrateState') ?
JSON.parse(localStorage.getItem('lowHashrateState')).isLowHashrateMode : false;
if (!permanentlyHidden && !sessionHidden) {
// Show the notice with a short delay to ensure the page is fully loaded
setTimeout(function () {
showHashrateNormalizeNotice();
}, 2000);
} else {
console.log("Hashrate notice will not be shown: permanently hidden = " +
permanentlyHidden + ", session hidden = " + sessionHidden);
}
}
$(document).ready(function () { $(document).ready(function () {
// Apply theme based on stored preference - moved to beginning for better initialization // Apply theme based on stored preference - moved to beginning for better initialization
try { try {
@ -2539,6 +2745,13 @@ $(document).ready(function () {
} }
}; };
// No need to create a copy of lowHashrateState here,
// as we'll load it from localStorage after chart recreation
// Save the low hashrate indicator element if it exists
const wasInLowHashrateMode = trendChart.lowHashrateState &&
trendChart.lowHashrateState.isLowHashrateMode;
// Check if we're on mobile (viewport width < 768px) // Check if we're on mobile (viewport width < 768px)
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
@ -2551,6 +2764,8 @@ $(document).ready(function () {
trendChart.destroy(); trendChart.destroy();
trendChart = initializeChart(); trendChart = initializeChart();
// The state will be automatically loaded from localStorage in updateChartWithNormalizedData
// Ensure font sizes are explicitly set to original values // Ensure font sizes are explicitly set to original values
// This is especially important for mobile // This is especially important for mobile
if (isMobile) { if (isMobile) {
@ -2673,6 +2888,33 @@ $(document).ready(function () {
// Call this function // Call this function
fixLastBlockLine(); fixLastBlockLine();
// Check if we should show the hashrate normalization notice
checkAndShowHashrateNotice();
// Also show notice when entering low hashrate mode for the first time in a session
// Track when we enter low hashrate mode to show specialized notification
const originalUpdateChartWithNormalizedData = updateChartWithNormalizedData;
window.updateChartWithNormalizedData = function (chart, data) {
const wasInLowHashrateMode = chart && chart.lowHashrateState &&
chart.lowHashrateState.isLowHashrateMode;
// Call original function
originalUpdateChartWithNormalizedData(chart, data);
// Check if we just entered low hashrate mode
if (chart && chart.lowHashrateState &&
chart.lowHashrateState.isLowHashrateMode && !wasInLowHashrateMode) {
console.log("Entered low hashrate mode - showing notification");
// Show the notice if it hasn't been permanently hidden
if (localStorage.getItem('hideHashrateNotice') !== 'true' &&
!$("#hashrateNormalizeNotice").length) {
showHashrateNormalizeNotice();
}
}
};
// Load timezone setting early // Load timezone setting early
(function loadTimezoneEarly() { (function loadTimezoneEarly() {
// First try to get from localStorage for instant access // First try to get from localStorage for instant access

View File

@ -135,7 +135,7 @@
<!-- Footer --> <!-- Footer -->
<footer class="footer text-center"> <footer class="footer text-center">
<p>Not affiliated with <a href="https://www.Ocean.xyz">Ocean.xyz</a></p> <p>Not affiliated with <a href="https://www.Ocean.xyz">Ocean.xyz</a></p>
<p>v0.8.7 - Public Beta</p> <p>v0.8.8 - Public Beta</p>
</footer> </footer>
</div> </div>

View File

@ -131,7 +131,7 @@ v.21
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="network-fee"> <label for="network-fee">
Network Fee (%) Firmware/Other Fees (%)
<span class="tooltip"> <span class="tooltip">
? ?
<span class="tooltip-text">Additional fees beyond pool fee, like Firmware fees</span> <span class="tooltip-text">Additional fees beyond pool fee, like Firmware fees</span>

View File

@ -56,15 +56,18 @@
<div class="card-header">Payout Info</div> <div class="card-header">Payout Info</div>
<div class="card-body"> <div class="card-body">
<p> <p>
<strong>Unpaid Earnings:</strong> <strong>Pool Fees:</strong>
<span id="unpaid_earnings" class="metric-value green"> <span id="pool_fees_percentage" class="metric-value">
{% if metrics and metrics.unpaid_earnings %} {% if metrics and metrics.pool_fees_percentage is defined and metrics.pool_fees_percentage is not none %}
{{ metrics.unpaid_earnings }} BTC {{ metrics.pool_fees_percentage }}%
{% if metrics.pool_fees_percentage is not none and metrics.pool_fees_percentage >= 0.9 and metrics.pool_fees_percentage <= 1.3 %}
<span class="fee-star"></span> <span class="datum-label">DATUM</span> <span class="fee-star"></span>
{% endif %}
{% else %} {% else %}
0 BTC N/A
{% endif %} {% endif %}
</span> </span>
<span id="indicator_unpaid_earnings"></span> <span id="indicator_pool_fees_percentage"></span>
</p> </p>
<p> <p>
<strong>Last Block:</strong> <strong>Last Block:</strong>
@ -85,6 +88,17 @@
</span> </span>
<span id="indicator_last_block"></span> <span id="indicator_last_block"></span>
</p> </p>
<p>
<strong>Unpaid Earnings:</strong>
<span id="unpaid_earnings" class="metric-value green">
{% if metrics and metrics.unpaid_earnings %}
{{ metrics.unpaid_earnings }} BTC
{% else %}
0 BTC
{% endif %}
</span>
<span id="indicator_unpaid_earnings"></span>
</p>
<p> <p>
<strong>Est. Time to Payout:</strong> <strong>Est. Time to Payout:</strong>
<span id="est_time_to_payout" class="metric-value yellow"> <span id="est_time_to_payout" class="metric-value yellow">
@ -92,20 +106,6 @@
</span> </span>
<span id="indicator_est_time_to_payout"></span> <span id="indicator_est_time_to_payout"></span>
</p> </p>
<p>
<strong>Pool Fees:</strong>
<span id="pool_fees_percentage" class="metric-value">
{% if metrics and metrics.pool_fees_percentage is defined and metrics.pool_fees_percentage is not none %}
{{ metrics.pool_fees_percentage }}%
{% if metrics.pool_fees_percentage is not none and metrics.pool_fees_percentage >= 0.9 and metrics.pool_fees_percentage <= 1.3 %}
<span class="fee-star"></span> <span class="datum-label">DATUM</span> <span class="fee-star"></span>
{% endif %}
{% else %}
N/A
{% endif %}
</span>
<span id="indicator_pool_fees_percentage"></span>
</p>
</div> </div>
</div> </div>
</div> </div>