Skip to content

Instantly share code, notes, and snippets.

@Timonchegs
Created December 7, 2025 15:20
Show Gist options
  • Select an option

  • Save Timonchegs/6c3ceed25db972ae863e9e41503b0500 to your computer and use it in GitHub Desktop.

Select an option

Save Timonchegs/6c3ceed25db972ae863e9e41503b0500 to your computer and use it in GitHub Desktop.
блок дашборда
const express = require('express');
const socketIO = require('socket.io');
const http = require('http');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
// Конфигурация
const WEB_PORT = 8080;
// Глобальные переменные
let minerStats = {};
let totalShares = 0;
let validShares = 0;
// Статическая папка
app.use(express.static('public'));
// Главная страница
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abelcoin Mining Pool - Miner Statistics</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; background: rgba(255,255,255,0.95); border-radius: 20px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; }
.header { background: linear-gradient(90deg, #2c3e50, #4a6491); color: white; padding: 30px 40px; text-align: center; }
.header h1 { font-size: 2.5rem; margin-bottom: 10px; background: linear-gradient(45deg, #00b4db, #0083b0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.header p { font-size: 1.2rem; opacity: 0.9; }
.stats-overview { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; padding: 30px; background: #f8f9fa; }
.stat-card { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); transition: transform 0.3s ease, box-shadow 0.3s ease; }
.stat-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0,0,0,0.15); }
.stat-card h3 { color: #2c3e50; font-size: 1.1rem; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
.stat-value { font-size: 2.5rem; font-weight: bold; color: #2c3e50; line-height: 1; }
.stat-unit { font-size: 1rem; color: #7f8c8d; margin-left: 5px; }
.miners-section { padding: 30px; }
.miners-section h2 { color: #2c3e50; margin-bottom: 25px; font-size: 1.8rem; display: flex; align-items: center; gap: 10px; }
.miners-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 25px; }
.miner-card { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); border-left: 5px solid #2ecc71; transition: all 0.3s ease; }
.miner-card:hover { box-shadow: 0 10px 25px rgba(0,0,0,0.15); transform: translateX(5px); }
.miner-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #f1f2f6; }
.miner-id { font-weight: bold; color: #2c3e50; font-size: 1.3rem; font-family: 'Courier New', monospace; }
.status-online { background: #2ecc71; color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.9rem; font-weight: bold; display: flex; align-items: center; gap: 5px; }
.miner-stats { display: flex; flex-direction: column; gap: 12px; }
.stat-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }
.stat-label { color: #7f8c8d; font-weight: 500; }
.stat-value { font-weight: bold; color: #2c3e50; font-size: 1.1rem; }
.footer { background: #f8f9fa; padding: 25px 30px; text-align: center; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 0.9rem; }
.update-time { background: #2c3e50; color: white; padding: 10px 20px; border-radius: 10px; margin-top: 15px; display: inline-block; font-weight: bold; }
.no-miners { text-align: center; padding: 50px; color: #7f8c8d; font-size: 1.2rem; }
.no-miners i { font-size: 3rem; margin-bottom: 20px; color: #bdc3c7; }
.log-container { background: #1a1a1a; color: #00ff00; font-family: 'Courier New', monospace; padding: 15px; border-radius: 5px; margin-top: 20px; max-height: 300px; overflow-y: auto; }
.log-entry { padding: 5px 0; border-bottom: 1px solid #333; }
@media (max-width: 768px) { .header h1 { font-size: 2rem; } .miners-grid, .stats-overview { grid-template-columns: 1fr; } }
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-server"></i> Abelcoin Mining Pool Proxy</h1>
<p>Real-time monitoring and statistics for connected miners</p>
</div>
<div class="stats-overview">
<div class="stat-card">
<h3><i class="fas fa-users"></i> Connected Miners</h3>
<div class="stat-value" id="connected-miners">0</div>
</div>
<div class="stat-card">
<h3><i class="fas fa-cube"></i> Total Shares</h3>
<div class="stat-value" id="total-shares">0</div>
</div>
<div class="stat-card">
<h3><i class="fas fa-check-circle"></i> Valid Shares</h3>
<div class="stat-value" id="valid-shares">0</div>
</div>
<div class="stat-card">
<h3><i class="fas fa-bolt"></i> Total Hashrate</h3>
<div class="stat-value" id="hashrate">0</div>
<span class="stat-unit">H/s</span>
</div>
</div>
<div class="miners-section">
<h2><i class="fas fa-microchip"></i> Connected Miners</h2>
<div id="miners-list" class="miners-grid">
<div class="no-miners">
<i class="fas fa-plug"></i>
<p>No miners connected. Waiting for connections...</p>
</div>
</div>
</div>
<div class="log-container" id="logs-container">
<h3><i class="fas fa-terminal"></i> System Logs</h3>
<div id="logs"></div>
</div>
<div class="footer">
<p>Abelcoin Mining Pool Proxy v1.0 | Real-time Monitoring System</p>
<div class="update-time" id="update-time">Last update: Never</div>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
socket.on('connect', function() {
console.log('Socket.IO connection established');
addLogEntry({ message: 'Socket.IO connection established' });
});
socket.on('stats', function(data) {
updateStats(data);
updateMiners(data.miners || []);
});
socket.on('log', function(data) {
addLogEntry(data);
});
socket.on('disconnect', function() {
console.log('Socket.IO connection closed');
addLogEntry({ message: 'Socket.IO connection closed' });
});
function updateStats(stats) {
document.getElementById('connected-miners').textContent = stats.connectedMiners || 0;
document.getElementById('total-shares').textContent = stats.totalShares || 0;
document.getElementById('valid-shares').textContent = stats.validShares || 0;
document.getElementById('hashrate').textContent = Math.round(stats.hashrate || 0);
document.getElementById('update-time').textContent = 'Last update: ' + new Date().toLocaleTimeString();
}
function updateMiners(miners) {
const minersList = document.getElementById('miners-list');
if (miners.length === 0) {
minersList.innerHTML = '<div class="no-miners"><i class="fas fa-plug"></i><p>No miners connected. Waiting for connections...</p></div>';
return;
}
minersList.innerHTML = miners.map(miner => {
let html = '<div class="miner-card">';
html += '<div class="miner-header">';
html += '<div class="miner-id"><i class="fas fa-desktop"></i> ' + (miner.id ? miner.id.split(':')[0] : 'Unknown') + '</div>';
html += '<div class="status-online"><i class="fas fa-circle"></i> ONLINE</div>';
html += '</div>';
html += '<div class="miner-stats">';
html += '<div class="stat-row"><span class="stat-label">IP Address:</span><span class="stat-value">' + (miner.ip || 'N/A') + '</span></div>';
html += '<div class="stat-row"><span class="stat-label">Total Shares:</span><span class="stat-value">' + (miner.shares || 0) + '</span></div>';
html += '<div class="stat-row"><span class="stat-label">Valid Shares:</span><span class="stat-value">' + (miner.validShares || 0) + '</span></div>';
html += '<div class="stat-row"><span class="stat-label">Hashrate:</span><span class="stat-value">' + Math.round(miner.hashrate || 0) + ' H/s</span></div>';
html += '<div class="stat-row"><span class="stat-label">Latency:</span><span class="stat-value">' + (miner.latency ? miner.latency + 'ms' : 'N/A') + '</span></div>';
if (miner.difficulty) {
html += '<div class="stat-row"><span class="stat-label">Difficulty:</span><span class="stat-value">' + miner.difficulty + '</span></div>';
}
html += '</div></div>';
return html;
}).join('');
}
function addLogEntry(data) {
const logsContainer = document.getElementById('logs');
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.textContent = '[' + new Date().toLocaleTimeString() + '] ' + data.message;
logsContainer.appendChild(entry);
logsContainer.scrollTop = logsContainer.scrollHeight;
}
// Initial log
addLogEntry({ message: 'Page loaded. Waiting for data...' });
</script>
</body>
</html>
`);
});
// API endpoint для статистики
app.get('/api/stats', (req, res) => {
res.json({
connectedMiners: Object.keys(minerStats).length,
totalShares: totalShares,
validShares: validShares,
hashrate: calculateTotalHashrate(),
miners: Object.entries(minerStats).map(([id, stats]) => ({
id: id,
ip: stats.ip,
shares: stats.shares,
validShares: stats.validShares,
hashrate: stats.hashrate,
latency: stats.latency,
lastShareTime: stats.lastShareTime,
difficulty: stats.difficulty
}))
});
});
// Socket.IO соединения
io.on('connection', (socket) => {
console.log('Новый Socket.IO клиент подключен');
// Отправляем текущую статистику при подключении
socket.emit('stats', {
connectedMiners: Object.keys(minerStats).length,
totalShares: totalShares,
validShares: validShares,
hashrate: calculateTotalHashrate(),
miners: Object.entries(minerStats).map(([id, stats]) => ({
id: id,
ip: stats.ip,
shares: stats.shares,
validShares: stats.validShares,
hashrate: stats.hashrate,
latency: stats.latency,
lastShareTime: stats.lastShareTime,
difficulty: stats.difficulty
}))
});
socket.on('disconnect', () => {
console.log('Socket.IO клиент отключен');
});
});
// Функция расчета общего хешрейта
function calculateTotalHashrate() {
let totalHashrate = 0;
Object.values(minerStats).forEach(stats => {
if (stats.hashrate) {
totalHashrate += stats.hashrate;
}
});
return totalHashrate;
}
// Симуляция данных для тестирования (удалите в продакшене)
setInterval(() => {
// Пример: добавляем тестового майнера
if (Object.keys(minerStats).length === 0) {
const testMinerId = 'test-miner-001';
minerStats[testMinerId] = {
ip: '192.168.1.100',
shares: Math.floor(Math.random() * 100),
validShares: Math.floor(Math.random() * 80),
hashrate: Math.random() * 1000,
connectTime: Date.now() - 3600000,
lastShareTime: Date.now() - 30000,
latency: 150,
difficulty: 1000
};
totalShares = minerStats[testMinerId].shares;
validShares = minerStats[testMinerId].validShares;
}
// Рассылаем обновление всем подключенным клиентам
io.emit('stats', {
connectedMiners: Object.keys(minerStats).length,
totalShares: totalShares,
validShares: validShares,
hashrate: calculateTotalHashrate(),
miners: Object.entries(minerStats).map(([id, stats]) => ({
id: id,
ip: stats.ip,
shares: stats.shares,
validShares: stats.validShares,
hashrate: stats.hashrate,
latency: stats.latency,
lastShareTime: stats.lastShareTime,
difficulty: stats.difficulty
}))
});
// Логируем
io.emit('log', { message: 'Статистика обновлена: ' + Object.keys(minerStats).length + ' майнеров подключено' });
}, 5000);
server.listen(WEB_PORT, () => {
console.log(`HTTP сервер запущен на порту ${WEB_PORT}`);
console.log(`Статистика доступна по адресу: http://localhost:${WEB_PORT}`);
});
console.log('Панель мониторинга Abelcoin успешно запущена!');
console.log('========================================');
console.log(`Веб-интерфейс: http://localhost:${WEB_PORT}`);
console.log('========================================');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment