278 lines
10 KiB
Python
278 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
RinCoin Mining Pool Web Interface
|
|
Provides web dashboard for pool statistics and miner management
|
|
"""
|
|
|
|
import json
|
|
import sqlite3
|
|
from datetime import datetime, timedelta
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
import threading
|
|
import time
|
|
|
|
class PoolWebInterface:
|
|
def __init__(self, pool_db, host='0.0.0.0', port=8080):
|
|
self.pool_db = pool_db
|
|
self.host = host
|
|
self.port = port
|
|
|
|
def get_pool_stats(self):
|
|
"""Get current pool statistics"""
|
|
try:
|
|
cursor = self.pool_db.cursor()
|
|
|
|
# Total miners
|
|
cursor.execute('SELECT COUNT(DISTINCT id) FROM miners')
|
|
total_miners = cursor.fetchone()[0]
|
|
|
|
# Active miners (last 10 minutes)
|
|
cursor.execute('''
|
|
SELECT COUNT(DISTINCT m.id) FROM miners m
|
|
JOIN shares s ON m.id = s.miner_id
|
|
WHERE s.submitted > datetime('now', '-10 minutes')
|
|
''')
|
|
active_miners = cursor.fetchone()[0]
|
|
|
|
# Total shares (last 24 hours)
|
|
cursor.execute('''
|
|
SELECT COUNT(*) FROM shares
|
|
WHERE submitted > datetime('now', '-24 hours')
|
|
''')
|
|
total_shares_24h = cursor.fetchone()[0]
|
|
|
|
# Pool hashrate estimate
|
|
cursor.execute('''
|
|
SELECT COUNT(*) FROM shares
|
|
WHERE submitted > datetime('now', '-5 minutes')
|
|
''')
|
|
recent_shares = cursor.fetchone()[0]
|
|
hashrate = recent_shares * 12 # Rough estimate
|
|
|
|
# Total blocks found
|
|
cursor.execute('SELECT COUNT(*) FROM blocks')
|
|
total_blocks = cursor.fetchone()[0]
|
|
|
|
# Recent blocks
|
|
cursor.execute('''
|
|
SELECT block_hash, height, reward, found_at
|
|
FROM blocks
|
|
ORDER BY found_at DESC
|
|
LIMIT 10
|
|
''')
|
|
recent_blocks = cursor.fetchall()
|
|
|
|
# Top miners
|
|
cursor.execute('''
|
|
SELECT m.user, m.worker, COUNT(s.id) as shares, m.last_share
|
|
FROM miners m
|
|
JOIN shares s ON m.id = s.miner_id
|
|
WHERE s.submitted > datetime('now', '-24 hours')
|
|
GROUP BY m.id
|
|
ORDER BY shares DESC
|
|
LIMIT 10
|
|
''')
|
|
top_miners = cursor.fetchall()
|
|
|
|
return {
|
|
'total_miners': total_miners,
|
|
'active_miners': active_miners,
|
|
'total_shares_24h': total_shares_24h,
|
|
'hashrate': hashrate,
|
|
'total_blocks': total_blocks,
|
|
'recent_blocks': recent_blocks,
|
|
'top_miners': top_miners
|
|
}
|
|
except Exception as e:
|
|
print(f"Error getting pool stats: {e}")
|
|
return {}
|
|
|
|
def generate_html(self, stats):
|
|
"""Generate HTML dashboard"""
|
|
html = f"""
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>RinCoin Mining Pool</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<style>
|
|
body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }}
|
|
.container {{ max-width: 1200px; margin: 0 auto; }}
|
|
.header {{ background: #2c3e50; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
|
|
.stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }}
|
|
.stat-card {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
|
.stat-value {{ font-size: 2em; font-weight: bold; color: #3498db; }}
|
|
.stat-label {{ color: #7f8c8d; margin-top: 5px; }}
|
|
.section {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }}
|
|
.section h2 {{ margin-top: 0; color: #2c3e50; }}
|
|
table {{ width: 100%; border-collapse: collapse; }}
|
|
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
|
|
th {{ background: #f8f9fa; font-weight: bold; }}
|
|
.block-hash {{ font-family: monospace; font-size: 0.9em; }}
|
|
.refresh-btn {{ background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }}
|
|
.refresh-btn:hover {{ background: #2980b9; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🏊♂️ RinCoin Mining Pool</h1>
|
|
<p>Distribute block rewards among multiple miners</p>
|
|
</div>
|
|
|
|
<button class="refresh-btn" onclick="location.reload()">🔄 Refresh</button>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value">{stats.get('total_miners', 0)}</div>
|
|
<div class="stat-label">Total Miners</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{stats.get('active_miners', 0)}</div>
|
|
<div class="stat-label">Active Miners</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{stats.get('hashrate', 0):.2f}</div>
|
|
<div class="stat-label">Hashrate (H/s)</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{stats.get('total_blocks', 0)}</div>
|
|
<div class="stat-label">Blocks Found</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>📊 Pool Statistics</h2>
|
|
<p><strong>24h Shares:</strong> {stats.get('total_shares_24h', 0):,}</p>
|
|
<p><strong>Pool Fee:</strong> 1%</p>
|
|
<p><strong>Connection String:</strong> <code>stratum+tcp://YOUR_IP:3333</code></p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>👥 Top Miners (24h)</h2>
|
|
<table>
|
|
<tr>
|
|
<th>User</th>
|
|
<th>Worker</th>
|
|
<th>Shares</th>
|
|
<th>Last Share</th>
|
|
</tr>
|
|
"""
|
|
|
|
for miner in stats.get('top_miners', []):
|
|
user, worker, shares, last_share = miner
|
|
html += f"""
|
|
<tr>
|
|
<td>{user}</td>
|
|
<td>{worker}</td>
|
|
<td>{shares:,}</td>
|
|
<td>{last_share or 'Never'}</td>
|
|
</tr>
|
|
"""
|
|
|
|
html += """
|
|
</table>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🏆 Recent Blocks</h2>
|
|
<table>
|
|
<tr>
|
|
<th>Height</th>
|
|
<th>Hash</th>
|
|
<th>Reward</th>
|
|
<th>Found At</th>
|
|
</tr>
|
|
"""
|
|
|
|
for block in stats.get('recent_blocks', []):
|
|
block_hash, height, reward, found_at = block
|
|
html += f"""
|
|
<tr>
|
|
<td>{height}</td>
|
|
<td class="block-hash">{block_hash[:16]}...</td>
|
|
<td>{reward:.8f} RIN</td>
|
|
<td>{found_at}</td>
|
|
</tr>
|
|
"""
|
|
|
|
html += """
|
|
</table>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🔗 Connect to Pool</h2>
|
|
<p>Use any RinHash-compatible miner:</p>
|
|
<pre><code>./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u username.workername -p x</code></pre>
|
|
<p><strong>Replace YOUR_IP with your server's public IP address</strong></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Auto-refresh every 30 seconds
|
|
setTimeout(() => location.reload(), 30000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html
|
|
|
|
class PoolWebHandler(BaseHTTPRequestHandler):
|
|
def __init__(self, *args, pool_interface=None, **kwargs):
|
|
self.pool_interface = pool_interface
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def do_GET(self):
|
|
if self.path == '/':
|
|
stats = self.pool_interface.get_pool_stats()
|
|
html = self.pool_interface.generate_html(stats)
|
|
|
|
self.send_response(200)
|
|
self.send_header('Content-type', 'text/html')
|
|
self.end_headers()
|
|
self.wfile.write(html.encode('utf-8'))
|
|
elif self.path == '/api/stats':
|
|
stats = self.pool_interface.get_pool_stats()
|
|
|
|
self.send_response(200)
|
|
self.send_header('Content-type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(stats).encode('utf-8'))
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
self.wfile.write(b'Not Found')
|
|
|
|
def log_message(self, format, *args):
|
|
# Suppress access logs
|
|
pass
|
|
|
|
def start_web_interface(pool_db, host='0.0.0.0', port=8083):
|
|
"""Start the web interface server"""
|
|
interface = PoolWebInterface(pool_db, host, port)
|
|
|
|
class Handler(PoolWebHandler):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, pool_interface=interface, **kwargs)
|
|
|
|
try:
|
|
server = HTTPServer((host, port), Handler)
|
|
print(f"🌐 Web interface running on http://{host}:{port}")
|
|
print("Press Ctrl+C to stop")
|
|
|
|
server.serve_forever()
|
|
except OSError as e:
|
|
if "Address already in use" in str(e):
|
|
print(f"⚠️ Port {port} is already in use, web interface not started")
|
|
print(f"💡 Try a different port or kill the process using port {port}")
|
|
else:
|
|
print(f"❌ Failed to start web interface: {e}")
|
|
except KeyboardInterrupt:
|
|
print("\n🛑 Shutting down web interface...")
|
|
server.shutdown()
|
|
|
|
if __name__ == "__main__":
|
|
# This would be called from the main pool server
|
|
print("Web interface module loaded")
|