Files
R00TS/dashboard.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>