Session Management (Noch leer)

Dieser Commit ist enthalten in:
2025-06-07 14:49:40 +02:00
Ursprung fc2145bd19
Commit 6f491e3833
9 geänderte Dateien mit 449 neuen und 5 gelöschten Zeilen

Datei anzeigen

@@ -86,6 +86,10 @@ def dashboard():
""")
active_licenses = cur.fetchone()[0]
# Aktive Sessions
cur.execute("SELECT COUNT(*) FROM sessions WHERE is_active = TRUE")
active_sessions_count = cur.fetchone()[0]
# Abgelaufene Lizenzen
cur.execute("""
SELECT COUNT(*) FROM licenses
@@ -151,7 +155,8 @@ def dashboard():
'full_licenses': license_types.get('full', 0),
'test_licenses': license_types.get('test', 0),
'recent_licenses': recent_licenses,
'expiring_licenses': expiring_licenses
'expiring_licenses': expiring_licenses,
'active_sessions': active_sessions_count
}
return render_template("dashboard.html", stats=stats, username=session.get('username'))
@@ -478,5 +483,66 @@ def delete_customer(customer_id):
return redirect("/customers")
@app.route("/sessions")
@login_required
def sessions():
conn = get_connection()
cur = conn.cursor()
# Aktive Sessions abrufen
cur.execute("""
SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address,
s.user_agent, s.started_at, s.last_heartbeat,
EXTRACT(EPOCH FROM (NOW() - s.last_heartbeat))/60 as minutes_inactive
FROM sessions s
JOIN licenses l ON s.license_id = l.id
JOIN customers c ON l.customer_id = c.id
WHERE s.is_active = TRUE
ORDER BY s.last_heartbeat DESC
""")
active_sessions = cur.fetchall()
# Inaktive Sessions der letzten 24 Stunden
cur.execute("""
SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address,
s.started_at, s.ended_at,
EXTRACT(EPOCH FROM (s.ended_at - s.started_at))/60 as duration_minutes
FROM sessions s
JOIN licenses l ON s.license_id = l.id
JOIN customers c ON l.customer_id = c.id
WHERE s.is_active = FALSE
AND s.ended_at > NOW() - INTERVAL '24 hours'
ORDER BY s.ended_at DESC
LIMIT 50
""")
recent_sessions = cur.fetchall()
cur.close()
conn.close()
return render_template("sessions.html",
active_sessions=active_sessions,
recent_sessions=recent_sessions,
username=session.get('username'))
@app.route("/session/end/<int:session_id>", methods=["POST"])
@login_required
def end_session(session_id):
conn = get_connection()
cur = conn.cursor()
# Session beenden
cur.execute("""
UPDATE sessions
SET is_active = FALSE, ended_at = NOW()
WHERE id = %s AND is_active = TRUE
""", (session_id,))
conn.commit()
cur.close()
conn.close()
return redirect("/sessions")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=443, ssl_context='adhoc')

Datei anzeigen

@@ -17,3 +17,15 @@ CREATE TABLE IF NOT EXISTS licenses (
valid_until DATE NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS sessions (
id SERIAL PRIMARY KEY,
license_id INTEGER REFERENCES licenses(id),
session_id TEXT UNIQUE NOT NULL,
ip_address TEXT,
user_agent TEXT,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_heartbeat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ended_at TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
);

Datei anzeigen

@@ -23,6 +23,7 @@
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
<a href="/create" class="btn btn-primary"> Neue Lizenz</a>
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
</div>
</div>

Datei anzeigen

@@ -34,6 +34,7 @@
<a href="/create" class="btn btn-primary"> Neue Lizenz</a>
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
</div>
</div>
@@ -69,9 +70,9 @@
<div class="col-md-3">
<div class="card stat-card h-100">
<div class="card-body text-center">
<h5 class="card-title">⚠️ Ablaufend</h5>
<h2 class="text-warning">{{ stats.expiring_soon }}</h2>
<p class="text-muted mb-0">Nächste 30 Tage</p>
<h5 class="card-title">🟢 Sessions</h5>
<h2 class="text-success">{{ stats.active_sessions }}</h2>
<p class="text-muted mb-0">Aktive Nutzer</p>
</div>
</div>
</div>

Datei anzeigen

@@ -23,6 +23,7 @@
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
</div>
</div>

Datei anzeigen

@@ -28,6 +28,7 @@
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
<a href="/create" class="btn btn-primary"> Neue Lizenz</a>
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
</div>
</div>

Datei anzeigen

@@ -0,0 +1,139 @@
<!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>
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Session-Tracking</h2>
<div>
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
</div>
</div>
<!-- Aktive Sessions -->
<div class="card mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0">🟢 Aktive Sessions ({{ active_sessions|length }})</h5>
</div>
<div class="card-body">
{% if active_sessions %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Kunde</th>
<th>Lizenz</th>
<th>IP-Adresse</th>
<th>Gestartet</th>
<th>Letzter Heartbeat</th>
<th>Inaktiv seit</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for session in active_sessions %}
<tr class="{% if session[8] > 5 %}session-warning{% else %}session-active{% endif %}">
<td>{{ session[3] }}</td>
<td><small><code>{{ session[2][:12] }}...</code></small></td>
<td>{{ session[4] or '-' }}</td>
<td>{{ session[6].strftime('%d.%m %H:%M') }}</td>
<td>{{ session[7].strftime('%d.%m %H:%M') }}</td>
<td>
{% if session[8] < 1 %}
<span class="badge bg-success">Aktiv</span>
{% elif session[8] < 5 %}
<span class="badge bg-warning">{{ session[8]|round|int }} Min.</span>
{% else %}
<span class="badge bg-danger">{{ session[8]|round|int }} Min.</span>
{% endif %}
</td>
<td>
<form method="post" action="/session/end/{{ session[0] }}" style="display: inline;">
<button type="submit" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Session wirklich beenden?');">
⏹️ Beenden
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<small class="text-muted">
Sessions gelten als inaktiv nach 5 Minuten ohne Heartbeat
</small>
{% else %}
<p class="text-muted mb-0">Keine aktiven Sessions vorhanden.</p>
{% endif %}
</div>
</div>
<!-- Beendete Sessions -->
<div class="card">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">⏸️ Beendete Sessions (letzte 24 Stunden)</h5>
</div>
<div class="card-body">
{% if recent_sessions %}
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Kunde</th>
<th>Lizenz</th>
<th>IP-Adresse</th>
<th>Gestartet</th>
<th>Beendet</th>
<th>Dauer</th>
</tr>
</thead>
<tbody>
{% for session in recent_sessions %}
<tr class="session-inactive">
<td>{{ session[3] }}</td>
<td><small><code>{{ session[2][:12] }}...</code></small></td>
<td>{{ session[4] or '-' }}</td>
<td>{{ session[5].strftime('%d.%m %H:%M') }}</td>
<td>{{ session[6].strftime('%d.%m %H:%M') }}</td>
<td>
{% if session[7] < 60 %}
{{ session[7]|round|int }} Min.
{% else %}
{{ (session[7]/60)|round(1) }} Std.
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted mb-0">Keine beendeten Sessions in den letzten 24 Stunden.</p>
{% endif %}
</div>
</div>
</div>
</body>
</html>