mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-13 03:30:46 +02:00

Updated the `logCurrentStats` function to enhance the display of daily profit and unpaid earnings. Daily profit now defaults to '0.00' instead of 'CALCULATING...', and unpaid earnings are parsed as integers. Additionally, power consumption now shows '0 WATTS' instead of 'N/A W' when not available.
384 lines
14 KiB
JavaScript
384 lines
14 KiB
JavaScript
/**
|
|
* Bitcoin Mining Console Log - Real-time Mining Data Display
|
|
* Displays actual mining metrics rather than simulated logs
|
|
*/
|
|
|
|
// Message type constants
|
|
const MSG_TYPE = {
|
|
SYSTEM: 'system',
|
|
INFO: 'info',
|
|
WARNING: 'warning',
|
|
ERROR: 'error',
|
|
SUCCESS: 'success',
|
|
HASH: 'hash',
|
|
SHARE: 'share',
|
|
BLOCK: 'block',
|
|
NETWORK: 'network'
|
|
};
|
|
|
|
// Global settings and state
|
|
const consoleSettings = {
|
|
maxLines: 500, // Maximum number of lines to keep in the console
|
|
autoScroll: true, // Auto-scroll to bottom on new messages
|
|
refreshInterval: 15000, // Refresh metrics every 15 seconds
|
|
glitchProbability: 0.05 // 5% chance of text glitch effect for retro feel
|
|
};
|
|
|
|
// Cache for metrics data and tracking changes
|
|
let cachedMetrics = null;
|
|
let previousMetrics = null;
|
|
let lastBlockHeight = null;
|
|
let lastUpdateTime = null;
|
|
let logUpdateQueue = []; // Queue to store log updates
|
|
let logInterval = 2000; // Interval to process log updates (2 seconds)
|
|
|
|
// Initialize console
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
console.log('Bitcoin Mining Console initialized');
|
|
|
|
// Update clock
|
|
updateClock();
|
|
setInterval(updateClock, 1000);
|
|
|
|
// Fetch initial metrics
|
|
fetchMetrics();
|
|
|
|
// Setup event source for real-time updates
|
|
setupEventSource();
|
|
|
|
// Periodic full refresh as backup
|
|
setInterval(fetchMetrics, consoleSettings.refreshInterval);
|
|
|
|
// Process queued metric updates regularly
|
|
setInterval(processLogQueue, logInterval);
|
|
|
|
// Add layout adjustment
|
|
adjustConsoleLayout();
|
|
window.addEventListener('resize', adjustConsoleLayout);
|
|
|
|
// Add initial system message
|
|
addConsoleMessage("BITCOIN MINING CONSOLE INITIALIZED - CONNECTING TO DATA SOURCES...", MSG_TYPE.SYSTEM);
|
|
});
|
|
|
|
/**
|
|
* Format date for console display
|
|
*/
|
|
function formatDate(date) {
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const year = date.getFullYear();
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
}
|
|
|
|
/**
|
|
* Update the clock in the console header
|
|
*/
|
|
function updateClock() {
|
|
const now = new Date();
|
|
document.getElementById('current-time').textContent = formatDate(now);
|
|
}
|
|
|
|
/**
|
|
* Set up Server-Sent Events for real-time updates
|
|
*/
|
|
function setupEventSource() {
|
|
const eventSource = new EventSource('/stream');
|
|
|
|
eventSource.onmessage = function (event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === "ping" || data.type === "timeout_warning" || data.type === "timeout") {
|
|
return; // Ignore ping and timeout messages
|
|
}
|
|
|
|
// Store previous metrics for comparison
|
|
previousMetrics = cachedMetrics ? { ...cachedMetrics } : null;
|
|
|
|
// Update cached metrics
|
|
cachedMetrics = data;
|
|
|
|
// Check for significant changes and log them
|
|
processMetricChanges(previousMetrics, cachedMetrics);
|
|
|
|
// Update dashboard stats
|
|
updateDashboardStats(data);
|
|
|
|
} catch (error) {
|
|
console.error('Error processing SSE data:', error);
|
|
addConsoleMessage(`DATA STREAM ERROR: ${error.message}`, MSG_TYPE.ERROR);
|
|
}
|
|
};
|
|
|
|
eventSource.onerror = function () {
|
|
console.error('SSE connection error');
|
|
addConsoleMessage("CONNECTION ERROR: METRICS STREAM INTERRUPTED - ATTEMPTING RECONNECTION...", MSG_TYPE.ERROR);
|
|
// Reconnect after 5 seconds
|
|
setTimeout(setupEventSource, 5000);
|
|
};
|
|
|
|
// Log successful connection
|
|
addConsoleMessage("REAL-TIME DATA STREAM ESTABLISHED", MSG_TYPE.SUCCESS);
|
|
}
|
|
|
|
/**
|
|
* Fetch metrics via API
|
|
*/
|
|
function fetchMetrics() {
|
|
fetch('/api/metrics')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Store previous metrics for comparison
|
|
previousMetrics = cachedMetrics ? { ...cachedMetrics } : null;
|
|
|
|
// Update cached metrics
|
|
cachedMetrics = data;
|
|
lastUpdateTime = new Date();
|
|
|
|
// Check for significant changes and log them
|
|
processMetricChanges(previousMetrics, cachedMetrics);
|
|
|
|
// Update dashboard stats
|
|
updateDashboardStats(data);
|
|
|
|
// Log first connection
|
|
if (!previousMetrics) {
|
|
addConsoleMessage("CONNECTED TO MINING DATA SOURCE - MONITORING METRICS", MSG_TYPE.SUCCESS);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching metrics:', error);
|
|
addConsoleMessage(`METRICS FETCH ERROR: ${error.message} - RETRYING...`, MSG_TYPE.ERROR);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Process changes between old and new metrics
|
|
*/
|
|
function processMetricChanges(oldMetrics, newMetrics) {
|
|
if (!oldMetrics || !newMetrics) return;
|
|
|
|
// Check for block height change (new block found)
|
|
if (oldMetrics.block_number !== newMetrics.block_number) {
|
|
const message = `BLOCKCHAIN UPDATE: NEW BLOCK #${numberWithCommas(newMetrics.block_number)} DETECTED`;
|
|
queueLogUpdate(message, MSG_TYPE.BLOCK);
|
|
lastBlockHeight = newMetrics.block_number;
|
|
}
|
|
|
|
// Check for worker count changes
|
|
if (oldMetrics.workers_hashing !== newMetrics.workers_hashing) {
|
|
let message;
|
|
if (newMetrics.workers_hashing > oldMetrics.workers_hashing) {
|
|
const diff = newMetrics.workers_hashing - oldMetrics.workers_hashing;
|
|
message = `WORKER STATUS: ${diff} ADDITIONAL WORKER${diff > 1 ? 'S' : ''} CAME ONLINE - NOW ${newMetrics.workers_hashing} ACTIVE`;
|
|
queueLogUpdate(message, MSG_TYPE.SUCCESS);
|
|
} else {
|
|
const diff = oldMetrics.workers_hashing - newMetrics.workers_hashing;
|
|
message = `WORKER STATUS: ${diff} WORKER${diff > 1 ? 'S' : ''} WENT OFFLINE - NOW ${newMetrics.workers_hashing} ACTIVE`;
|
|
queueLogUpdate(message, MSG_TYPE.WARNING);
|
|
}
|
|
}
|
|
|
|
// Check for significant hashrate changes (>5%)
|
|
const oldHashrate = oldMetrics.hashrate_10min || oldMetrics.hashrate_3hr || 0;
|
|
const newHashrate = newMetrics.hashrate_10min || newMetrics.hashrate_3hr || 0;
|
|
const hashrateUnit = newMetrics.hashrate_10min_unit || newMetrics.hashrate_3hr_unit || 'TH/s';
|
|
|
|
if (oldHashrate > 0 && Math.abs((newHashrate - oldHashrate) / oldHashrate) > 0.05) {
|
|
const pctChange = ((newHashrate - oldHashrate) / oldHashrate * 100).toFixed(1);
|
|
const direction = newHashrate > oldHashrate ? 'INCREASE' : 'DECREASE';
|
|
const message = `HASHRATE ${direction}: ${newHashrate.toFixed(2)} ${hashrateUnit} - ${Math.abs(pctChange)}% CHANGE`;
|
|
queueLogUpdate(message, newHashrate > oldHashrate ? MSG_TYPE.SUCCESS : MSG_TYPE.INFO);
|
|
}
|
|
|
|
// Check for BTC price changes
|
|
if (Math.abs((newMetrics.btc_price - oldMetrics.btc_price) / oldMetrics.btc_price) > 0.005) {
|
|
const direction = newMetrics.btc_price > oldMetrics.btc_price ? 'UP' : 'DOWN';
|
|
const pctChange = ((newMetrics.btc_price - oldMetrics.btc_price) / oldMetrics.btc_price * 100).toFixed(2);
|
|
const message = `MARKET UPDATE: BTC ${direction} ${Math.abs(pctChange)}% - NOW $${numberWithCommas(newMetrics.btc_price.toFixed(2))}`;
|
|
queueLogUpdate(message, newMetrics.btc_price > oldMetrics.btc_price ? MSG_TYPE.SUCCESS : MSG_TYPE.INFO);
|
|
}
|
|
|
|
// Check mining profitability changes
|
|
if (newMetrics.daily_profit_usd !== oldMetrics.daily_profit_usd) {
|
|
if ((oldMetrics.daily_profit_usd < 0 && newMetrics.daily_profit_usd >= 0) ||
|
|
(oldMetrics.daily_profit_usd >= 0 && newMetrics.daily_profit_usd < 0)) {
|
|
const message = newMetrics.daily_profit_usd >= 0
|
|
? `PROFITABILITY ALERT: MINING NOW PROFITABLE AT $${newMetrics.daily_profit_usd.toFixed(2)}/DAY`
|
|
: `PROFITABILITY ALERT: MINING NOW UNPROFITABLE - LOSING $${Math.abs(newMetrics.daily_profit_usd).toFixed(2)}/DAY`;
|
|
queueLogUpdate(message, newMetrics.daily_profit_usd >= 0 ? MSG_TYPE.SUCCESS : MSG_TYPE.WARNING);
|
|
}
|
|
}
|
|
|
|
// Log difficulty changes
|
|
if (oldMetrics.difficulty && newMetrics.difficulty &&
|
|
Math.abs((newMetrics.difficulty - oldMetrics.difficulty) / oldMetrics.difficulty) > 0.001) {
|
|
const direction = newMetrics.difficulty > oldMetrics.difficulty ? 'INCREASED' : 'DECREASED';
|
|
const pctChange = ((newMetrics.difficulty - oldMetrics.difficulty) / oldMetrics.difficulty * 100).toFixed(2);
|
|
const message = `NETWORK DIFFICULTY ${direction} BY ${Math.abs(pctChange)}% - NOW ${numberWithCommas(Math.round(newMetrics.difficulty))}`;
|
|
queueLogUpdate(message, MSG_TYPE.NETWORK);
|
|
}
|
|
|
|
// Add periodic stats summary regardless of changes
|
|
if (!lastUpdateTime || (new Date() - lastUpdateTime) > 60000) { // Every minute
|
|
logCurrentStats(newMetrics);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queue a log update to be shown (prevents flooding)
|
|
*/
|
|
function queueLogUpdate(message, type = MSG_TYPE.INFO) {
|
|
logUpdateQueue.push({ message, type });
|
|
}
|
|
|
|
/**
|
|
* Process queued log updates
|
|
*/
|
|
function processLogQueue() {
|
|
if (logUpdateQueue.length > 0) {
|
|
const update = logUpdateQueue.shift(); // Get the next update
|
|
addConsoleMessage(update.message, update.type); // Display it
|
|
} else {
|
|
// If the queue is empty, log periodic stats
|
|
logCurrentStats(cachedMetrics);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log current mining stats periodically
|
|
*/
|
|
function logCurrentStats(metrics) {
|
|
if (!metrics) return;
|
|
|
|
// Define an array of possible log messages with corrected formatting
|
|
const logMessages = [
|
|
`HASHRATE: ${metrics.hashrate_60sec || metrics.hashrate_10min || metrics.hashrate_3hr || 0} ${metrics.hashrate_60sec_unit || metrics.hashrate_10min_unit || metrics.hashrate_3hr_unit || 'TH/s'}`,
|
|
`BLOCK HEIGHT: ${numberWithCommas(metrics.block_number || 0)}`,
|
|
`WORKERS ONLINE: ${metrics.workers_hashing || 0}`,
|
|
`BTC PRICE: $${numberWithCommas(parseFloat(metrics.btc_price || 0).toFixed(2))}`,
|
|
`DAILY PROFIT: $${metrics.daily_profit_usd ? metrics.daily_profit_usd.toFixed(2) : '0.00'}`,
|
|
// Fix the unpaid earnings format to display as SATS correctly
|
|
`UNPAID EARNINGS: ${numberWithCommas(parseInt(metrics.unpaid_earnings || 0))} SATS`,
|
|
`NETWORK DIFFICULTY: ${numberWithCommas(Math.round(metrics.difficulty || 0))}`,
|
|
// Fix power consumption to show 0W instead of N/AW when not available
|
|
`POWER CONSUMPTION: ${metrics.power_usage || '0'} WATTS`,
|
|
];
|
|
|
|
// Randomize the order of log messages
|
|
shuffleArray(logMessages);
|
|
|
|
// Queue the first few messages for display
|
|
logMessages.slice(0, 3).forEach(message => queueLogUpdate(message, MSG_TYPE.INFO));
|
|
|
|
// Update the last update time
|
|
lastUpdateTime = new Date();
|
|
}
|
|
|
|
/**
|
|
* Shuffle an array in place
|
|
*/
|
|
function shuffleArray(array) {
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the dashboard stats display in the footer
|
|
*/
|
|
function updateDashboardStats(data) {
|
|
if (!data) return;
|
|
|
|
// Update hashrate
|
|
const hashrate = data.hashrate_60sec || data.hashrate_10min || data.hashrate_3hr || 0;
|
|
const hashrateUnit = (data.hashrate_60sec_unit || data.hashrate_10min_unit || data.hashrate_3hr_unit || 'TH/s').toUpperCase();
|
|
document.getElementById('current-hashrate').textContent = `${hashrate} ${hashrateUnit}`;
|
|
|
|
// Update block height
|
|
document.getElementById('block-height').textContent = numberWithCommas(data.block_number || 0);
|
|
|
|
// Update workers online
|
|
document.getElementById('workers-online').textContent = data.workers_hashing || 0;
|
|
|
|
// Update BTC price
|
|
document.getElementById('btc-price').textContent = `$${numberWithCommas(parseFloat(data.btc_price || 0).toFixed(2))}`;
|
|
}
|
|
|
|
/**
|
|
* Format number with commas
|
|
*/
|
|
function numberWithCommas(x) {
|
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
/**
|
|
* Add a message to the console output
|
|
*/
|
|
function addConsoleMessage(message, type = MSG_TYPE.SYSTEM) {
|
|
const consoleOutput = document.getElementById('console-output');
|
|
const now = new Date();
|
|
const timestamp = formatDate(now);
|
|
|
|
// Create the message element
|
|
const lineElement = document.createElement('div');
|
|
lineElement.className = 'console-line';
|
|
|
|
// Add timestamp
|
|
const timestampSpan = document.createElement('span');
|
|
timestampSpan.className = 'timestamp';
|
|
timestampSpan.textContent = `[${timestamp}] `;
|
|
lineElement.appendChild(timestampSpan);
|
|
|
|
// Add message with appropriate class based on type
|
|
const messageSpan = document.createElement('span');
|
|
messageSpan.className = type;
|
|
messageSpan.textContent = message;
|
|
|
|
// Apply glitch effect occasionally for retro aesthetic
|
|
if (Math.random() < consoleSettings.glitchProbability) {
|
|
messageSpan.classList.add('glitch');
|
|
messageSpan.setAttribute('data-text', message);
|
|
}
|
|
|
|
lineElement.appendChild(messageSpan);
|
|
|
|
// Add to console
|
|
consoleOutput.appendChild(lineElement);
|
|
|
|
// Limit the number of lines in the console
|
|
while (consoleOutput.children.length > consoleSettings.maxLines) {
|
|
consoleOutput.removeChild(consoleOutput.firstChild);
|
|
}
|
|
|
|
// Auto-scroll to bottom
|
|
if (consoleSettings.autoScroll) {
|
|
const consoleWrapper = document.querySelector('.console-wrapper');
|
|
consoleWrapper.scrollTop = consoleWrapper.scrollHeight;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust console layout to ensure proper proportions
|
|
*/
|
|
function adjustConsoleLayout() {
|
|
const container = document.querySelector('.console-container');
|
|
const header = document.querySelector('.console-header');
|
|
const stats = document.querySelector('.console-stats');
|
|
const wrapper = document.querySelector('.console-wrapper');
|
|
|
|
if (container && header && stats && wrapper) {
|
|
// Calculate the proper height for wrapper
|
|
const containerHeight = container.clientHeight;
|
|
const headerHeight = header.clientHeight;
|
|
const statsHeight = stats.clientHeight;
|
|
|
|
// Set the wrapper height to fill the space between header and stats
|
|
const wrapperHeight = containerHeight - headerHeight - statsHeight;
|
|
wrapper.style.height = `${Math.max(wrapperHeight, 150)}px`; // Min height of 150px
|
|
}
|
|
} |