Session-Timeout nach 5min.

Dieser Commit ist enthalten in:
2025-06-07 22:19:45 +02:00
Ursprung df5e0e0365
Commit 65fe0d4bf5
15 geänderte Dateien mit 508 neuen und 249 gelöschten Zeilen

Datei anzeigen

@@ -1,7 +1,7 @@
import os
import psycopg2
from psycopg2.extras import Json
from flask import Flask, render_template, request, redirect, session, url_for, send_file, jsonify
from flask import Flask, render_template, request, redirect, session, url_for, send_file, jsonify, flash
from flask_session import Session
from functools import wraps
from dotenv import load_dotenv
@@ -25,6 +25,13 @@ app.config['SECRET_KEY'] = os.urandom(24)
app.config['SESSION_TYPE'] = 'filesystem'
app.config['JSON_AS_ASCII'] = False # JSON-Ausgabe mit UTF-8
app.config['JSONIFY_MIMETYPE'] = 'application/json; charset=utf-8'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=5) # 5 Minuten Session-Timeout
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = False # Wird auf True gesetzt wenn HTTPS (intern läuft HTTP)
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['SESSION_COOKIE_NAME'] = 'admin_session'
# WICHTIG: Session-Cookie soll auch nach 5 Minuten ablaufen
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
Session(app)
# Backup-Konfiguration
@@ -57,6 +64,32 @@ def login_required(f):
def decorated_function(*args, **kwargs):
if 'logged_in' not in session:
return redirect(url_for('login'))
# Prüfe ob Session abgelaufen ist
if 'last_activity' in session:
last_activity = datetime.fromisoformat(session['last_activity'])
time_since_activity = datetime.now() - last_activity
# Debug-Logging
app.logger.info(f"Session check for {session.get('username', 'unknown')}: "
f"Last activity: {last_activity}, "
f"Time since: {time_since_activity.total_seconds()} seconds")
if time_since_activity > timedelta(minutes=5):
# Session abgelaufen - Logout
username = session.get('username', 'unbekannt')
app.logger.info(f"Session timeout for user {username} - auto logout")
# Audit-Log für automatischen Logout (vor session.clear()!)
try:
log_audit('AUTO_LOGOUT', 'session', additional_info={'reason': 'Session timeout (5 minutes)', 'username': username})
except:
pass
session.clear()
flash('Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.', 'warning')
return redirect(url_for('login'))
# Aktivität NICHT automatisch aktualisieren
# Nur bei expliziten Benutzeraktionen (wird vom Heartbeat gemacht)
return f(*args, **kwargs)
return decorated_function
@@ -533,8 +566,10 @@ def login():
if login_success:
# Erfolgreicher Login
session.permanent = True # Aktiviert das Timeout
session['logged_in'] = True
session['username'] = username
session['last_activity'] = datetime.now().isoformat()
reset_login_attempts(ip_address)
log_audit('LOGIN_SUCCESS', 'user',
additional_info=f"Erfolgreiche Anmeldung von IP: {ip_address}")
@@ -569,6 +604,21 @@ def logout():
session.pop('username', None)
return redirect(url_for('login'))
@app.route("/heartbeat", methods=['POST'])
@login_required
def heartbeat():
"""Endpoint für Session Keep-Alive - aktualisiert last_activity"""
# Aktualisiere last_activity nur wenn explizit angefordert
session['last_activity'] = datetime.now().isoformat()
# Force session save
session.modified = True
return jsonify({
'status': 'ok',
'last_activity': session['last_activity'],
'username': session.get('username')
})
@app.route("/")
@login_required
def dashboard():

Datei anzeigen

