mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +02:00
870 lines
39 KiB
HTML
870 lines
39 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 class="form-group">
|
|
<label for="network-fee">
|
|
Network Fee (%)
|
|
<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>
|
|
// 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 = "bc1py5zmrtssheq3shd8cptpl5l5m3txxr5afynyg2gyvam6w78s4dlqqnt4v9";
|
|
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...");
|
|
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>
|