mirror of
https://github.com/elder-plinius/R00TS.git
synced 2026-02-12 09:12:51 +00:00
391 lines
17 KiB
HTML
391 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>R00TS Admin Dashboard</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
background-color: #f8f9fa;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
.dashboard-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
.card {
|
|
margin-bottom: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
.card-header {
|
|
font-weight: bold;
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
border-radius: 10px 10px 0 0 !important;
|
|
}
|
|
.status-indicator {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
margin-right: 5px;
|
|
}
|
|
.status-healthy {
|
|
background-color: #4CAF50;
|
|
}
|
|
.status-warning {
|
|
background-color: #FFC107;
|
|
}
|
|
.status-error {
|
|
background-color: #DC3545;
|
|
}
|
|
.refresh-btn {
|
|
background-color: #4CAF50;
|
|
border-color: #4CAF50;
|
|
}
|
|
.refresh-btn:hover {
|
|
background-color: #3e8e41;
|
|
border-color: #3e8e41;
|
|
}
|
|
.stats-value {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: #4CAF50;
|
|
}
|
|
.stats-label {
|
|
color: #6c757d;
|
|
font-size: 0.9rem;
|
|
}
|
|
.action-btn {
|
|
margin-right: 5px;
|
|
margin-bottom: 5px;
|
|
}
|
|
#last-updated {
|
|
font-style: italic;
|
|
color: #6c757d;
|
|
}
|
|
.backup-item {
|
|
border-left: 3px solid #4CAF50;
|
|
padding-left: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.memory-bar {
|
|
height: 5px;
|
|
background-color: #e9ecef;
|
|
border-radius: 5px;
|
|
margin-top: 5px;
|
|
}
|
|
.memory-used {
|
|
height: 100%;
|
|
background-color: #4CAF50;
|
|
border-radius: 5px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="dashboard-container">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1>R00TS Admin Dashboard</h1>
|
|
<button id="refresh-btn" class="btn btn-primary refresh-btn">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- System Status Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">System Status</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<div>
|
|
<span id="server-status-indicator" class="status-indicator"></span>
|
|
<span id="server-status">Checking server status...</span>
|
|
</div>
|
|
<div>
|
|
<span id="db-status-indicator" class="status-indicator"></span>
|
|
<span id="db-status">Checking database status...</span>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<p class="mb-1">Server Uptime:</p>
|
|
<h4 id="server-uptime">Loading...</h4>
|
|
</div>
|
|
<div class="mb-3">
|
|
<p class="mb-1">Memory Usage:</p>
|
|
<div class="d-flex justify-content-between">
|
|
<span id="memory-usage">Loading...</span>
|
|
<span id="memory-percentage">0%</span>
|
|
</div>
|
|
<div class="memory-bar">
|
|
<div id="memory-bar-used" class="memory-used" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<button id="restart-server" class="btn btn-warning action-btn">Restart Server</button>
|
|
<button id="view-logs" class="btn btn-info action-btn">View Logs</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">Statistics</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-6 mb-4">
|
|
<div class="stats-value" id="total-words">-</div>
|
|
<div class="stats-label">Total Words</div>
|
|
</div>
|
|
<div class="col-6 mb-4">
|
|
<div class="stats-value" id="unique-words">-</div>
|
|
<div class="stats-label">Unique Words</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="stats-value" id="total-datasets">-</div>
|
|
<div class="stats-label">Datasets</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="stats-value" id="last-submission">-</div>
|
|
<div class="stats-label">Last Submission</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-3">
|
|
<!-- Backup Management Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">Backup Management</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<h5>Recent Backups</h5>
|
|
<button id="create-backup" class="btn btn-sm btn-success">Create Backup Now</button>
|
|
</div>
|
|
<div id="backup-list">
|
|
<p>Loading backups...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions Card -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">Quick Actions</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="index.html" class="btn btn-outline-success mb-2" target="_blank">Open R00TS Application</a>
|
|
<a href="datasets.html" class="btn btn-outline-success mb-2" target="_blank">Manage Datasets</a>
|
|
<button id="export-all-data" class="btn btn-outline-primary mb-2">Export All Data</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p id="last-updated" class="mt-3 text-center">Last updated: Never</p>
|
|
</div>
|
|
|
|
<script>
|
|
// API base URL
|
|
const API_BASE_URL = 'http://localhost:5000/api';
|
|
|
|
// Format uptime function
|
|
function formatUptime(seconds) {
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
|
|
let result = '';
|
|
if (days > 0) result += `${days}d `;
|
|
if (hours > 0) result += `${hours}h `;
|
|
if (minutes > 0) result += `${minutes}m `;
|
|
result += `${secs}s`;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Format bytes function
|
|
function formatBytes(bytes, decimals = 2) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
const k = 1024;
|
|
const dm = decimals < 0 ? 0 : decimals;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// Format date function
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleString();
|
|
}
|
|
|
|
// Update dashboard function
|
|
async function updateDashboard() {
|
|
try {
|
|
// Fetch health data
|
|
const healthResponse = await fetch(`${API_BASE_URL}/health`);
|
|
const healthData = await healthResponse.json();
|
|
|
|
// Update server status
|
|
document.getElementById('server-status').textContent = 'Server: Online';
|
|
document.getElementById('server-status-indicator').className = 'status-indicator status-healthy';
|
|
|
|
// Update database status
|
|
const dbStatus = healthData.database.status;
|
|
document.getElementById('db-status').textContent = `Database: ${dbStatus === 'connected' ? 'Connected' : 'Disconnected'}`;
|
|
document.getElementById('db-status-indicator').className = `status-indicator ${dbStatus === 'connected' ? 'status-healthy' : 'status-error'}`;
|
|
|
|
// Update uptime
|
|
document.getElementById('server-uptime').textContent = formatUptime(healthData.server.uptime);
|
|
|
|
// Update memory usage
|
|
const rss = healthData.server.memory.rss;
|
|
const heapTotal = healthData.server.memory.heapTotal;
|
|
const heapUsed = healthData.server.memory.heapUsed;
|
|
const memoryPercentage = Math.round((heapUsed / heapTotal) * 100);
|
|
|
|
document.getElementById('memory-usage').textContent = `${formatBytes(heapUsed)} / ${formatBytes(heapTotal)}`;
|
|
document.getElementById('memory-percentage').textContent = `${memoryPercentage}%`;
|
|
document.getElementById('memory-bar-used').style.width = `${memoryPercentage}%`;
|
|
|
|
// Fetch statistics
|
|
const statsResponse = await fetch(`${API_BASE_URL}/words/stats`);
|
|
const statsData = await statsResponse.json();
|
|
|
|
document.getElementById('total-words').textContent = statsData.totalSubmissions || 0;
|
|
document.getElementById('unique-words').textContent = statsData.uniqueWords || 0;
|
|
|
|
// Fetch datasets
|
|
const datasetsResponse = await fetch(`${API_BASE_URL}/datasets`);
|
|
const datasetsData = await datasetsResponse.json();
|
|
|
|
document.getElementById('total-datasets').textContent = datasetsData.length || 0;
|
|
|
|
// Get last submission time
|
|
if (statsData.lastSubmission) {
|
|
const lastSubmissionDate = new Date(statsData.lastSubmission);
|
|
const now = new Date();
|
|
const diffMinutes = Math.floor((now - lastSubmissionDate) / (1000 * 60));
|
|
|
|
if (diffMinutes < 60) {
|
|
document.getElementById('last-submission').textContent = `${diffMinutes}m ago`;
|
|
} else if (diffMinutes < 1440) {
|
|
document.getElementById('last-submission').textContent = `${Math.floor(diffMinutes / 60)}h ago`;
|
|
} else {
|
|
document.getElementById('last-submission').textContent = `${Math.floor(diffMinutes / 1440)}d ago`;
|
|
}
|
|
} else {
|
|
document.getElementById('last-submission').textContent = 'Never';
|
|
}
|
|
|
|
// Update last updated time
|
|
document.getElementById('last-updated').textContent = `Last updated: ${new Date().toLocaleString()}`;
|
|
|
|
// Simulate backup list (in a real implementation, you would fetch this from the server)
|
|
const backupList = document.getElementById('backup-list');
|
|
backupList.innerHTML = '';
|
|
|
|
// Create dummy backup data (in production, this would come from the server)
|
|
const backups = [
|
|
{ date: new Date(Date.now() - 86400000), size: 1024 * 1024 * 2.5 },
|
|
{ date: new Date(Date.now() - 86400000 * 2), size: 1024 * 1024 * 2.3 },
|
|
{ date: new Date(Date.now() - 86400000 * 3), size: 1024 * 1024 * 2.1 }
|
|
];
|
|
|
|
backups.forEach(backup => {
|
|
const backupItem = document.createElement('div');
|
|
backupItem.className = 'backup-item';
|
|
backupItem.innerHTML = `
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<strong>${backup.date.toLocaleDateString()}</strong> at ${backup.date.toLocaleTimeString()}
|
|
<br>
|
|
<small>${formatBytes(backup.size)}</small>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-secondary">Download</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
backupList.appendChild(backupItem);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error updating dashboard:', error);
|
|
document.getElementById('server-status').textContent = 'Server: Offline';
|
|
document.getElementById('server-status-indicator').className = 'status-indicator status-error';
|
|
document.getElementById('db-status').textContent = 'Database: Unknown';
|
|
document.getElementById('db-status-indicator').className = 'status-indicator status-error';
|
|
}
|
|
}
|
|
|
|
// Initial update
|
|
updateDashboard();
|
|
|
|
// Setup refresh button
|
|
document.getElementById('refresh-btn').addEventListener('click', updateDashboard);
|
|
|
|
// Setup action buttons
|
|
document.getElementById('restart-server').addEventListener('click', async () => {
|
|
if (confirm('Are you sure you want to restart the server?')) {
|
|
try {
|
|
// In a real implementation, you would have an API endpoint to restart the server
|
|
alert('Server restart initiated. The dashboard will refresh in 10 seconds.');
|
|
setTimeout(updateDashboard, 10000);
|
|
} catch (error) {
|
|
console.error('Error restarting server:', error);
|
|
alert('Failed to restart server. Check the console for details.');
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('view-logs').addEventListener('click', () => {
|
|
// In a real implementation, you would have a logs viewer or redirect to a logs page
|
|
alert('Log viewer is not implemented in this demo. Check the server logs directory.');
|
|
});
|
|
|
|
document.getElementById('create-backup').addEventListener('click', async () => {
|
|
try {
|
|
// In a real implementation, you would have an API endpoint to create a backup
|
|
alert('Backup creation initiated. The dashboard will refresh in 5 seconds.');
|
|
setTimeout(updateDashboard, 5000);
|
|
} catch (error) {
|
|
console.error('Error creating backup:', error);
|
|
alert('Failed to create backup. Check the console for details.');
|
|
}
|
|
});
|
|
|
|
document.getElementById('export-all-data').addEventListener('click', async () => {
|
|
try {
|
|
// In a real implementation, you would have an API endpoint to export all data
|
|
alert('Data export is not implemented in this demo.');
|
|
} catch (error) {
|
|
console.error('Error exporting data:', error);
|
|
alert('Failed to export data. Check the console for details.');
|
|
}
|
|
});
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(updateDashboard, 30000);
|
|
</script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
|
</body>
|
|
</html>
|