1102 / static /index.html
lovyone's picture
Upload 5 files
cca7387 verified
Raw
History Blame Contribute Delete
19.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XMRig Mining Server</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 20px;
}
.container-main {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
animation: slideDown 0.6s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header h1 {
font-size: 3em;
font-weight: 700;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.header p {
font-size: 1.2em;
opacity: 0.95;
}
.card-custom {
background: rgba(255, 255, 255, 0.95);
border: none;
border-radius: 15px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
margin-bottom: 25px;
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-header-custom {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
font-size: 1.2em;
padding: 20px;
border-radius: 15px 15px 0 0;
display: flex;
align-items: center;
gap: 10px;
}
.card-body-custom {
padding: 25px;
}
.status-badge {
display: inline-block;
padding: 10px 20px;
border-radius: 25px;
font-weight: 600;
font-size: 0.95em;
}
.status-running {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.status-stopped {
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
color: white;
}
.status-admin {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.info-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 15px;
}
.info-item {
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
border-left: 4px solid #667eea;
}
.info-item label {
font-weight: 600;
color: #333;
display: block;
margin-bottom: 5px;
}
.info-item value {
font-size: 1.1em;
color: #667eea;
font-weight: 600;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
font-weight: 600;
color: #333;
margin-bottom: 8px;
display: block;
}
.form-control-custom {
border: 2px solid #e0e0e0;
border-radius: 10px;
padding: 12px 15px;
font-size: 1em;
transition: all 0.3s ease;
}
.form-control-custom:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.1);
outline: none;
}
.btn-custom {
font-weight: 600;
padding: 12px 25px;
border-radius: 10px;
border: none;
transition: all 0.3s ease;
font-size: 1em;
}
.btn-start {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
.btn-start:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(17, 153, 142, 0.3);
color: white;
}
.btn-start:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-stop {
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
color: white;
}
.btn-stop:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(235, 51, 73, 0.3);
color: white;
}
.btn-stop:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-group-custom {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
}
.logs-container {
background: #1e1e1e;
color: #0fc700;
padding: 20px;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
line-height: 1.6;
max-height: 500px;
overflow-y: auto;
border: 2px solid #333;
}
.logs-container::-webkit-scrollbar {
width: 8px;
}
.logs-container::-webkit-scrollbar-track {
background: #2d2d2d;
border-radius: 5px;
}
.logs-container::-webkit-scrollbar-thumb {
background: #667eea;
border-radius: 5px;
}
.log-line {
margin: 2px 0;
word-break: break-word;
}
.log-error {
color: #ff6b6b;
}
.log-success {
color: #51cf66;
}
.log-warning {
color: #ffd700;
}
.log-info {
color: #74c0fc;
}
.spinner {
display: none;
width: 18px;
height: 18px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-right: 8px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner.active {
display: inline-block;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.stat-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.stat-label {
font-size: 0.9em;
opacity: 0.9;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.8em;
font-weight: 700;
}
.warning-box {
background: #fff3cd;
border: 2px solid #ffc107;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
display: none;
}
.warning-box.show {
display: block;
}
.warning-box strong {
color: #856404;
}
@media (max-width: 768px) {
.info-row, .stats-grid {
grid-template-columns: 1fr;
}
.btn-group-custom {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2em;
}
}
</style>
</head>
<body>
<div class="container-main">
<!-- Header -->
<div class="header">
<h1>⛏️ XMRig Mining</h1>
<p>RandomX Benchmarking Server</p>
</div>
<!-- System Status -->
<div class="card-custom">
<div class="card-header-custom">
📊 System Status
</div>
<div class="card-body-custom">
<div class="stats-grid">
<div class="stat-box">
<div class="stat-label">Platform</div>
<div class="stat-value" id="platform">Loading...</div>
</div>
<div class="stat-box">
<div class="stat-label">CPU Cores</div>
<div class="stat-value" id="cpu-count">Loading...</div>
</div>
</div>
<div style="margin-top: 15px;">
<div id="admin-status" style="text-align: center;"></div>
<div style="text-align: center; margin-top: 10px; color: #666;">
<strong>XMRig:</strong> <span id="binary-status">Loading...</span>
</div>
</div>
</div>
</div>
<!-- Mining Control -->
<div class="card-custom">
<div class="card-header-custom">
🚀 Mining Control
</div>
<div class="card-body-custom">
<div style="text-align: center; margin-bottom: 20px;">
<span id="status-badge" class="status-badge status-stopped">⏹️ Stopped</span>
</div>
<div class="warning-box" id="admin-warning">
<strong>⚠️ Warning:</strong> Not running as Administrator. Performance will be limited. Run as Admin for 2-3x better hash rates.
</div>
<!-- Benchmark Type -->
<div class="form-group">
<label>Benchmark Type</label>
<select id="benchmark-type" class="form-control-custom w-100">
<option value="1M">1M Hash (Fast ~1 min)</option>
<option value="10M" selected>10M Hash (Accurate ~10 min)</option>
</select>
<small style="color: #999; display: block; margin-top: 5px;">Choose benchmark size</small>
</div>
<!-- Threads -->
<div class="form-group">
<label>Threads</label>
<input type="number" id="threads" class="form-control-custom w-100" value="8" min="1" max="64">
<small style="color: #999; display: block; margin-top: 5px;">Number of threads (default: 8)</small>
</div>
<!-- Control Buttons -->
<div class="btn-group-custom">
<button class="btn btn-custom btn-start" id="start-btn" onclick="startMining()">
<span class="spinner" id="start-spinner"></span>
▶️ Start Mining
</button>
<button class="btn btn-custom btn-stop" id="stop-btn" onclick="stopMining()" disabled>
⏹️ Stop Mining
</button>
</div>
</div>
</div>
<!-- Mining Logs -->
<div class="card-custom">
<div class="card-header-custom">
📋 Live Mining Logs
</div>
<div class="card-body-custom">
<div class="logs-container" id="logs-container">
<div class="log-line log-info">[*] Waiting for mining to start...</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const API_URL = '/';
let statusCheckInterval = null;
let lastLogCount = 0;
async function getStatus() {
try {
const response = await fetch(API_URL + 'status');
const data = await response.json();
updateUI(data);
} catch (error) {
console.error('Error fetching status:', error);
}
}
async function getLogs() {
try {
const response = await fetch(API_URL + 'logs');
const data = await response.json();
updateLogs(data.logs);
} catch (error) {
console.error('Error fetching logs:', error);
}
}
function updateLogs(logs) {
const logsContainer = document.getElementById('logs-container');
if (logs.length === 0) {
logsContainer.innerHTML = '<div class="log-line log-info">[*] Waiting for logs...</div>';
return;
}
if (logs.length > lastLogCount) {
const newLogs = logs.slice(lastLogCount);
newLogs.forEach(log => {
const logLine = document.createElement('div');
logLine.className = 'log-line';
if (log.message.includes('[!]') || log.message.includes('FAILED')) {
logLine.classList.add('log-error');
} else if (log.message.includes('[+]')) {
logLine.classList.add('log-success');
} else if (log.message.includes('[*]')) {
logLine.classList.add('log-info');
} else if (log.message.includes('H/s') || log.message.includes('speed')) {
logLine.classList.add('log-success');
} else {
logLine.classList.add('log-info');
}
logLine.textContent = log.message;
logsContainer.appendChild(logLine);
});
logsContainer.scrollTop = logsContainer.scrollHeight;
}
lastLogCount = logs.length;
}
function updateUI(data) {
// System Info
document.getElementById('platform').textContent = data.system.platform;
document.getElementById('cpu-count').textContent = data.system.cpu_count;
// Admin Status
const adminStatus = document.getElementById('admin-status');
if (data.system.admin) {
adminStatus.innerHTML = '<span class="status-badge status-admin">✓ Administrator</span>';
document.getElementById('admin-warning').classList.remove('show');
} else {
adminStatus.innerHTML = '<span class="status-badge" style="background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white;">✗ Standard User</span>';
document.getElementById('admin-warning').classList.add('show');
}
// Binary Status
if (data.xmrig.binary_exists) {
document.getElementById('binary-status').textContent = '✓ Ready';
document.getElementById('start-btn').disabled = false;
} else {
document.getElementById('binary-status').textContent = '✗ Missing';
document.getElementById('start-btn').disabled = true;
}
// Mining Status
if (data.benchmark.running) {
document.getElementById('status-badge').textContent = '▶️ Running';
document.getElementById('status-badge').className = 'status-badge status-running';
document.getElementById('start-btn').disabled = true;
document.getElementById('stop-btn').disabled = false;
} else {
document.getElementById('status-badge').textContent = '⏹️ Stopped';
document.getElementById('status-badge').className = 'status-badge status-stopped';
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
}
}
async function startMining() {
const benchmark = document.getElementById('benchmark-type').value;
const threads = parseInt(document.getElementById('threads').value) || 8;
const btn = document.getElementById('start-btn');
const spinner = document.getElementById('start-spinner');
btn.disabled = true;
spinner.classList.add('active');
try {
const response = await fetch(API_URL + 'mining/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ benchmark, threads })
});
if (response.ok) {
const data = await response.json();
console.log('Mining started:', data);
lastLogCount = 0;
startStatusPolling();
await getStatus();
} else {
const error = await response.json();
alert('Error: ' + error.detail);
btn.disabled = false;
spinner.classList.remove('active');
}
} catch (error) {
alert('Error: ' + error.message);
btn.disabled = false;
spinner.classList.remove('active');
}
}
async function stopMining() {
const btn = document.getElementById('stop-btn');
btn.disabled = true;
try {
const response = await fetch(API_URL + 'mining/stop', { method: 'POST' });
if (response.ok) {
stopStatusPolling();
await getStatus();
} else {
alert('Failed to stop mining');
}
} catch (error) {
alert('Error: ' + error.message);
}
}
function startStatusPolling() {
if (statusCheckInterval) return;
statusCheckInterval = setInterval(() => {
getStatus();
getLogs();
}, 1000);
}
function stopStatusPolling() {
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
statusCheckInterval = null;
}
}
// Initial load
getStatus();
getLogs();
// Refresh every 2 seconds
setInterval(() => {
getStatus();
getLogs();
}, 2000);
</script>
</body>
</html>