Implementiere umfassendes Logging-System für WOL-Ereignisse

- Neue Log-Tabelle in der Datenbank
- Automatisches Logging bei WOL-Button-Klicks
- Dedizierte Logs-Seite mit Bootstrap-Design
- Tooltips mit letzten 5 WOL-Ereignissen pro PC
- API-Endpunkte für Log-Verwaltung
- Einheitliches Design zwischen Haupt- und Logs-Seite
- Vollständige Dokumentation des Logging-Systems
This commit is contained in:
2025-08-22 07:16:14 +02:00
parent 2f4920cc27
commit b6888ca5da
10 changed files with 723 additions and 6 deletions

80
web/static/logs.js Normal file
View File

@ -0,0 +1,80 @@
// Logs-Seite JavaScript
document.addEventListener('DOMContentLoaded', function() {
loadLogs();
});
// Alle Logs laden
async function loadLogs() {
try {
const response = await fetch('/api/logs');
const data = await response.json();
if (data.success) {
displayLogs(data.logs);
} else {
showError('Fehler beim Laden der Logs: ' + data.message);
}
} catch (error) {
showError('Fehler beim Laden der Logs: ' + error.message);
}
}
// Logs in der Tabelle anzeigen
function displayLogs(logs) {
const tableBody = document.getElementById('logsTableBody');
const loading = document.getElementById('loading');
const noLogs = document.getElementById('noLogs');
// Loading ausblenden
loading.style.display = 'none';
if (!logs || logs.length === 0) {
noLogs.style.display = 'block';
return;
}
// Tabelle leeren
tableBody.innerHTML = '';
// Logs hinzufügen
logs.forEach(log => {
const row = document.createElement('tr');
// Zeitstempel formatieren
const timestamp = new Date(log.timestamp);
const formattedTime = timestamp.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Auslöser übersetzen
const triggerText = log.trigger === 'button' ? 'Button-Klick' :
log.trigger === 'cron' ? 'Automatisch' : log.trigger;
row.innerHTML = `
<td>${formattedTime}</td>
<td>${escapeHtml(log.pc_name)}</td>
<td>${escapeHtml(log.mac)}</td>
<td>${escapeHtml(triggerText)}</td>
`;
tableBody.appendChild(row);
});
}
// Fehler anzeigen
function showError(message) {
const loading = document.getElementById('loading');
loading.innerHTML = `<p class="error">${escapeHtml(message)}</p>`;
}
// HTML-Escaping für Sicherheit
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}

View File

@ -145,7 +145,7 @@ class PCManager {
pcList.style.display = 'block';
tableBody.innerHTML = this.filteredPCs.map(pc => `
<tr>
<tr data-pc-id="${pc.id}">
<td><strong>${this.escapeHtml(pc.name)}</strong></td>
<td><code>${this.escapeHtml(pc.mac)}</code></td>
<td><code>${this.escapeHtml(pc.ip || 'N/A')}</code></td>
@ -174,6 +174,9 @@ class PCManager {
</td>
</tr>
`).join('');
// Tooltips für alle PC-Zeilen laden
this.loadTooltipsForAllPCs();
}
async addPC() {
@ -368,6 +371,68 @@ class PCManager {
input.value = value.toUpperCase();
}
// Tooltip für alle PCs laden
async loadTooltipsForAllPCs() {
for (const pc of this.filteredPCs) {
await this.loadTooltipForPC(pc.id);
}
}
// Tooltip für einen bestimmten PC laden
async loadTooltipForPC(pcId) {
try {
const response = await fetch(`/api/logs/pc/${pcId}/recent`);
const data = await response.json();
if (data.success) {
const tooltipContent = this.createTooltipContent(data.logs);
const row = document.querySelector(`tr[data-pc-id="${pcId}"]`);
if (row) {
// Tooltip-Attribut setzen
row.setAttribute('data-bs-toggle', 'tooltip');
row.setAttribute('data-bs-html', 'true');
row.setAttribute('title', tooltipContent);
// Bootstrap Tooltip initialisieren
new bootstrap.Tooltip(row, {
placement: 'top',
trigger: 'hover',
html: true
});
}
}
} catch (error) {
console.error(`Fehler beim Laden des Tooltips für PC ${pcId}:`, error);
}
}
// Tooltip-Inhalt erstellen
createTooltipContent(logs) {
if (!logs || logs.length === 0) {
return '<div class="text-muted">Keine WOL-Ereignisse</div>';
}
let content = '<div class="text-start"><strong>Letzte WOL-Ereignisse:</strong><br>';
logs.forEach(log => {
const timestamp = new Date(log.timestamp).toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
const triggerText = log.trigger === 'button' ? 'Button' :
log.trigger === 'cron' ? 'Auto' : log.trigger;
content += `${timestamp} (${triggerText})<br>`;
});
content += '</div>';
return content;
}
}
// PC Manager initialisieren, wenn die Seite geladen ist

