mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +02:00

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.
764 lines
38 KiB
HTML
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>
|