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

Updated the application to use a configurable timezone instead of hardcoding "America/Los_Angeles". This change impacts the dashboard, API endpoints, and worker services. Timezone is now fetched from a configuration file or environment variable, enhancing flexibility in time display. New API endpoints for available timezones and the current configured timezone have been added. The frontend now allows users to select their timezone from a dropdown menu, which is stored in local storage for future use. Timestamps in the UI have been updated to reflect the selected timezone.
897 lines
40 KiB
HTML
897 lines
40 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">
|
|
<style>
|
|
/* Added styles for configuration form */
|
|
#config-form {
|
|
display: none;
|
|
margin-top: 20px;
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
border: 1px solid #f7931a;
|
|
padding: 15px;
|
|
max-width: 600px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
box-shadow: 0 0 10px rgba(247, 147, 26, 0.5);
|
|
}
|
|
|
|
.config-title {
|
|
color: #f7931a;
|
|
font-size: 22px;
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
text-shadow: 0 0 8px rgba(247, 147, 26, 0.8);
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
color: #f7931a;
|
|
}
|
|
|
|
.form-group input {
|
|
width: 100%;
|
|
background-color: #111;
|
|
border: 1px solid #f7931a;
|
|
padding: 8px;
|
|
color: white;
|
|
font-family: 'VT323', monospace;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.form-group input:focus {
|
|
outline: none;
|
|
box-shadow: 0 0 5px #f7931a;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.btn {
|
|
background-color: #f7931a;
|
|
border: none;
|
|
color: black;
|
|
padding: 8px 15px;
|
|
font-family: 'VT323', monospace;
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
min-width: 120px;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn:hover {
|
|
background-color: #ffa642;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: #333;
|
|
color: #f7931a;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: #444;
|
|
}
|
|
|
|
/* Make skip button more mobile-friendly */
|
|
#skip-button {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
z-index: 1000;
|
|
padding: 12px 20px;
|
|
font-size: 18px;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
#skip-button {
|
|
bottom: 10px;
|
|
right: 10px;
|
|
padding: 15px 25px;
|
|
font-size: 20px; /* Larger font size for better tap targets */
|
|
border-radius: 10px;
|
|
width: auto;
|
|
}
|
|
|
|
.form-actions {
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
padding: 12px;
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
|
|
/* Tooltip styles */
|
|
.tooltip {
|
|
position: relative;
|
|
display: inline-block;
|
|
margin-left: 5px;
|
|
cursor: help;
|
|
}
|
|
|
|
.tooltip .tooltip-text {
|
|
visibility: hidden;
|
|
width: 200px;
|
|
background-color: #000;
|
|
color: #fff;
|
|
text-align: center;
|
|
border: 1px solid #f7931a;
|
|
padding: 5px;
|
|
position: absolute;
|
|
z-index: 1;
|
|
bottom: 125%;
|
|
left: 50%;
|
|
margin-left: -100px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.tooltip:hover .tooltip-text {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Form success/error message */
|
|
#form-message {
|
|
margin-top: 10px;
|
|
padding: 8px;
|
|
text-align: center;
|
|
display: none;
|
|
}
|
|
|
|
.message-success {
|
|
background-color: rgba(50, 205, 50, 0.2);
|
|
border: 1px solid #32CD32;
|
|
color: #32CD32;
|
|
}
|
|
|
|
.message-error {
|
|
background-color: rgba(255, 0, 0, 0.2);
|
|
border: 1px solid #ff0000;
|
|
color: #ff0000;
|
|
}
|
|
|
|
/* Add these styles to match the timezone dropdown with your other inputs */
|
|
.form-group select {
|
|
width: 100%;
|
|
background-color: #111;
|
|
border: 1px solid #f7931a;
|
|
padding: 8px;
|
|
color: white;
|
|
font-family: 'VT323', monospace;
|
|
font-size: 18px;
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Add a custom dropdown arrow */
|
|
.form-group select {
|
|
background-image: linear-gradient(45deg, transparent 50%, #f7931a 50%), linear-gradient(135deg, #f7931a 50%, transparent 50%);
|
|
background-position: calc(100% - 15px) 50%, calc(100% - 10px) 50%;
|
|
background-size: 5px 5px, 5px 5px;
|
|
background-repeat: no-repeat;
|
|
padding-right: 25px; /* Space for the arrow */
|
|
}
|
|
|
|
.form-group select:focus {
|
|
outline: none;
|
|
box-shadow: 0 0 5px #f7931a;
|
|
}
|
|
|
|
/* Style the option groups */
|
|
.form-group select optgroup {
|
|
background-color: #111;
|
|
color: #f7931a;
|
|
font-family: 'VT323', monospace;
|
|
}
|
|
|
|
/* Style the options */
|
|
.form-group select option {
|
|
background-color: #222;
|
|
color: white;
|
|
padding: 8px;
|
|
}
|
|
|
|
/* Improve mobile appearance */
|
|
@media (max-width: 768px) {
|
|
.form-group select {
|
|
padding: 12px;
|
|
padding-right: 30px; /* More space for the dropdown arrow */
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<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 id="form-message"></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 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>
|
|
// 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 the saveConfig function to include timezone
|
|
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 updatedConfig = {
|
|
wallet: wallet || currentConfig.wallet,
|
|
power_cost: powerCost,
|
|
power_usage: powerUsage,
|
|
timezone: timezone
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
// Replace the current loadConfig function with this improved version
|
|
function loadConfig() {
|
|
// Return a promise that resolves when the config is loaded
|
|
return new Promise((resolve, reject) => {
|
|
// Always make a fresh request to get the latest config
|
|
fetch('/api/config?nocache=' + new Date().getTime()) // Add cache-busting parameter
|
|
.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;
|
|
|
|
// After loading, always update the form fields with the 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 || "";
|
|
configLoaded = true;
|
|
resolve(currentConfig); // Resolve the promise with the config data
|
|
})
|
|
.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
|
|
};
|
|
|
|
// Still update the form with default values
|
|
document.getElementById('wallet-address').value = currentConfig.wallet || "";
|
|
document.getElementById('power-cost').value = currentConfig.power_cost || "";
|
|
document.getElementById('power-usage').value = currentConfig.power_usage || "";
|
|
resolve(currentConfig); // Resolve the promise with the default config
|
|
});
|
|
});
|
|
}
|
|
|
|
// 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";
|
|
});
|
|
});
|
|
|
|
// Save configuration
|
|
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 updatedConfig = {
|
|
wallet: wallet || (currentConfig ? currentConfig.wallet : ""),
|
|
power_cost: powerCost,
|
|
power_usage: powerUsage,
|
|
timezone: timezone
|
|
};
|
|
|
|
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();
|
|
});
|
|
}
|
|
|
|
// 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";
|
|
});
|
|
});
|
|
|
|
// Replace the current Use Defaults button event listener with this fixed version
|
|
document.getElementById('use-defaults').addEventListener('click', function () {
|
|
console.log("Use Defaults button clicked");
|
|
|
|
// Always use the hardcoded default values, not the currentConfig
|
|
const defaultWallet = "bc1py5zmrtssheq3shd8cptpl5l5m3txxr5afynyg2gyvam6w78s4dlqqnt4v9";
|
|
const defaultPowerCost = 0.0;
|
|
const defaultPowerUsage = 0.0;
|
|
|
|
console.log("Setting to default values");
|
|
|
|
// Apply the hardcoded default values to the form fields
|
|
document.getElementById('wallet-address').value = defaultWallet;
|
|
document.getElementById('power-cost').value = defaultPowerCost;
|
|
document.getElementById('power-usage').value = defaultPowerUsage;
|
|
|
|
// Show visual feedback that the button was clicked
|
|
const btn = document.getElementById('use-defaults');
|
|
const originalText = btn.textContent;
|
|
btn.textContent = "Defaults Applied";
|
|
btn.style.backgroundColor = "#32CD32";
|
|
|
|
// Reset the button after a short delay
|
|
setTimeout(function () {
|
|
btn.textContent = originalText;
|
|
btn.style.backgroundColor = "";
|
|
}, 1500);
|
|
});
|
|
|
|
// Redirect to dashboard
|
|
function redirectToDashboard() {
|
|
updateDebug("Boot sequence complete, redirecting...");
|
|
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', just redirect to dashboard
|
|
outputElement.innerHTML += "N\n\nDASHBOARD INITIALIZATION ABORTED.\n";
|
|
outputElement.innerHTML += "\nUsing default configuration values.\n";
|
|
setTimeout(redirectToDashboard, 2000);
|
|
}
|
|
} 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 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>
|