View File

@ -172,3 +172,127 @@ body {
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
}
/* Navigation */
.navbar {
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.navbar-nav .nav-link {
color: var(--brand-primary) !important;
font-weight: 500;
padding: 10px 20px;
border-radius: 25px;
transition: all 0.3s ease;
}
.navbar-nav .nav-link:hover,
.navbar-nav .nav-link.active {
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
color: white !important;
transform: translateY(-1px);
}
/* Logs-Seite */
.logs-container {
position: relative;
min-height: 400px;
}
.logs-table-container {
background: white;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.logs-table {
margin: 0;
width: 100%;
}
.logs-table th {
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
color: white;
border: none;
padding: 15px;
font-weight: 600;
text-align: left;
}
.logs-table td {
padding: 12px 15px;
border-bottom: 1px solid #e9ecef;
vertical-align: middle;
}
.logs-table tbody tr:hover {
background-color: #f8f9fa;
}
.loading {
text-align: center;
padding: 40px;
color: var(--brand-primary);
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--brand-primary);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.no-logs {
text-align: center;
padding: 40px;
color: #6c757d;
}
.error {
color: #dc3545;
font-weight: 500;
}
/* Verbesserte Tooltips */
.tooltip {
font-size: 0.875rem;
}
.tooltip-inner {
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-accent) 100%);
color: white;
border-radius: 8px;
padding: 8px 12px;
max-width: 300px;
text-align: left;
}
.tooltip-arrow::before {
border-top-color: var(--brand-primary) !important;
}
/* Content Header */
.content-header {
text-align: center;
margin-bottom: 30px;
}
.content-header h2 {
color: var(--brand-primary);
margin-bottom: 10px;
}
.content-header p {
color: #6c757d;
font-size: 1.1rem;
}

View File

@ -21,6 +21,24 @@
<img src="/static/logo.png" alt="medisoftware Logo" />
<h1>{{.title}}</h1>
</div>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
<div class="container-fluid">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="/">
<i class="fas fa-desktop"></i> PCs
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logs">
<i class="fas fa-list-alt"></i> Logs
</a>
</li>
</ul>
</div>
</nav>
</div>
</div>

89
web/templates/logs.html Normal file
View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Medi-WOL - Logs</title>
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
<!-- Stylesheets -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="app-header mb-4">
<img src="/static/logo.png" alt="medisoftware Logo" />
<h1>Medi-WOL</h1>
</div>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
<div class="container-fluid">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">
<i class="fas fa-desktop"></i> PCs
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/logs">
<i class="fas fa-list-alt"></i> Logs
</a>
</li>
</ul>
</div>
</nav>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-list-alt"></i> Wake-on-LAN Logs
</h5>
</div>
<div class="card-body">
<div class="logs-container">
<div class="logs-table-container">
<table class="logs-table">
<thead>
<tr>
<th>Zeitstempel</th>
<th>PC-Name</th>
<th>MAC-Adresse</th>
<th>Auslöser</th>
</tr>
</thead>
<tbody id="logsTableBody">
<!-- Log-Einträge werden hier dynamisch eingefügt -->
</tbody>
</table>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Lade Logs...</p>
</div>
<div class="no-logs" id="noLogs" style="display: none;">
<p>Keine Log-Einträge gefunden.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/logs.js"></script>
</body>
</html>