- Add config.py for environment configuration management - Add utils.py with utility functions - Add .env.example for environment variable reference - Add routes_example.py as route reference - Add login.html template for authentication - Update server.py with enhancements - Update all dashboard and log templates - Move documentation to 'explanations and old code' directory - Update database schema
367 lines
13 KiB
HTML
367 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Device Logs Dashboard</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||
<style>
|
||
body {
|
||
background-color: #f8f9fa;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
h1 {
|
||
text-align: center;
|
||
color: #343a40;
|
||
}
|
||
|
||
/* Sidebar Styles */
|
||
.sidebar {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 56px; /* Height of navbar */
|
||
width: 250px;
|
||
height: calc(100vh - 56px);
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 20px;
|
||
transform: translateX(-100%);
|
||
transition: transform 0.3s ease;
|
||
z-index: 999;
|
||
overflow-y: auto;
|
||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.sidebar.open {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.sidebar-title {
|
||
color: white;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.sidebar-menu {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.sidebar-menu a,
|
||
.sidebar-menu button {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
border-radius: 5px;
|
||
text-decoration: none;
|
||
transition: all 0.3s;
|
||
text-align: left;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.sidebar-menu a:hover,
|
||
.sidebar-menu button:hover {
|
||
background: rgba(255, 255, 255, 0.4);
|
||
border-color: rgba(255, 255, 255, 0.6);
|
||
transform: translateX(5px);
|
||
}
|
||
|
||
.sidebar-menu i {
|
||
margin-right: 10px;
|
||
width: 20px;
|
||
}
|
||
|
||
.toggle-sidebar {
|
||
position: fixed;
|
||
left: 10px;
|
||
top: 70px;
|
||
z-index: 1000;
|
||
background: #667eea;
|
||
border: none;
|
||
color: white;
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.toggle-sidebar:hover {
|
||
background: #764ba2;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.main-content {
|
||
margin-left: 0;
|
||
transition: margin-left 0.3s ease;
|
||
}
|
||
|
||
.main-content.sidebar-open {
|
||
margin-left: 250px;
|
||
}
|
||
|
||
.table-container {
|
||
background-color: #ffffff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
padding: 20px;
|
||
}
|
||
.table {
|
||
margin-bottom: 0;
|
||
table-layout: fixed;
|
||
width: 100%;
|
||
}
|
||
.table th, .table td {
|
||
text-align: center;
|
||
word-wrap: break-word;
|
||
}
|
||
.table th:nth-child(1), .table td:nth-child(1) {
|
||
width: 20%;
|
||
}
|
||
.table th:nth-child(2), .table td:nth-child(2) {
|
||
width: 20%;
|
||
}
|
||
.table th:nth-child(3), .table td:nth-child(3) {
|
||
width: 20%;
|
||
}
|
||
.table th:nth-child(4), .table td:nth-child(4) {
|
||
width: 20%;
|
||
}
|
||
.table th:nth-child(5), .table td:nth-child(5) {
|
||
width: 20%;
|
||
}
|
||
.refresh-timer {
|
||
text-align: center;
|
||
margin-bottom: 10px;
|
||
font-size: 1.2rem;
|
||
color: #343a40;
|
||
}
|
||
</style>
|
||
<script>
|
||
// Sidebar toggle
|
||
function toggleSidebar() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
const mainContent = document.getElementById('main-content');
|
||
const toggleBtn = document.getElementById('toggle-btn');
|
||
|
||
sidebar.classList.toggle('open');
|
||
mainContent.classList.toggle('sidebar-open');
|
||
|
||
// Rotate arrow
|
||
toggleBtn.style.transform = sidebar.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
|
||
}
|
||
|
||
// Close sidebar when clicking on a link
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const sidebarLinks = document.querySelectorAll('.sidebar-menu a, .sidebar-menu button');
|
||
sidebarLinks.forEach(link => {
|
||
link.addEventListener('click', function() {
|
||
document.getElementById('sidebar').classList.remove('open');
|
||
document.getElementById('main-content').classList.remove('sidebar-open');
|
||
document.getElementById('toggle-btn').style.transform = 'rotate(0deg)';
|
||
});
|
||
});
|
||
});
|
||
|
||
// Countdown timer for refresh
|
||
let countdown = 30; // 30 seconds
|
||
function updateTimer() {
|
||
document.getElementById('refresh-timer').innerText = countdown;
|
||
countdown--;
|
||
if (countdown < 0) {
|
||
location.reload(); // Refresh the page
|
||
}
|
||
}
|
||
setInterval(updateTimer, 1000); // Update every second
|
||
|
||
// Database reset functionality
|
||
async function resetDatabase(event) {
|
||
try {
|
||
// First, get database statistics
|
||
const statsResponse = await fetch('/database_stats');
|
||
const stats = await statsResponse.json();
|
||
|
||
if (!stats.success) {
|
||
alert('❌ Error getting database statistics:\n' + stats.error);
|
||
return;
|
||
}
|
||
|
||
const totalLogs = stats.total_logs;
|
||
const uniqueDevices = stats.unique_devices;
|
||
|
||
if (totalLogs <= 1) { // Only reset log exists
|
||
alert('ℹ️ Database is already empty!\nNo user logs to delete.');
|
||
return;
|
||
}
|
||
|
||
// Show confirmation dialog with detailed statistics
|
||
const confirmed = confirm(
|
||
`⚠️ WARNING: Database Reset Operation ⚠️\n\n` +
|
||
`This will permanently delete:\n` +
|
||
`• ${totalLogs} log entries\n` +
|
||
`• Data from ${uniqueDevices} unique devices\n` +
|
||
`• Date range: ${stats.earliest_log || 'N/A'} to ${stats.latest_log || 'N/A'}\n\n` +
|
||
`⚠️ ALL DEVICE HISTORY WILL BE LOST ⚠️\n\n` +
|
||
`This action cannot be undone!\n\n` +
|
||
`Are you absolutely sure you want to proceed?`
|
||
);
|
||
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
|
||
// Second confirmation for safety
|
||
const doubleConfirmed = confirm(
|
||
`🚨 FINAL CONFIRMATION 🚨\n\n` +
|
||
`You are about to permanently DELETE:\n` +
|
||
`✗ ${totalLogs} log entries\n` +
|
||
`✗ ${uniqueDevices} device histories\n\n` +
|
||
`This is your LAST CHANCE to cancel!\n\n` +
|
||
`Click OK to proceed with deletion.`
|
||
);
|
||
|
||
if (!doubleConfirmed) {
|
||
return;
|
||
}
|
||
|
||
// Show loading indicator
|
||
const button = event ? event.target : document.querySelector('button[onclick*="resetDatabase"]');
|
||
const originalText = button ? button.innerHTML : '';
|
||
if (button) {
|
||
button.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Clearing Database...';
|
||
button.disabled = true;
|
||
}
|
||
|
||
// Send reset request
|
||
const resetResponse = await fetch('/reset_database', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
const result = await resetResponse.json();
|
||
|
||
if (result.success) {
|
||
alert(
|
||
`✅ Database Reset Completed Successfully!\n\n` +
|
||
`Operation Summary:\n` +
|
||
`• ${result.deleted_count} log entries deleted\n` +
|
||
`• Database schema reinitialized\n` +
|
||
`• Reset timestamp: ${result.timestamp}\n\n` +
|
||
`The dashboard will refresh to show the clean database.`
|
||
);
|
||
location.reload(); // Refresh to show empty database
|
||
} else {
|
||
alert('❌ Database Reset Failed:\n' + result.error);
|
||
if (button) {
|
||
button.innerHTML = originalText;
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
alert('❌ Network Error:\n' + error.message);
|
||
// Restore button if it was changed
|
||
try {
|
||
const button = event ? event.target : document.querySelector('button[onclick*="resetDatabase"]');
|
||
if (button) {
|
||
button.innerHTML = '<i class="fas fa-trash-alt"></i> Clear Database';
|
||
button.disabled = false;
|
||
}
|
||
} catch (e) {
|
||
// Ignore if button restoration fails
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<!-- User Header -->
|
||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||
<div class="container-fluid">
|
||
<span class="navbar-brand">🖥️ Server Monitoring</span>
|
||
<div class="ms-auto d-flex align-items-center gap-3">
|
||
<span class="text-white">Welcome, <strong>{{ current_user.username }}</strong></span>
|
||
<a href="/logout" class="btn btn-sm btn-danger">Logout</a>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Sidebar Toggle Button -->
|
||
<button class="toggle-sidebar" id="toggle-btn" onclick="toggleSidebar()" title="Toggle Menu">
|
||
<i class="fas fa-chevron-right" style="font-size: 20px; transition: transform 0.3s;"></i>
|
||
</button>
|
||
|
||
<!-- Sidebar Menu -->
|
||
<div class="sidebar" id="sidebar">
|
||
<div class="sidebar-title">
|
||
<span>Quick Actions</span>
|
||
<i class="fas fa-times" onclick="toggleSidebar()" style="cursor: pointer; font-size: 20px;"></i>
|
||
</div>
|
||
<div class="sidebar-menu">
|
||
<a href="/unique_devices" class="btn btn-link">
|
||
<i class="fas fa-microchip"></i> Unique Devices
|
||
</a>
|
||
<a href="/device_management" class="btn btn-link">
|
||
<i class="fas fa-cogs"></i> Device Management
|
||
</a>
|
||
<a href="/server_logs" class="btn btn-link" title="View server operations and system logs">
|
||
<i class="fas fa-server"></i> Server Logs
|
||
</a>
|
||
<button class="btn btn-link" onclick="resetDatabase(event)" title="Clear all logs and reset database">
|
||
<i class="fas fa-trash-alt"></i> Clear Database
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="main-content" id="main-content">
|
||
<div class="container mt-5">
|
||
<h1 class="mb-4">Device Logs Dashboard</h1>
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<div class="refresh-timer">
|
||
Time until refresh: <span id="refresh-timer">30</span> seconds
|
||
</div>
|
||
</div>
|
||
<div class="table-container">
|
||
<table class="table table-striped table-bordered">
|
||
<thead class="table-dark">
|
||
<tr>
|
||
<th>Hostname</th>
|
||
<th>Device IP</th>
|
||
<th>Nume Masa</th>
|
||
<th>Timestamp</th>
|
||
<th>Event Description</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for log in logs %}
|
||
<tr>
|
||
<td><a href="hostname_logs/{{ log[0] }}">{{ log[0] }}</a></td>
|
||
<td>{{ log[1] }}</td>
|
||
<td><a href="/device_logs/{{ log[2] }}">{{ log[2] }}</a></td>
|
||
<td>{{ log[3] }}</td>
|
||
<td>{{ log[4] }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<footer>
|
||
<p class="text-center mt-4">© 2023 Device Logs Dashboard. All rights reserved.</p>
|
||
</footer>
|
||
</body>
|
||
</html> |