watchdog-docker/app/templates/dashboard.html

364 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Watchdog Docker</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background-color: #f5f6fa;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.navbar-brand {
font-weight: 600;
font-size: 24px;
}
.stats-card {
border: none;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
}
.stats-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
}
.stats-icon {
font-size: 48px;
opacity: 0.8;
}
.events-table-card {
border: none;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.badge-dhcp_lease {
background-color: #0d6efd;
}
.badge-new_device {
background-color: #dc3545;
}
.badge-interface_status {
background-color: #ffc107;
color: #000;
}
.badge-gateway_status {
background-color: #fd7e14;
}
.table-hover tbody tr:hover {
background-color: #f8f9fa;
}
.filter-section {
background-color: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.last-updated {
font-size: 12px;
color: #6c757d;
}
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-dark mb-4">
<div class="container-fluid">
<span class="navbar-brand mb-0 h1">
<i class="bi bi-shield-check"></i> Watchdog Docker
</span>
<div class="d-flex align-items-center">
<span class="text-white me-3">
<i class="bi bi-person-circle"></i> {{ current_user.id }}
</span>
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</div>
</div>
</nav>
<div class="container-fluid">
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card stats-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Gesamt Events</h6>
<h2 class="mb-0">{{ stats.total_events }}</h2>
</div>
<div class="stats-icon text-primary">
<i class="bi bi-database"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Heute</h6>
<h2 class="mb-0">{{ stats.events_today }}</h2>
</div>
<div class="stats-icon text-success">
<i class="bi bi-calendar-check"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Letzte Stunde</h6>
<h2 class="mb-0">{{ stats.events_last_hour }}</h2>
</div>
<div class="stats-icon text-warning">
<i class="bi bi-clock-history"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card stats-card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-1">Bekannte Geräte</h6>
<h2 class="mb-0">{{ stats.known_devices }}</h2>
</div>
<div class="stats-icon text-info">
<i class="bi bi-router"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="filter-section">
<div class="row align-items-center">
<div class="col-md-3">
<label for="filterType" class="form-label mb-1">Event-Typ</label>
<select class="form-select form-select-sm" id="filterType">
<option value="">Alle Typen</option>
<option value="dhcp_lease">DHCP Lease</option>
<option value="new_device">Neues Gerät</option>
<option value="interface_status">Interface Status</option>
<option value="gateway_status">Gateway Status</option>
</select>
</div>
<div class="col-md-3">
<label for="filterInterface" class="form-label mb-1">Interface</label>
<select class="form-select form-select-sm" id="filterInterface">
<option value="">Alle Interfaces</option>
{% for interface in config.monitoring.monitored_interfaces %}
<option value="{{ interface }}">{{ interface }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label mb-1">Auto-Refresh</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="autoRefresh" checked>
<label class="form-check-label" for="autoRefresh">
Alle 10 Sekunden
</label>
</div>
</div>
<div class="col-md-3 text-end">
<label class="form-label mb-1 d-block">Aktualisierung</label>
<button class="btn btn-primary btn-sm" onclick="refreshEvents()">
<i class="bi bi-arrow-clockwise"></i> Jetzt aktualisieren
</button>
</div>
</div>
</div>
<!-- Events Table -->
<div class="card events-table-card">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-list-ul"></i> Aktuelle Events
</h5>
<span class="last-updated" id="lastUpdated">
Zuletzt aktualisiert: {{ stats.last_updated if stats.last_updated else 'Nie' }}
</span>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0" id="eventsTable">
<thead class="table-light">
<tr>
<th style="width: 180px;">Zeitpunkt</th>
<th style="width: 150px;">Typ</th>
<th style="width: 100px;">Interface</th>
<th>Details</th>
</tr>
</thead>
<tbody id="eventsTableBody">
{% for event in events %}
<tr data-type="{{ event.type }}" data-interface="{{ event.interface }}">
<td>
<small>{{ event.timestamp }}</small>
</td>
<td>
<span class="badge badge-{{ event.type }}">
{{ event.type.replace('_', ' ').upper() }}
</span>
</td>
<td>
<span class="badge bg-secondary">{{ event.interface or 'N/A' }}</span>
</td>
<td>
{{ event.details }}
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="text-center text-muted py-4">
<i class="bi bi-inbox"></i> Keine Events vorhanden
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let autoRefreshInterval;
// Filter events
function filterEvents() {
const typeFilter = document.getElementById('filterType').value;
const interfaceFilter = document.getElementById('filterInterface').value;
const rows = document.querySelectorAll('#eventsTableBody tr');
rows.forEach(row => {
const type = row.getAttribute('data-type');
const iface = row.getAttribute('data-interface');
const typeMatch = !typeFilter || type === typeFilter;
const interfaceMatch = !interfaceFilter || iface === interfaceFilter;
if (typeMatch && interfaceMatch) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}
// Refresh events via AJAX
async function refreshEvents() {
try {
const typeFilter = document.getElementById('filterType').value;
const interfaceFilter = document.getElementById('filterInterface').value;
let url = '/api/events?limit=100';
if (typeFilter) url += `&type=${typeFilter}`;
if (interfaceFilter) url += `&interface=${interfaceFilter}`;
const response = await fetch(url);
const events = await response.json();
// Update table
const tbody = document.getElementById('eventsTableBody');
if (events.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="4" class="text-center text-muted py-4">
<i class="bi bi-inbox"></i> Keine Events vorhanden
</td>
</tr>
`;
} else {
tbody.innerHTML = events.map(event => `
<tr data-type="${event.type}" data-interface="${event.interface || ''}">
<td><small>${event.timestamp}</small></td>
<td>
<span class="badge badge-${event.type}">
${event.type.replace('_', ' ').toUpperCase()}
</span>
</td>
<td>
<span class="badge bg-secondary">${event.interface || 'N/A'}</span>
</td>
<td>${event.details}</td>
</tr>
`).join('');
}
// Update timestamp
const now = new Date();
document.getElementById('lastUpdated').textContent =
`Zuletzt aktualisiert: ${now.toLocaleString('de-DE')}`;
// Refresh stats
const statsResponse = await fetch('/api/stats');
const stats = await statsResponse.json();
updateStats(stats);
} catch (error) {
console.error('Error refreshing events:', error);
}
}
// Update statistics
function updateStats(stats) {
const statsElements = document.querySelectorAll('.stats-card h2');
if (statsElements.length >= 4) {
statsElements[0].textContent = stats.total_events || 0;
statsElements[1].textContent = stats.events_today || 0;
statsElements[2].textContent = stats.events_last_hour || 0;
statsElements[3].textContent = stats.known_devices || 0;
}
}
// Auto-refresh toggle
function toggleAutoRefresh() {
const checkbox = document.getElementById('autoRefresh');
if (checkbox.checked) {
// Start auto-refresh every 10 seconds
autoRefreshInterval = setInterval(refreshEvents, 10000);
} else {
// Stop auto-refresh
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
}
}
}
// Event listeners
document.getElementById('filterType').addEventListener('change', filterEvents);
document.getElementById('filterInterface').addEventListener('change', filterEvents);
document.getElementById('autoRefresh').addEventListener('change', toggleAutoRefresh);
// Start auto-refresh on page load
toggleAutoRefresh();
</script>
</body>
</html>