Created
December 7, 2025 15:20
-
-
Save Timonchegs/6c3ceed25db972ae863e9e41503b0500 to your computer and use it in GitHub Desktop.
блок дашборда
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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