|
|
""" |
|
|
FastAPI server for Bitcoin mining dashboard - Complete Integration |
|
|
""" |
|
|
from fastapi import FastAPI, HTTPException, BackgroundTasks |
|
|
from fastapi.staticfiles import StaticFiles |
|
|
from fastapi.responses import FileResponse, HTMLResponse |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
import uvicorn |
|
|
import threading |
|
|
import time |
|
|
import json |
|
|
import asyncio |
|
|
from typing import Dict, Optional, List |
|
|
import logging |
|
|
import sys |
|
|
import os |
|
|
|
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(levelname)s - %(message)s' |
|
|
) |
|
|
|
|
|
app = FastAPI(title="Bitcoin Mining Dashboard", version="1.0.0") |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static") |
|
|
|
|
|
|
|
|
class MiningState: |
|
|
def __init__(self): |
|
|
self.is_mining = False |
|
|
self.miner_instance = None |
|
|
self.mining_thread = None |
|
|
self.stats = { |
|
|
"status": "Stopped", |
|
|
"hashrate": "0 H/s", |
|
|
"total_hashes": "0", |
|
|
"blocks_found": "0", |
|
|
"best_hash": "None", |
|
|
"difficulty": "0", |
|
|
"network_difficulty": "0", |
|
|
"block_alert": "Ready to start mining", |
|
|
"mining_time": "0s", |
|
|
"cores_active": "0", |
|
|
"wallet_address": "1Ks4WtCEK96BaBF7HSuCGt3rEpVKPqcJKf" |
|
|
} |
|
|
self.start_time = None |
|
|
|
|
|
mining_state = MiningState() |
|
|
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
|
async def get_index(): |
|
|
"""Serve the dashboard HTML""" |
|
|
try: |
|
|
return FileResponse("static/index.html") |
|
|
except: |
|
|
|
|
|
return """ |
|
|
<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<title>Bitcoin Mining Dashboard</title> |
|
|
<style> |
|
|
body { font-family: Arial, sans-serif; margin: 40px; background: #f0f0f0; } |
|
|
.container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; } |
|
|
.stats { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; margin: 10px 0; } |
|
|
.alert { background: #e74c3c; color: white; padding: 15px; border-radius: 5px; margin: 10px 0; } |
|
|
.success { background: #27ae60; } |
|
|
.button { background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 5px; } |
|
|
.button:disabled { background: #95a5a6; } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>βοΈ Bitcoin Mining Dashboard</h1> |
|
|
<div id="blockAlert" class="alert">Ready to start mining</div> |
|
|
<div class="stats"> |
|
|
<h3>Mining Statistics</h3> |
|
|
<div id="statsDisplay"></div> |
|
|
</div> |
|
|
<button class="button" onclick="startMining()" id="startBtn">Start Mining</button> |
|
|
<button class="button" onclick="stopMining()" id="stopBtn" disabled>Stop Mining</button> |
|
|
<script> |
|
|
function updateStats(stats) { |
|
|
document.getElementById('statsDisplay').innerHTML = ` |
|
|
<p><strong>Status:</strong> ${stats.status}</p> |
|
|
<p><strong>Hash Rate:</strong> ${stats.hashrate}</p> |
|
|
<p><strong>Total Hashes:</strong> ${stats.total_hashes}</p> |
|
|
<p><strong>Blocks Found:</strong> ${stats.blocks_found}</p> |
|
|
<p><strong>Best Hash:</strong> ${stats.best_hash}</p> |
|
|
<p><strong>Mining Time:</strong> ${stats.mining_time}</p> |
|
|
<p><strong>Wallet:</strong> ${stats.wallet_address}</p> |
|
|
`; |
|
|
document.getElementById('blockAlert').textContent = stats.block_alert; |
|
|
document.getElementById('blockAlert').className = stats.blocks_found > 0 ? 'alert success' : 'alert'; |
|
|
|
|
|
document.getElementById('startBtn').disabled = stats.status === 'Running'; |
|
|
document.getElementById('stopBtn').disabled = stats.status !== 'Running'; |
|
|
} |
|
|
|
|
|
async function startMining() { |
|
|
const response = await fetch('/start_mining', { method: 'POST' }); |
|
|
const result = await response.json(); |
|
|
alert(result.message); |
|
|
} |
|
|
|
|
|
async function stopMining() { |
|
|
const response = await fetch('/stop_mining', { method: 'POST' }); |
|
|
const result = await response.json(); |
|
|
alert(result.message); |
|
|
} |
|
|
|
|
|
// Poll for stats updates |
|
|
setInterval(async () => { |
|
|
const response = await fetch('/get_stats'); |
|
|
const stats = await response.json(); |
|
|
updateStats(stats); |
|
|
}, 1000); |
|
|
</script> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
""" |
|
|
|
|
|
def mining_worker(duration=None): |
|
|
"""Worker function to run mining in background thread""" |
|
|
try: |
|
|
|
|
|
from parallel_miner_v3 import ParallelMiner |
|
|
|
|
|
mining_state.miner_instance = ParallelMiner( |
|
|
num_cores=7, |
|
|
wallet_address="1Ks4WtCEK96BaBF7HSuCGt3rEpVKPqcJKf" |
|
|
) |
|
|
mining_state.start_time = time.time() |
|
|
|
|
|
|
|
|
mining_state.miner_instance.start_mining(duration=duration) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Mining worker error: {e}") |
|
|
finally: |
|
|
mining_state.is_mining = False |
|
|
mining_state.mining_thread = None |
|
|
|
|
|
@app.post("/start_mining") |
|
|
async def start_mining(background_tasks: BackgroundTasks): |
|
|
"""Start the mining process""" |
|
|
global mining_state |
|
|
|
|
|
if mining_state.is_mining: |
|
|
raise HTTPException(status_code=400, detail="Mining is already running") |
|
|
|
|
|
try: |
|
|
mining_state.is_mining = True |
|
|
mining_state.stats["status"] = "Starting..." |
|
|
mining_state.stats["block_alert"] = "π Starting mining process..." |
|
|
|
|
|
|
|
|
mining_state.mining_thread = threading.Thread( |
|
|
target=mining_worker, |
|
|
kwargs={"duration": None} |
|
|
) |
|
|
mining_state.mining_thread.daemon = True |
|
|
mining_state.mining_thread.start() |
|
|
|
|
|
logging.info("β
Mining started via API") |
|
|
return {"message": "Mining started successfully", "status": "started"} |
|
|
|
|
|
except Exception as e: |
|
|
mining_state.is_mining = False |
|
|
logging.error(f"Error starting mining: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
@app.post("/stop_mining") |
|
|
async def stop_mining(): |
|
|
"""Stop the mining process""" |
|
|
global mining_state |
|
|
|
|
|
if not mining_state.is_mining: |
|
|
raise HTTPException(status_code=400, detail="Mining is not running") |
|
|
|
|
|
try: |
|
|
if mining_state.miner_instance: |
|
|
mining_state.miner_instance.mining = False |
|
|
mining_state.is_mining = False |
|
|
|
|
|
|
|
|
if hasattr(mining_state.miner_instance, 'total_hashes'): |
|
|
total_hashes = mining_state.miner_instance.total_hashes |
|
|
blocks_found = mining_state.miner_instance.blocks_found |
|
|
mining_time = time.time() - mining_state.start_time |
|
|
|
|
|
logging.info("\n" + "="*50) |
|
|
logging.info("βοΈ FINAL MINING STATISTICS") |
|
|
logging.info("="*50) |
|
|
logging.info(f"β±οΈ Total mining time: {mining_time:.2f} seconds") |
|
|
logging.info(f"π’ Total hashes: {total_hashes:,}") |
|
|
logging.info(f"π° Blocks found: {blocks_found}") |
|
|
logging.info(f"β‘ Average hash rate: {total_hashes/max(mining_time,1)/1000:.2f} KH/s") |
|
|
|
|
|
if hasattr(mining_state.miner_instance, 'cores'): |
|
|
for core_idx, core in enumerate(mining_state.miner_instance.cores): |
|
|
logging.info(f"π© Core {core_idx}: {core.total_hashes:,} hashes") |
|
|
logging.info("="*50) |
|
|
|
|
|
mining_state.stats["status"] = "Stopped" |
|
|
mining_state.stats["block_alert"] = "π Mining stopped" |
|
|
|
|
|
return {"message": "Mining stopped successfully", "status": "stopped"} |
|
|
|
|
|
raise HTTPException(status_code=400, detail="No active mining instance") |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error stopping mining: {e}") |
|
|
raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
|
@app.get("/get_stats") |
|
|
async def get_mining_stats(): |
|
|
"""Get current mining statistics""" |
|
|
global mining_state |
|
|
|
|
|
if not mining_state.is_mining or not mining_state.miner_instance: |
|
|
return mining_state.stats |
|
|
|
|
|
try: |
|
|
miner = mining_state.miner_instance |
|
|
|
|
|
|
|
|
mining_time = time.time() - mining_state.start_time |
|
|
time_str = f"{int(mining_time//3600)}h {int((mining_time%3600)//60)}m {int(mining_time%60)}s" |
|
|
|
|
|
|
|
|
if miner.blocks_found > 0: |
|
|
block_alert = f"π FOUND {miner.blocks_found} BLOCK(S)! π" |
|
|
if miner.best_hash: |
|
|
block_alert += f" Hash: {miner.best_hash.hex()[:16]}..." |
|
|
else: |
|
|
progress = (miner.best_hash_difficulty / max(miner.network_difficulty, 1)) * 100 if miner.best_hash_difficulty else 0 |
|
|
block_alert = f"βοΈ Mining... Progress: {progress:.8f}%" |
|
|
|
|
|
|
|
|
mining_state.stats.update({ |
|
|
"status": "Running", |
|
|
"hashrate": f"{miner.current_hashrate/1000:.2f} KH/s", |
|
|
"total_hashes": f"{miner.total_hashes:,}", |
|
|
"blocks_found": str(miner.blocks_found), |
|
|
"best_hash": miner.best_hash.hex()[:32] + "..." if miner.best_hash else "None", |
|
|
"difficulty": f"{miner.best_hash_difficulty:.8f}" if miner.best_hash_difficulty else "0", |
|
|
"network_difficulty": f"{miner.network_difficulty:,.2f}" if hasattr(miner, 'network_difficulty') else "0", |
|
|
"block_alert": block_alert, |
|
|
"mining_time": time_str, |
|
|
"cores_active": f"{len(miner.cores)}" if hasattr(miner, 'cores') else "0" |
|
|
}) |
|
|
|
|
|
return mining_state.stats |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Error getting mining stats: {e}") |
|
|
return { |
|
|
"status": "Error", |
|
|
"hashrate": "0 H/s", |
|
|
"total_hashes": "0", |
|
|
"blocks_found": "0", |
|
|
"best_hash": "Error", |
|
|
"difficulty": "0", |
|
|
"block_alert": f"Error: {str(e)}", |
|
|
"mining_time": "0s", |
|
|
"cores_active": "0" |
|
|
} |
|
|
|
|
|
@app.get("/get_detailed_stats") |
|
|
async def get_detailed_stats(): |
|
|
"""Get detailed mining statistics including per-core info""" |
|
|
global mining_state |
|
|
|
|
|
if not mining_state.is_mining or not mining_state.miner_instance: |
|
|
return {"error": "Mining not active"} |
|
|
|
|
|
try: |
|
|
miner = mining_state.miner_instance |
|
|
detailed_stats = { |
|
|
"overview": { |
|
|
"status": "Running", |
|
|
"total_hashes": miner.total_hashes, |
|
|
"blocks_found": miner.blocks_found, |
|
|
"current_hashrate": miner.current_hashrate, |
|
|
"best_hash_difficulty": miner.best_hash_difficulty, |
|
|
"network_difficulty": miner.network_difficulty, |
|
|
"mining_time": time.time() - mining_state.start_time |
|
|
}, |
|
|
"cores": [] |
|
|
} |
|
|
|
|
|
if hasattr(miner, 'cores'): |
|
|
for core in miner.cores: |
|
|
core_info = { |
|
|
"core_id": core.core_id, |
|
|
"total_hashes": core.total_hashes, |
|
|
"blocks_found": core.blocks_found, |
|
|
"units": [] |
|
|
} |
|
|
|
|
|
for unit in core.units: |
|
|
core_info["units"].append({ |
|
|
"unit_id": unit.unit_id, |
|
|
"total_hashes": unit.total_hashes, |
|
|
"blocks_found": unit.blocks_found |
|
|
}) |
|
|
|
|
|
detailed_stats["cores"].append(core_info) |
|
|
|
|
|
return detailed_stats |
|
|
|
|
|
except Exception as e: |
|
|
return {"error": str(e)} |
|
|
|
|
|
@app.get("/health") |
|
|
async def health_check(): |
|
|
"""Health check endpoint""" |
|
|
return { |
|
|
"status": "healthy", |
|
|
"mining_active": mining_state.is_mining, |
|
|
"timestamp": time.time() |
|
|
} |
|
|
|
|
|
|
|
|
@app.on_event("startup") |
|
|
async def startup_event(): |
|
|
"""Initialize on startup""" |
|
|
logging.info("π Bitcoin Mining Dashboard starting up...") |
|
|
|
|
|
@app.on_event("shutdown") |
|
|
async def shutdown_event(): |
|
|
"""Cleanup on shutdown""" |
|
|
global mining_state |
|
|
if mining_state.is_mining and mining_state.miner_instance: |
|
|
mining_state.miner_instance.mining = False |
|
|
mining_state.is_mining = False |
|
|
logging.info("π Bitcoin Mining Dashboard shutting down...") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
uvicorn.run( |
|
|
"app:app", |
|
|
host="0.0.0.0", |
|
|
port=7868, |
|
|
reload=False, |
|
|
log_level="info" |
|
|
) |