custom-ocean.xyz-dashboard/templates/boot.html
DJObleezy 2f1cbd3143 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.
2025-04-27 11:18:42 -07:00

764 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ocean.xyz Pool Miner - Initializing...</title>
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/boot.css">
<link rel="stylesheet" href="/static/css/theme-toggle.css">
<!-- Add Theme JS -->
<script src="/static/js/theme.js"></script>
<!-- Add Boot Sequence Bypass Logic -->
<script>
// Check if we should bypass boot sequence and go directly to dashboard
(function () {
// Only try to bypass if we've booted before
const hasBootedBefore = localStorage.getItem('hasCompletedBootSequence') === 'true';
if (hasBootedBefore) {
console.log("Boot sequence previously completed, checking configuration...");
// Check if configuration exists and is valid
fetch('/api/config?nocache=' + new Date().getTime())
.then(response => {
if (!response.ok) {
throw new Error('Configuration check failed');
}
return response.json();
})
.then(config => {
// Check if configuration has a valid wallet (not default/empty)
const isConfigured = config.wallet &&
config.wallet !== "yourwallethere" &&
config.wallet.trim() !== "";
if (isConfigured) {
console.log("Valid configuration found, bypassing boot sequence");
window.location.href = window.location.origin + "/dashboard";
} else {
console.log("Configuration invalid or incomplete, showing boot sequence");
// Continue with normal boot sequence
}
})
.catch(error => {
console.error("Error checking configuration:", error);
// On error, continue with boot sequence
});
}
})();
</script>
</head>
<body>
<script>
// Add underwater effects for DeepSea theme
document.addEventListener('DOMContentLoaded', function () {
// Check if DeepSea theme is active
if (localStorage.getItem('useDeepSeaTheme') === 'true') {
// Create underwater light rays
const rays = document.createElement('div');
rays.className = 'underwater-rays';
document.body.appendChild(rays);
// Create digital noise
const noise = document.createElement('div');
noise.className = 'digital-noise';
document.body.appendChild(noise);
}
});
</script>
<!-- Theme toggle button (new) -->
<!--<button id="themeToggle" class="theme-toggle-btn">
<span>Toggle Theme</span>
</button>-->
<button id="skip-button">SKIP</button>
<div id="debug-info"></div>
<div id="loading-message">Loading mining data...</div>
<div id="bitcoin-logo">
██████╗ ████████╗ ██████╗ ██████╗ ███████╗
██╔══██╗╚══██╔══╝██╔════╝ ██╔═══██╗██╔════╝
██████╔╝ ██║ ██║ ██║ ██║███████╗
██╔══██╗ ██║ ██║ ██║ ██║╚════██║
██████╔╝ ██║ ╚██████╗ ╚██████╔╝███████║
╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
v.21
</div>
<div id="terminal">
<div id="terminal-content">
<span id="output"></span><span class="cursor"></span>
<span id="prompt-container">
<span id="prompt-text">
Initialize mining dashboard? [Y/N]:
<span class="prompt-cursor"></span>
<input type="text" id="user-input" maxlength="1" autocomplete="off" spellcheck="false" autofocus style="font-size: 16px; font-weight: bold;">
</span>
</span>
</div>
</div>
<!-- Configuration Form -->
<div id="config-form">
<div class="config-title">MINING CONFIGURATION</div>
<div class="form-group">
<label for="wallet-address">
Bitcoin Wallet Address
<span class="tooltip">
?
<span class="tooltip-text">Your Ocean.xyz pool mining address</span>
</span>
</label>
<input type="text" id="wallet-address" placeholder="bc1..." value="">
</div>
<div class="form-group">
<label for="power-cost">
Power Cost ($/kWh)
<span class="tooltip">
?
<span class="tooltip-text">Your electricity cost per kilowatt-hour</span>
</span>
</label>
<input type="number" id="power-cost" step="0.01" min="0" placeholder="0.12" value="">
</div>
<div class="form-group">
<label for="power-usage">
Power Usage (Watts)
<span class="tooltip">
?
<span class="tooltip-text">Total power consumption of your mining equipment</span>
</span>
</label>
<input type="number" id="power-usage" step="50" min="0" placeholder="13450" value="">
</div>
<div class="form-group">
<label for="network-fee">
Firmware/Other Fees (%)
<span class="tooltip">
?
<span class="tooltip-text">Additional fees beyond pool fee, like Firmware fees</span>
</span>
</label>
<input type="number" id="network-fee" step="0.1" min="0" max="10" placeholder="0.0" value="">
</div>
<div class="form-group">
<label for="timezone">
Timezone
<span class="tooltip">
?
<span class="tooltip-text">Your local timezone for displaying time information</span>
</span>
</label>
<select id="timezone" class="form-control">
<optgroup label="Common Timezones">
<option value="America/Los_Angeles">Los Angeles (Pacific Time)</option>
<option value="America/Denver">Denver (Mountain Time)</option>
<option value="America/Chicago">Chicago (Central Time)</option>
<option value="America/New_York">New York (Eastern Time)</option>
<option value="Europe/London">London (GMT/BST)</option>
<option value="Europe/Paris">Paris (Central European Time)</option>
<option value="Asia/Tokyo">Tokyo (Japan Standard Time)</option>
<option value="Australia/Sydney">Sydney (Australian Eastern Time)</option>
</optgroup>
<optgroup label="Other Timezones" id="other-timezones">
<!-- Will be populated by JavaScript -->
</optgroup>
</select>
</div>
<div id="form-message"></div>
<div class="form-actions">
<button class="btn btn-secondary" id="use-defaults">Use Defaults</button>
<button class="btn" id="save-config">Save & Continue</button>
</div>
</div>
<script>
// Theme toggle initialization
document.addEventListener('DOMContentLoaded', function () {
// Initialize theme toggle button based on current theme
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
// Check current theme
const isDeepSea = localStorage.getItem('useDeepSeaTheme') === 'true';
// Update button style based on theme
if (isDeepSea) {
document.body.classList.add('deepsea-theme');
themeToggle.style.borderColor = '#0088cc';
themeToggle.style.color = '#0088cc';
} else {
document.body.classList.remove('deepsea-theme');
themeToggle.style.borderColor = '#f2a900';
themeToggle.style.color = '#f2a900';
}
// Add click event listener
themeToggle.addEventListener('click', function () {
toggleTheme(); // This will now trigger a page refresh
});
}
// Update terminal colors based on theme (boot.html specific)
function updateTerminalColors() {
const isDeepSeaTheme = localStorage.getItem('useDeepSeaTheme') === 'true';
if (isDeepSeaTheme) {
document.body.classList.add('deepsea-theme');
} else {
document.body.classList.remove('deepsea-theme');
}
}
// Initialize terminal colors
updateTerminalColors();
});
// Add a function to populate all available timezones
function populateTimezones() {
const otherTimezones = document.getElementById('other-timezones');
// Common timezone areas to include
const commonAreas = [
'Africa', 'America', 'Antarctica', 'Asia', 'Atlantic',
'Australia', 'Europe', 'Indian', 'Pacific'
];
// Fetch the list of available timezones
fetch('/api/available_timezones')
.then(response => response.json())
.then(data => {
if (!data.timezones || !Array.isArray(data.timezones)) {
console.error('Invalid timezone data received');
return;
}
// Sort timezones and filter to include only common areas
const sortedTimezones = data.timezones
.filter(tz => commonAreas.some(area => tz.startsWith(area + '/')))
.sort();
// Add options for each timezone (excluding those already in common list)
const commonOptions = Array.from(document.querySelectorAll('#timezone optgroup:first-child option'))
.map(opt => opt.value);
sortedTimezones.forEach(tz => {
if (!commonOptions.includes(tz)) {
const option = document.createElement('option');
option.value = tz;
option.textContent = tz.replace('_', ' ');
otherTimezones.appendChild(option);
}
});
})
.catch(error => console.error('Error fetching timezones:', error));
}
// Call this when the page loads
document.addEventListener('DOMContentLoaded', populateTimezones);
// Load the current timezone from configuration
function loadTimezoneFromConfig() {
if (currentConfig && currentConfig.timezone) {
const timezoneSelect = document.getElementById('timezone');
// First, check if the option exists
let optionExists = false;
for (let i = 0; i < timezoneSelect.options.length; i++) {
if (timezoneSelect.options[i].value === currentConfig.timezone) {
timezoneSelect.selectedIndex = i;
optionExists = true;
break;
}
}
// If the option doesn't exist yet (might be in the 'other' group being loaded)
// set a data attribute to select it when options are loaded
if (!optionExists) {
timezoneSelect.setAttribute('data-select-value', currentConfig.timezone);
}
}
}
// Call this after loading config
loadConfig().then(() => {
loadTimezoneFromConfig();
});
// Update saveConfig to include network fee
function saveConfig() {
const wallet = document.getElementById('wallet-address').value.trim();
const powerCost = parseFloat(document.getElementById('power-cost').value) || 0;
const powerUsage = parseFloat(document.getElementById('power-usage').value) || 0;
const timezone = document.getElementById('timezone').value;
const networkFee = parseFloat(document.getElementById('network-fee').value) || 0;
const updatedConfig = {
wallet: wallet || (currentConfig ? currentConfig.wallet : ""),
power_cost: powerCost,
power_usage: powerUsage,
timezone: timezone,
network_fee: networkFee
};
return fetch('/api/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedConfig)
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to save configuration');
}
return response.json();
});
}
// Debug logging
function updateDebug(message) {
document.getElementById('debug-info').textContent = message;
console.log(message);
}
// Format numbers with commas
function numberWithCommas(x) {
if (x == null) return "N/A";
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// Global variables
let bootMessages = [];
let dashboardData = null;
let outputElement = document.getElementById('output');
const bitcoinLogo = document.getElementById('bitcoin-logo');
const skipButton = document.getElementById('skip-button');
const loadingMessage = document.getElementById('loading-message');
const promptContainer = document.getElementById('prompt-container');
const userInput = document.getElementById('user-input');
const configForm = document.getElementById('config-form');
let messageIndex = 0;
let timeoutId = null;
let waitingForUserInput = false;
let bootComplete = false;
let configLoaded = false;
let currentConfig = {
wallet: "yourwallethere",
power_cost: 0.0,
power_usage: 0.0
};
// Update loadConfig function to include network fee
function loadConfig() {
return new Promise((resolve, reject) => {
fetch('/api/config?nocache=' + new Date().getTime())
.then(response => {
if (!response.ok) {
throw new Error('Failed to load configuration: ' + response.statusText);
}
return response.json();
})
.then(data => {
console.log("Loaded configuration:", data);
currentConfig = data;
// Update form fields with latest values
document.getElementById('wallet-address').value = currentConfig.wallet || "";
document.getElementById('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || "";
document.getElementById('network-fee').value = currentConfig.network_fee || "";
configLoaded = true;
resolve(currentConfig);
})
.catch(err => {
console.error("Error loading config:", err);
// Use default values if loading fails
currentConfig = {
wallet: "yourwallethere",
power_cost: 0.0,
power_usage: 0.0,
network_fee: 0.0
};
document.getElementById('wallet-address').value = currentConfig.wallet || "";
document.getElementById('power-cost').value = currentConfig.power_cost || "";
document.getElementById('power-usage').value = currentConfig.power_usage || "";
document.getElementById('network-fee').value = currentConfig.network_fee || "";
resolve(currentConfig);
});
});
}
// Also update the save button event handler to reload the config after saving
document.getElementById('save-config').addEventListener('click', function () {
const messageElement = document.getElementById('form-message');
messageElement.style.display = 'block';
saveConfig()
.then(data => {
console.log("Configuration saved:", data);
messageElement.textContent = "Configuration saved successfully!";
messageElement.className = "message-success";
// Update currentConfig with the saved values
currentConfig = data.config || data;
setTimeout(redirectToDashboard, 1000);
})
.catch(error => {
console.error("Error saving configuration:", error);
messageElement.textContent = "Error saving configuration. Please try again.";
messageElement.className = "message-error";
});
});
// Safety timeout: redirect after 120 seconds if boot not complete
window.addEventListener('load', function () {
setTimeout(function () {
if (!bootComplete && !waitingForUserInput) {
console.warn("Safety timeout reached - redirecting to dashboard");
redirectToDashboard();
}
}, 120000);
});
// Configuration form event listeners
document.getElementById('save-config').addEventListener('click', function () {
const messageElement = document.getElementById('form-message');
messageElement.style.display = 'block';
saveConfig()
.then(data => {
console.log("Configuration saved:", data);
messageElement.textContent = "Configuration saved successfully!";
messageElement.className = "message-success";
setTimeout(redirectToDashboard, 1000);
})
.catch(error => {
console.error("Error saving configuration:", error);
messageElement.textContent = "Error saving configuration. Please try again.";
messageElement.className = "message-error";
});
});
// Update Use Defaults button handler
document.getElementById('use-defaults').addEventListener('click', function () {
// Set default values including network fee
document.getElementById('wallet-address').value = "35eS5Lsqw8NCjFJ8zhp9JaEmyvLDwg6XtS";
document.getElementById('power-cost').value = 0.0;
document.getElementById('power-usage').value = 0.0;
document.getElementById('network-fee').value = 0.0;
// Visual feedback
const btn = document.getElementById('use-defaults');
const originalText = btn.textContent;
btn.textContent = "Defaults Applied";
btn.style.backgroundColor = "#32CD32";
setTimeout(function () {
btn.textContent = originalText;
btn.style.backgroundColor = "";
}, 1500);
});
// Redirect to dashboard
function redirectToDashboard() {
updateDebug("Boot sequence complete, redirecting...");
// Set localStorage flag to indicate boot sequence has been completed
localStorage.setItem('hasCompletedBootSequence', 'true');
const baseUrl = window.location.origin;
window.location.href = baseUrl + "/dashboard";
}
// Fade in Bitcoin logo
document.addEventListener('DOMContentLoaded', function () {
setTimeout(function () {
bitcoinLogo.style.visibility = 'visible';
setTimeout(function () {
bitcoinLogo.style.opacity = '1';
}, 100);
}, 500);
// Load configuration
loadConfig();
});
// Post-confirmation messages with retro typing effect
function showPostConfirmationMessages(response) {
try {
outputElement = document.getElementById('output');
if (!outputElement) {
setTimeout(redirectToDashboard, 1000);
return;
}
// Configuration form will be shown after boot sequence
if (response.toUpperCase() === 'Y') {
const yesMessages = [
{ text: "INITIALIZING DASHBOARD...\n", html: true, delay: 400 },
{ text: "Connecting to real-time data feeds...", speed: 20, delay: 300 },
{ text: "<span class='green'>CONNECTED</span>\n", html: true, delay: 400 },
{ text: "Loading blockchain validators...", speed: 15, delay: 300 },
{ text: "<span class='green'>COMPLETE</span>\n", html: true, delay: 400 },
{ text: "Starting TX fee calculation module...", speed: 15, delay: 400 },
{ text: "<span class='green'>ACTIVE</span>\n", html: true, delay: 400 },
{ text: "Verifying BTC-USD exchange rates...", speed: 15, delay: 200 },
{ text: "<span class='green'>CURRENT RATE CONFIRMED</span>\n", html: true, delay: 300 },
{ text: "Calibrating hashrate telemetry...", speed: 15, delay: 200 },
{ text: "<span class='green'>CALIBRATED</span>\n", html: true, delay: 200 },
{ text: "Loading mining configuration...", speed: 15, delay: 200 },
{ text: "<span class='green'>LOADED</span>\n", html: true, delay: 300 },
{ text: "Preparing configuration interface...", speed: 15, delay: 800 },
{ text: "<span class='green'>READY</span>\n", html: true, delay: 500 },
{ text: "\nPlease configure your mining setup or use the default values:\n", html: true, delay: 800, showConfigForm: true }
];
let msgIndex = 0;
function processNextMessage() {
if (msgIndex >= yesMessages.length) {
return;
}
const currentMessage = yesMessages[msgIndex];
if (currentMessage.showConfigForm) {
msgIndex++;
// Show configuration form
document.getElementById('config-form').style.display = 'block';
return;
}
if (currentMessage.html) {
outputElement.innerHTML += currentMessage.text;
msgIndex++;
setTimeout(processNextMessage, currentMessage.delay || 300);
} else {
let charIndex = 0;
function typeCharacter() {
if (charIndex < currentMessage.text.length) {
outputElement.innerHTML += currentMessage.text.charAt(charIndex);
charIndex++;
setTimeout(typeCharacter, currentMessage.speed || 20);
} else {
msgIndex++;
setTimeout(processNextMessage, currentMessage.delay || 300);
}
}
typeCharacter();
}
}
setTimeout(processNextMessage, 500);
} else {
// If user selects 'N', show configuration form directly without boot messages
outputElement.innerHTML += "N\n\nDASHBOARD INITIALIZATION ABORTED.\n";
outputElement.innerHTML += "\nPlease configure your mining setup:\n";
// Short pause and then show the configuration form
setTimeout(function () {
document.getElementById('config-form').style.display = 'block';
}, 1000);
}
} catch (err) {
setTimeout(redirectToDashboard, 1000);
}
}
// Handle Y/N prompt input
userInput.addEventListener('keydown', function (e) {
if (waitingForUserInput && e.key === 'Enter') {
e.preventDefault();
const response = userInput.value.toUpperCase();
if (response === 'Y' || response === 'N') {
promptContainer.style.display = 'none';
waitingForUserInput = false;
outputElement.innerHTML += response + "\n";
userInput.value = '';
showPostConfirmationMessages(response);
}
}
});
// Show the prompt
function showUserPrompt() {
promptContainer.style.display = 'inline';
waitingForUserInput = true;
document.querySelector('.cursor').style.display = 'none';
userInput.focus();
}
// Disable truncation so all text is visible
function manageTerminalContent() { }
// Retro typing effect for boot messages
function typeBootMessages() {
try {
if (!outputElement) {
outputElement = document.getElementById('output');
if (!outputElement) {
skipButton.click();
return;
}
}
if (messageIndex >= bootMessages.length) { return; }
const currentMessage = bootMessages[messageIndex];
if (currentMessage.showPrompt) {
messageIndex++;
showUserPrompt();
return;
}
if (currentMessage.html) {
outputElement.innerHTML += currentMessage.text;
messageIndex++;
timeoutId = setTimeout(typeBootMessages, currentMessage.delay || 300);
return;
}
if (!currentMessage.typingIndex) { currentMessage.typingIndex = 0; }
if (currentMessage.typingIndex < currentMessage.text.length) {
outputElement.innerHTML += currentMessage.text.charAt(currentMessage.typingIndex);
currentMessage.typingIndex++;
timeoutId = setTimeout(typeBootMessages, currentMessage.speed || 15);
} else {
messageIndex++;
timeoutId = setTimeout(typeBootMessages, currentMessage.delay || 300);
}
} catch (err) {
messageIndex++;
timeoutId = setTimeout(typeBootMessages, 500);
}
}
// Skip button: reveal configuration form only
skipButton.addEventListener('click', function () {
clearTimeout(timeoutId);
// Optionally, clear boot messages or hide elements related to boot sequence
outputElement.innerHTML = "";
// Hide any loading or prompt messages
loadingMessage.style.display = 'none';
promptContainer.style.display = 'none';
// Show the configuration form
configForm.style.display = 'block';
});
// Start the typing animation (hides loading message)
function startTyping() {
loadingMessage.style.display = 'none';
setTimeout(typeBootMessages, 150);
}
// Fallback messages (used immediately)
function setupFallbackMessages() {
bootMessages = [
{ text: "BITCOIN OS - MINING CONTROL SYSTEM - v21.000.000\n", speed: 25, delay: 300 },
{ text: "Copyright (c) 2009-2025 Satoshi Nakamoto\n", speed: 20, delay: 250 },
{ text: "All rights reserved.\n\n", speed: 25, delay: 300 },
{ text: "INITIALIZING SYSTEM...\n", speed: 25, delay: 300 },
{ text: "HARDWARE: ", speed: 25, delay: 100 },
{ text: "<span class='green'>OK</span>\n", html: true, delay: 300 },
{ text: "NETWORK: ", speed: 25, delay: 100 },
{ text: "<span class='green'>OK</span>\n", html: true, delay: 300 },
{ text: "BLOCKCHAIN: ", speed: 25, delay: 100 },
{ text: "<span class='green'>SYNCHRONIZED</span>\n", html: true, delay: 300 },
{ text: "MINING RIG: ", speed: 25, delay: 100 },
{ text: "<span class='green'>ONLINE</span>\n", html: true, delay: 300 },
{ text: "\nSystem ready. ", speed: 25, delay: 400 },
{ showPrompt: true, delay: 0 }
];
startTyping();
}
// Initialize with fallback, then try live data
setupFallbackMessages();
updateDebug("Fetching dashboard data...");
fetch('/api/metrics')
.then(response => {
if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); }
return response.json();
})
.then(data => {
dashboardData = data;
clearTimeout(timeoutId);
messageIndex = 0;
outputElement = document.getElementById('output');
outputElement.innerHTML = "";
bootMessages = [
{ text: "BITCOIN OS - MINING CONTROL SYSTEM - v21.000.000\n", speed: 25, delay: 300 },
{ text: "Copyright (c) 2009-2025 Satoshi Nakamoto & The Bitcoin Core Developers\n", speed: 20, delay: 250 },
{ text: "All rights reserved.\n\n", speed: 25, delay: 300 },
{ text: "INITIALIZING SHA-256 MINING SUBSYSTEMS...\n", speed: 25, delay: 400 },
{ text: "ASIC CLUSTER STATUS: ", speed: 15, delay: 100 },
{ text: "<span class='green'>ONLINE</span>\n", html: true, delay: 300 },
{ text: "CHIP TEMPERATURE: ", speed: 15, delay: 100 },
{ text: "<span class='green'>62°C - WITHIN OPTIMAL RANGE</span>\n", html: true, delay: 300 },
{ text: "COOLING SYSTEMS: ", speed: 15, delay: 100 },
{ text: "<span class='green'>OPERATIONAL</span>\n", html: true, delay: 300 },
{ text: "POWER SUPPLY HEALTH: ", speed: 15, delay: 100 },
{ text: "<span class='green'>98.7% - NOMINAL</span>\n", html: true, delay: 300 },
{ text: "\nCONNECTING TO BITCOIN NETWORK...\n", speed: 20, delay: 400 },
{ text: "BLOCKCHAIN SYNC STATUS: ", speed: 15, delay: 100 },
{ text: "<span class='green'>SYNCHRONIZED</span>\n", html: true, delay: 300 },
{ text: "DIFFICULTY ADJUSTMENT: ", speed: 15, delay: 100 },
{ text: "<span class='yellow'>CALCULATED</span>\n", html: true, delay: 300 },
{ text: "MEMPOOL MONITORING: ", speed: 15, delay: 100 },
{ text: "<span class='green'>ACTIVE</span>\n", html: true, delay: 300 },
{ text: "\nESTABLISHING POOL CONNECTION...\n", speed: 20, delay: 300 },
{ text: "CONNECTING TO OCEAN.XYZ...\n", speed: 20, delay: 300 },
{ text: "STRATUM PROTOCOL v2: ", speed: 15, delay: 100 },
{ text: "<span class='green'>INITIALIZED</span>\n", html: true, delay: 300 },
{ text: "POOL HASHRATE: ", speed: 15, delay: 100 },
{ text: "<span class='green'>VERIFIED</span>\n", html: true, delay: 300 },
{ text: "WORKER AUTHENTICATION: ", speed: 15, delay: 100 },
{ text: "<span class='green'>SUCCESSFUL</span>\n", html: true, delay: 300 },
{ text: "\nINITIALIZING METRICS COLLECTORS...\n", speed: 20, delay: 300 },
{ text: "HASHRATE MONITOR: ", speed: 15, delay: 100 },
{ text: "<span class='green'>ACTIVE</span>\n", html: true, delay: 300 },
{ text: "EARNINGS CALCULATOR: ", speed: 15, delay: 100 },
{ text: "<span class='green'>CALIBRATED</span>\n", html: true, delay: 300 },
{ text: "POWER USAGE TRACKING: ", speed: 15, delay: 100 },
{ text: "<span class='green'>ENABLED</span>\n", html: true, delay: 300 },
{ text: "PAYOUT THRESHOLD MONITOR: ", speed: 15, delay: 100 },
{ text: "<span class='green'>ACTIVE</span>\n", html: true, delay: 300 },
{ text: "\nCURRENT NETWORK METRICS DETECTED\n", speed: 20, delay: 300 },
{ text: "BTC PRICE: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>$" + numberWithCommas((data.btc_price || 0).toFixed(2)) + "</span>\n", html: true, delay: 300 },
{ text: "NETWORK DIFFICULTY: ", speed: 20, delay: 100 },
{ text: "<span class='white'>" + numberWithCommas(Math.round(data.difficulty || 0)) + "</span>\n", html: true, delay: 300 },
{ text: "NETWORK HASHRATE: ", speed: 20, delay: 100 },
{ text: "<span class='white'>" + (data.network_hashrate ? numberWithCommas(Math.round(data.network_hashrate)) : "N/A") + " EH/s</span>\n", html: true, delay: 300 },
{ text: "BLOCK HEIGHT: ", speed: 20, delay: 100 },
{ text: "<span class='white'>" + numberWithCommas(data.block_number || "N/A") + "</span>\n", html: true, delay: 300 },
{ text: "\nMINER PERFORMANCE DATA\n", speed: 20, delay: 300 },
{ text: "CURRENT HASHRATE: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>" + (data.hashrate_60sec || "N/A") + " " + (data.hashrate_60sec_unit || "TH/s") + "</span>\n", html: true, delay: 300 },
{ text: "24HR AVG HASHRATE: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>" + (data.hashrate_24hr || "N/A") + " " + (data.hashrate_24hr_unit || "TH/s") + "</span>\n", html: true, delay: 300 },
{ text: "ACTIVE WORKERS: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>" + (data.workers_hashing || "0") + "</span>\n", html: true, delay: 300 },
{ text: "\nFINANCIAL CALCULATIONS\n", speed: 20, delay: 300 },
{ text: "DAILY MINING REVENUE: ", speed: 20, delay: 100 },
{ text: "<span class='green'>$" + numberWithCommas((data.daily_revenue || 0).toFixed(2)) + "</span>\n", html: true, delay: 300 },
{ text: "DAILY POWER COST: ", speed: 20, delay: 100 },
{ text: "<span class='red'>$" + numberWithCommas((data.daily_power_cost || 0).toFixed(2)) + "</span>\n", html: true, delay: 300 },
{ text: "DAILY PROFIT: ", speed: 20, delay: 100 },
{ text: "<span class='green'>$" + numberWithCommas((data.daily_profit_usd || 0).toFixed(2)) + "</span>\n", html: true, delay: 300 },
{ text: "PROJECTED MONTHLY PROFIT: ", speed: 20, delay: 100 },
{ text: "<span class='green'>$" + numberWithCommas((data.monthly_profit_usd || 0).toFixed(2)) + "</span>\n", html: true, delay: 300 },
{ text: "DAILY SATOSHI YIELD: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>" + numberWithCommas(data.daily_mined_sats || 0) + " sats</span>\n", html: true, delay: 300 },
{ text: "UNPAID EARNINGS: ", speed: 20, delay: 100 },
{ text: "<span class='green'>" + (data.unpaid_earnings || "0") + " BTC</span>\n", html: true, delay: 300 },
{ text: "ESTIMATED TIME TO PAYOUT: ", speed: 20, delay: 100 },
{ text: "<span class='yellow'>" + (data.est_time_to_payout || "Unknown") + "</span>\n", html: true, delay: 300 },
{ text: "\n", speed: 25, delay: 100 },
{ text: "<span class='green'>ALL MINING PROCESSES OPERATIONAL</span>\n", html: true, delay: 400 },
{ text: "\nInitialize mining dashboard? ", speed: 25, delay: 400 },
{ showPrompt: true, delay: 0 }
];
startTyping();
})
.catch(error => {
updateDebug(`Error fetching dashboard data: ${error.message}`);
});
</script>
</body>
</html>