Session Management (Noch leer)
Dieser Commit ist enthalten in:
31
JOURNAL.md
31
JOURNAL.md
@@ -274,4 +274,33 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker
|
|||||||
- SQL WHERE-Klauseln für Filter
|
- SQL WHERE-Klauseln für Filter
|
||||||
- LIMIT/OFFSET für Pagination
|
- LIMIT/OFFSET für Pagination
|
||||||
- URL-Parameter bleiben bei Navigation erhalten
|
- URL-Parameter bleiben bei Navigation erhalten
|
||||||
- Responsive Bootstrap-Komponenten
|
- Responsive Bootstrap-Komponenten
|
||||||
|
|
||||||
|
### 2025-01-06 - Session-Tracking implementiert
|
||||||
|
- Neue Tabelle für Session-Verwaltung
|
||||||
|
- Anzeige aktiver und beendeter Sessions
|
||||||
|
- Manuelles Beenden von Sessions möglich
|
||||||
|
- Dashboard zeigt Anzahl aktiver Sessions
|
||||||
|
|
||||||
|
**Neue Features:**
|
||||||
|
- **Sessions-Tabelle**: Speichert Session-ID, IP, User-Agent, Zeitstempel
|
||||||
|
- **Aktive Sessions**: Zeigt alle laufenden Sessions mit Inaktivitätszeit
|
||||||
|
- **Session-Historie**: Letzte 24 Stunden beendeter Sessions
|
||||||
|
- **Session beenden**: Admins können Sessions manuell beenden
|
||||||
|
- **Farbcodierung**: Grün (aktiv), Gelb (>5 Min inaktiv), Rot (lange inaktiv)
|
||||||
|
|
||||||
|
**Geänderte/Neue Dateien:**
|
||||||
|
- v2_adminpanel/init.sql (sessions Tabelle hinzugefügt)
|
||||||
|
- v2_adminpanel/app.py (sessions() und end_session() Routen)
|
||||||
|
- v2_adminpanel/templates/sessions.html (neu erstellt)
|
||||||
|
- v2_adminpanel/templates/dashboard.html (Session-Statistik)
|
||||||
|
- Alle Templates (Session-Navigation hinzugefügt)
|
||||||
|
|
||||||
|
**Technische Details:**
|
||||||
|
- Heartbeat-basiertes Tracking (last_heartbeat)
|
||||||
|
- Automatische Inaktivitätsberechnung
|
||||||
|
- Session-Dauer Berechnung
|
||||||
|
- Responsive Tabellen mit Bootstrap
|
||||||
|
|
||||||
|
**Hinweis:**
|
||||||
|
Die Session-Daten werden erst gefüllt, wenn der License Server API implementiert ist und Clients sich verbinden.
|
||||||
@@ -86,6 +86,10 @@ def dashboard():
|
|||||||
""")
|
""")
|
||||||
active_licenses = cur.fetchone()[0]
|
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
|
# Abgelaufene Lizenzen
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT COUNT(*) FROM licenses
|
SELECT COUNT(*) FROM licenses
|
||||||
@@ -151,7 +155,8 @@ def dashboard():
|
|||||||
'full_licenses': license_types.get('full', 0),
|
'full_licenses': license_types.get('full', 0),
|
||||||
'test_licenses': license_types.get('test', 0),
|
'test_licenses': license_types.get('test', 0),
|
||||||
'recent_licenses': recent_licenses,
|
'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'))
|
return render_template("dashboard.html", stats=stats, username=session.get('username'))
|
||||||
@@ -478,5 +483,66 @@ def delete_customer(customer_id):
|
|||||||
|
|
||||||
return redirect("/customers")
|
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__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=443, ssl_context='adhoc')
|
app.run(host="0.0.0.0", port=443, ssl_context='adhoc')
|
||||||
|
|||||||
@@ -17,3 +17,15 @@ CREATE TABLE IF NOT EXISTS licenses (
|
|||||||
valid_until DATE NOT NULL,
|
valid_until DATE NOT NULL,
|
||||||
is_active BOOLEAN DEFAULT TRUE
|
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
|
||||||
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
||||||
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
||||||
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
||||||
|
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
||||||
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
||||||
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
||||||
|
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,9 +70,9 @@
|
|||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stat-card h-100">
|
<div class="card stat-card h-100">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<h5 class="card-title">⚠️ Ablaufend</h5>
|
<h5 class="card-title">🟢 Sessions</h5>
|
||||||
<h2 class="text-warning">{{ stats.expiring_soon }}</h2>
|
<h2 class="text-success">{{ stats.active_sessions }}</h2>
|
||||||
<p class="text-muted mb-0">Nächste 30 Tage</p>
|
<p class="text-muted mb-0">Aktive Nutzer</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
||||||
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
<a href="/licenses" class="btn btn-secondary">📋 Lizenzen</a>
|
||||||
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
||||||
|
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
<a href="/" class="btn btn-secondary">📊 Dashboard</a>
|
||||||
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
<a href="/create" class="btn btn-primary">➕ Neue Lizenz</a>
|
||||||
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
<a href="/customers" class="btn btn-secondary">👥 Kunden</a>
|
||||||
|
<a href="/sessions" class="btn btn-secondary">🟢 Sessions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
139
v2_adminpanel/templates/sessions.html
Normale Datei
139
v2_adminpanel/templates/sessions.html
Normale Datei
@@ -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>
|
||||||
194
v2_testing/test_sessions.py
Normale Datei
194
v2_testing/test_sessions.py
Normale Datei
@@ -0,0 +1,194 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Disable SSL warnings
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
base_url = "https://localhost:443"
|
||||||
|
admin_user = {"username": "rac00n", "password": "1248163264"}
|
||||||
|
|
||||||
|
def login(session):
|
||||||
|
"""Login to admin panel"""
|
||||||
|
login_data = {
|
||||||
|
"username": admin_user["username"],
|
||||||
|
"password": admin_user["password"]
|
||||||
|
}
|
||||||
|
response = session.post(f"{base_url}/login", data=login_data, verify=False, allow_redirects=False)
|
||||||
|
return response.status_code == 302
|
||||||
|
|
||||||
|
def test_session_table():
|
||||||
|
"""Test if session table exists and structure"""
|
||||||
|
print("1. Checking Session Table Structure:")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||||
|
"-c", "\\d sessions"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
|
if "Table \"public.sessions\"" in result.stdout:
|
||||||
|
print("✓ Sessions table exists")
|
||||||
|
print("\nTable structure:")
|
||||||
|
print(result.stdout)
|
||||||
|
else:
|
||||||
|
print("✗ Sessions table not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if table is empty
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
|
||||||
|
"-c", "SELECT COUNT(*) FROM sessions;"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
|
count = int(result.stdout.strip())
|
||||||
|
print(f"\nCurrent session count: {count}")
|
||||||
|
if count == 0:
|
||||||
|
print("✓ Session table is empty (as expected - License Server not implemented)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def test_session_page():
|
||||||
|
"""Test the sessions management page"""
|
||||||
|
session = requests.Session()
|
||||||
|
|
||||||
|
if not login(session):
|
||||||
|
return "✗ Failed to login"
|
||||||
|
|
||||||
|
print("\n2. Testing Sessions Page:")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
response = session.get(f"{base_url}/sessions", verify=False)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✓ Sessions page accessible")
|
||||||
|
|
||||||
|
content = response.text
|
||||||
|
|
||||||
|
# Check for expected elements
|
||||||
|
checks = [
|
||||||
|
("Aktive Sessions", "Active sessions section"),
|
||||||
|
("Letzte Sessions", "Recent sessions section"),
|
||||||
|
("IP-Adresse", "IP address column"),
|
||||||
|
("User Agent", "User agent column"),
|
||||||
|
("Heartbeat", "Heartbeat info"),
|
||||||
|
("Session beenden", "End session button")
|
||||||
|
]
|
||||||
|
|
||||||
|
for check_text, description in checks:
|
||||||
|
if check_text in content:
|
||||||
|
print(f"✓ Found: {description}")
|
||||||
|
else:
|
||||||
|
print(f"✗ Missing: {description}")
|
||||||
|
|
||||||
|
# Check for empty state
|
||||||
|
if "Keine aktiven Sessions" in content:
|
||||||
|
print("✓ Shows empty state for active sessions")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"✗ Failed to access sessions page: Status {response.status_code}")
|
||||||
|
|
||||||
|
def test_dashboard_session_count():
|
||||||
|
"""Test if dashboard shows session count"""
|
||||||
|
session = requests.Session()
|
||||||
|
|
||||||
|
if not login(session):
|
||||||
|
return "✗ Failed to login"
|
||||||
|
|
||||||
|
print("\n3. Testing Dashboard Session Counter:")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
response = session.get(f"{base_url}/", verify=False)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
content = response.text
|
||||||
|
|
||||||
|
if "Aktive Sessions" in content or "sessions" in content.lower():
|
||||||
|
print("✓ Dashboard shows session information")
|
||||||
|
|
||||||
|
# Check if it shows 0
|
||||||
|
if "0" in content:
|
||||||
|
print("✓ Shows 0 active sessions (correct)")
|
||||||
|
else:
|
||||||
|
print("✗ No session information on dashboard")
|
||||||
|
|
||||||
|
def simulate_session_data():
|
||||||
|
"""Add test session data directly to database"""
|
||||||
|
print("\n4. Simulating Session Data:")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
# Get a license ID
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
|
||||||
|
"-c", "SELECT id FROM licenses WHERE is_active = TRUE LIMIT 1;"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
|
license_id = result.stdout.strip()
|
||||||
|
if not license_id:
|
||||||
|
print("✗ No active license found for test")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Insert test sessions
|
||||||
|
test_sessions = [
|
||||||
|
# Active session
|
||||||
|
f"""INSERT INTO sessions (license_id, session_id, ip_address, user_agent, is_active)
|
||||||
|
VALUES ({license_id}, 'TEST-SESSION-001', '192.168.1.100',
|
||||||
|
'Mozilla/5.0 Test Browser', TRUE);""",
|
||||||
|
|
||||||
|
# Inactive session from today
|
||||||
|
f"""INSERT INTO sessions (license_id, session_id, ip_address, user_agent,
|
||||||
|
started_at, ended_at, is_active)
|
||||||
|
VALUES ({license_id}, 'TEST-SESSION-002', '10.0.0.50',
|
||||||
|
'Chrome/120.0 Windows',
|
||||||
|
NOW() - INTERVAL '2 hours', NOW() - INTERVAL '1 hour', FALSE);"""
|
||||||
|
]
|
||||||
|
|
||||||
|
for sql in test_sessions:
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||||
|
"-c", sql
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
|
||||||
|
if "INSERT" in result.stdout:
|
||||||
|
print("✓ Test session inserted")
|
||||||
|
else:
|
||||||
|
print("✗ Failed to insert test session")
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||||
|
"-c", "SELECT COUNT(*) as total, COUNT(CASE WHEN is_active THEN 1 END) as active FROM sessions;"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
print("\nSession count after simulation:")
|
||||||
|
print(result.stdout)
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
print("Testing Session Management Features")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Rebuild admin panel
|
||||||
|
print("Rebuilding admin panel with session features...")
|
||||||
|
subprocess.run(["docker-compose", "build", "admin-panel"], capture_output=True)
|
||||||
|
subprocess.run(["docker-compose", "up", "-d"], capture_output=True)
|
||||||
|
subprocess.run(["sleep", "5"], capture_output=True)
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
test_session_table()
|
||||||
|
test_session_page()
|
||||||
|
test_dashboard_session_count()
|
||||||
|
simulate_session_data()
|
||||||
|
|
||||||
|
# Test again with data
|
||||||
|
print("\n5. Re-testing with simulated data:")
|
||||||
|
print("-" * 40)
|
||||||
|
test_session_page()
|
||||||
|
|
||||||
|
# Cleanup test data
|
||||||
|
print("\n6. Cleaning up test data:")
|
||||||
|
subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||||
|
"-c", "DELETE FROM sessions WHERE session_id LIKE 'TEST-%';"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
print("✓ Test sessions removed")
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren