Files
Server_Monitorizare/templates/dashboard.html
Developer 376240fb06 Add configuration, utilities, and update server with enhanced monitoring features
- 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
2025-12-18 09:11:11 +02:00

367 lines
13 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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">&copy; 2023 Device Logs Dashboard. All rights reserved.</p>
</footer>
</body>
</html>