Dateien
Hetzner-Backup/v2_adminpanel/templates/base.html
2025-06-08 21:01:41 +02:00

217 Zeilen
7.6 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Admin Panel{% endblock %} - Lizenzverwaltung</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
{% block extra_css %}{% endblock %}
<style>
#session-timer {
font-family: monospace;
font-weight: bold;
font-size: 1.1rem;
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
transition: all 0.3s ease;
}
.timer-normal {
background-color: #28a745;
color: white;
}
.timer-warning {
background-color: #ffc107;
color: #000;
}
.timer-danger {
background-color: #dc3545;
color: white;
animation: pulse 1s infinite;
}
.timer-critical {
background-color: #dc3545;
color: white;
animation: blink 0.5s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.session-warning-modal {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<a href="/" class="navbar-brand text-decoration-none">🎛️ AccountForger - Admin Panel</a>
<div class="d-flex align-items-center">
<div id="session-timer" class="timer-normal me-3">
⏱️ <span id="timer-display">5:00</span>
</div>
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<!-- Session Warning Modal -->
<div id="session-warning" class="session-warning-modal" style="display: none;">
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>⚠️ Session läuft ab!</strong><br>
Ihre Session läuft in weniger als 1 Minute ab.<br>
<button type="button" class="btn btn-sm btn-success mt-2" onclick="extendSession()">
Session verlängern
</button>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
// Session-Timer Konfiguration
const SESSION_TIMEOUT = 5 * 60; // 5 Minuten in Sekunden
let timeRemaining = SESSION_TIMEOUT;
let timerInterval;
let warningShown = false;
let lastActivity = Date.now();
// Timer Display Update
function updateTimerDisplay() {
const minutes = Math.floor(timeRemaining / 60);
const seconds = timeRemaining % 60;
const display = `${minutes}:${seconds.toString().padStart(2, '0')}`;
document.getElementById('timer-display').textContent = display;
// Timer-Farbe ändern
const timerElement = document.getElementById('session-timer');
timerElement.className = timerElement.className.replace(/timer-\w+/, '');
if (timeRemaining <= 30) {
timerElement.classList.add('timer-critical');
} else if (timeRemaining <= 60) {
timerElement.classList.add('timer-danger');
if (!warningShown) {
showSessionWarning();
warningShown = true;
}
} else if (timeRemaining <= 120) {
timerElement.classList.add('timer-warning');
} else {
timerElement.classList.add('timer-normal');
warningShown = false;
hideSessionWarning();
}
}
// Session Warning anzeigen
function showSessionWarning() {
document.getElementById('session-warning').style.display = 'block';
}
// Session Warning verstecken
function hideSessionWarning() {
document.getElementById('session-warning').style.display = 'none';
}
// Timer zurücksetzen
function resetTimer() {
timeRemaining = SESSION_TIMEOUT;
lastActivity = Date.now();
updateTimerDisplay();
}
// Session verlängern
function extendSession() {
fetch('/heartbeat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'ok') {
resetTimer();
}
})
.catch(error => console.error('Heartbeat error:', error));
}
// Timer Countdown
function countdown() {
timeRemaining--;
updateTimerDisplay();
if (timeRemaining <= 0) {
clearInterval(timerInterval);
window.location.href = '/logout';
}
}
// Aktivitäts-Tracking
function trackActivity() {
const now = Date.now();
// Nur wenn mehr als 5 Sekunden seit letzter Aktivität
if (now - lastActivity > 5000) {
lastActivity = now;
extendSession(); // resetTimer() wird in extendSession nach erfolgreicher Response aufgerufen
}
}
// Event Listeners für Benutzeraktivität
document.addEventListener('click', trackActivity);
document.addEventListener('keypress', trackActivity);
document.addEventListener('mousemove', () => {
const now = Date.now();
// Mausbewegung nur alle 30 Sekunden tracken
if (now - lastActivity > 30000) {
trackActivity();
}
});
// AJAX Interceptor für automatische Session-Verlängerung
const originalFetch = window.fetch;
window.fetch = function(...args) {
// Nur für non-heartbeat requests den Timer verlängern
if (!args[0].includes('/heartbeat')) {
trackActivity();
}
return originalFetch.apply(this, args);
};
// Timer starten
timerInterval = setInterval(countdown, 1000);
updateTimerDisplay();
// Initial Heartbeat
extendSession();
</script>
{% block extra_js %}{% endblock %}
</body>
</html>