@@ -1,44 +1,35 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Audit-Log - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.audit-details {
font-size: 0.85em;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.json-display {
background-color: #f8f9fa;
padding: 5px;
border-radius: 3px;
font-family: monospace;
font-size: 0.8em;
max-height: 100px;
overflow-y: auto;
}
.action-CREATE { color: #28a745; }
.action-UPDATE { color: #007bff; }
.action-DELETE { color: #dc3545; }
.action-LOGIN { color: #17a2b8; }
.action-LOGOUT { color: #6c757d; }
.action-EXPORT { color: #ffc107; }
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Audit-Log{% endblock %}
{% block extra_css %}
<style>
.audit-details {
font-size: 0.85em;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.json-display {
background-color: #f8f9fa;
padding: 5px;
border-radius: 3px;
font-family: monospace;
font-size: 0.8em;
max-height: 100px;
overflow-y: auto;
}
.action-CREATE { color: #28a745; }
.action-UPDATE { color: #007bff; }
.action-DELETE { color: #dc3545; }
.action-LOGIN { color: #17a2b8; }
.action-LOGOUT { color: #6c757d; }
.action-AUTO_LOGOUT { color: #fd7e14; }
.action-EXPORT { color: #ffc107; }
</style>
{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>📋 Audit-Log</h2>
@@ -70,6 +61,7 @@
<option value="DELETE" {% if filter_action == 'DELETE' %}selected{% endif %}>🗑️ Gelöscht</option>
<option value="LOGIN" {% if filter_action == 'LOGIN' %}selected{% endif %}>🔑 Anmeldung</option>
<option value="LOGOUT" {% if filter_action == 'LOGOUT' %}selected{% endif %}>🚪 Abmeldung</option>
<option value="AUTO_LOGOUT" {% if filter_action == 'AUTO_LOGOUT' %}selected{% endif %}>⏰ Auto-Logout</option>
<option value="EXPORT" {% if filter_action == 'EXPORT' %}selected{% endif %}>📥 Export</option>
</select>
</div>
@@ -119,6 +111,7 @@
{% elif log[3] == 'DELETE' %}🗑️ Gelöscht
{% elif log[3] == 'LOGIN' %}🔑 Anmeldung
{% elif log[3] == 'LOGOUT' %}🚪 Abmeldung
{% elif log[3] == 'AUTO_LOGOUT' %}⏰ Auto-Logout
{% elif log[3] == 'EXPORT' %}📥 Export
{% else %}{{ log[3] }}
{% endif %}
@@ -230,7 +223,4 @@
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,27 +1,17 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Backup-Verwaltung - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.status-success { color: #28a745; }
.status-failed { color: #dc3545; }
.status-in_progress { color: #ffc107; }
.backup-actions { white-space: nowrap; }
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Backup-Verwaltung{% endblock %}
{% block extra_css %}
<style>
.status-success { color: #28a745; }
.status-failed { color: #dc3545; }
.status-in_progress { color: #ffc107; }
.backup-actions { white-space: nowrap; }
</style>
{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>💾 Backup-Verwaltung</h2>
@@ -197,8 +187,9 @@
</div>
</div>
</div>
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}
<script>
function createBackup() {
const btn = document.getElementById('createBackupBtn');
@@ -282,5 +273,4 @@ function confirmRestore() {
});
}
</script>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -0,0 +1,213 @@
<!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">
{% 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">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<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>
// 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>

Datei anzeigen

@@ -1,21 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Gesperrte IPs - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Gesperrte IPs{% endblock %}
{% block content %}
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>🔒 Gesperrte IPs</h1>
@@ -110,6 +97,4 @@
</div>
</div>
</div>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,21 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Kundenverwaltung - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Kundenverwaltung{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Kundenverwaltung</h2>
@@ -156,6 +143,4 @@
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,32 +1,22 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Dashboard - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.stat-card {
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-5px);
}
.status-aktiv { color: #28a745; }
.status-ablaufend { color: #ffc107; }
.status-abgelaufen { color: #dc3545; }
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block extra_css %}
<style>
.stat-card {
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-5px);
}
.status-aktiv { color: #28a745; }
.status-ablaufend { color: #ffc107; }
.status-abgelaufen { color: #dc3545; }
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Dashboard</h1>
@@ -303,5 +293,4 @@
</div>
</div>
</div>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,21 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Kunde bearbeiten - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Kunde bearbeiten{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Kunde bearbeiten</h2>
@@ -108,5 +95,4 @@
</div>
</div>
</div>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,21 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Lizenz bearbeiten - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Lizenz bearbeiten{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Lizenz bearbeiten</h2>
@@ -80,5 +67,4 @@
</div>
</div>
</div>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,21 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Admin Panel – Lizenzverwaltung</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Admin Panel{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Neue Lizenz erstellen</h2>
@@ -65,5 +52,4 @@
</div>
</form>
</div>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,26 +1,16 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Lizenzübersicht - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.status-aktiv { color: #28a745; }
.status-ablaufend { color: #ffc107; }
.status-abgelaufen { color: #dc3545; }
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Lizenzübersicht{% endblock %}
{% block extra_css %}
<style>
.status-aktiv { color: #28a745; }
.status-ablaufend { color: #ffc107; }
.status-abgelaufen { color: #dc3545; }
</style>
{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Lizenzübersicht</h2>
@@ -208,6 +198,4 @@
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
{% endblock %}

Datei anzeigen

@@ -1,26 +1,16 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Session-Tracking - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.session-active { background-color: #d4edda; }
.session-warning { background-color: #fff3cd; }
.session-inactive { background-color: #f8f9fa; }
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
<div class="d-flex align-items-center">
<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>
{% extends "base.html" %}
{% block title %}Session-Tracking{% endblock %}
{% block extra_css %}
<style>
.session-active { background-color: #d4edda; }
.session-warning { background-color: #fff3cd; }
.session-inactive { background-color: #f8f9fa; }
</style>
{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Session-Tracking</h2>
@@ -137,5 +127,4 @@
</div>
</div>
</div>
</body>
</html>
{% endblock %}