diff --git a/JOURNAL.md b/JOURNAL.md
index 4929356..54994cd 100644
--- a/JOURNAL.md
+++ b/JOURNAL.md
@@ -193,4 +193,33 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker
**Besonderheiten:**
- Lösch-Button ist deaktiviert, wenn Kunde Lizenzen hat
- Aktive Lizenzen werden separat gezählt (nicht abgelaufen + aktiv)
-- UTF-8 Support für Kundennamen mit Umlauten
\ No newline at end of file
+- UTF-8 Support für Kundennamen mit Umlauten
+
+### 2025-01-06 - Dashboard mit Statistiken implementiert
+- Übersichtliches Dashboard als neue Startseite
+- Statistik-Karten mit wichtigen Kennzahlen
+- Listen für bald ablaufende und zuletzt erstellte Lizenzen
+- Routing angepasst: Dashboard (/) und Lizenz erstellen (/create)
+
+**Neue Features:**
+- Statistik-Karten: Kunden, Lizenzen gesamt, Aktive, Ablaufende
+- Aufteilung nach Lizenztypen (Vollversion/Testversion)
+- Aufteilung nach Status (Aktiv/Abgelaufen)
+- Top 10 bald ablaufende Lizenzen mit Restlaufzeit
+- Letzte 5 erstellte Lizenzen mit Status
+- Hover-Effekt auf Statistik-Karten
+- Einheitliche Navigation mit Dashboard-Link
+
+**Geänderte/Neue Dateien:**
+- v2_adminpanel/app.py (dashboard() komplett überarbeitet, create_license() Route)
+- v2_adminpanel/templates/dashboard.html (neu erstellt)
+- v2_adminpanel/templates/index.html (Navigation erweitert)
+- v2_adminpanel/templates/licenses.html (Navigation angepasst)
+- v2_adminpanel/templates/customers.html (Navigation angepasst)
+
+**Dashboard-Inhalte:**
+- 4 Hauptstatistiken als Karten
+- Lizenztyp-Verteilung
+- Status-Verteilung
+- Warnung für bald ablaufende Lizenzen
+- Übersicht der neuesten Aktivitäten
\ No newline at end of file
diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py
index ef7cc8b..de9d7ec 100644
--- a/v2_adminpanel/app.py
+++ b/v2_adminpanel/app.py
@@ -64,9 +64,101 @@ def logout():
session.pop('username', None)
return redirect(url_for('login'))
-@app.route("/", methods=["GET", "POST"])
+@app.route("/")
@login_required
def dashboard():
+ conn = get_connection()
+ cur = conn.cursor()
+
+ # Statistiken abrufen
+ # Gesamtanzahl Kunden
+ cur.execute("SELECT COUNT(*) FROM customers")
+ total_customers = cur.fetchone()[0]
+
+ # Gesamtanzahl Lizenzen
+ cur.execute("SELECT COUNT(*) FROM licenses")
+ total_licenses = cur.fetchone()[0]
+
+ # Aktive Lizenzen (nicht abgelaufen und is_active = true)
+ cur.execute("""
+ SELECT COUNT(*) FROM licenses
+ WHERE valid_until >= CURRENT_DATE AND is_active = TRUE
+ """)
+ active_licenses = cur.fetchone()[0]
+
+ # Abgelaufene Lizenzen
+ cur.execute("""
+ SELECT COUNT(*) FROM licenses
+ WHERE valid_until < CURRENT_DATE
+ """)
+ expired_licenses = cur.fetchone()[0]
+
+ # Lizenzen die in den nächsten 30 Tagen ablaufen
+ cur.execute("""
+ SELECT COUNT(*) FROM licenses
+ WHERE valid_until >= CURRENT_DATE
+ AND valid_until < CURRENT_DATE + INTERVAL '30 days'
+ AND is_active = TRUE
+ """)
+ expiring_soon = cur.fetchone()[0]
+
+ # Testlizenzen vs Vollversionen
+ cur.execute("""
+ SELECT license_type, COUNT(*)
+ FROM licenses
+ GROUP BY license_type
+ """)
+ license_types = dict(cur.fetchall())
+
+ # Letzte 5 erstellten Lizenzen
+ cur.execute("""
+ SELECT l.id, l.license_key, c.name, l.valid_until,
+ CASE
+ WHEN l.valid_until < CURRENT_DATE THEN 'abgelaufen'
+ WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'läuft bald ab'
+ ELSE 'aktiv'
+ END as status
+ FROM licenses l
+ JOIN customers c ON l.customer_id = c.id
+ ORDER BY l.id DESC
+ LIMIT 5
+ """)
+ recent_licenses = cur.fetchall()
+
+ # Bald ablaufende Lizenzen (nächste 30 Tage)
+ cur.execute("""
+ SELECT l.id, l.license_key, c.name, l.valid_until,
+ l.valid_until - CURRENT_DATE as days_left
+ FROM licenses l
+ JOIN customers c ON l.customer_id = c.id
+ WHERE l.valid_until >= CURRENT_DATE
+ AND l.valid_until < CURRENT_DATE + INTERVAL '30 days'
+ AND l.is_active = TRUE
+ ORDER BY l.valid_until
+ LIMIT 10
+ """)
+ expiring_licenses = cur.fetchall()
+
+ cur.close()
+ conn.close()
+
+ stats = {
+ 'total_customers': total_customers,
+ 'total_licenses': total_licenses,
+ 'active_licenses': active_licenses,
+ 'expired_licenses': expired_licenses,
+ 'expiring_soon': expiring_soon,
+ 'full_licenses': license_types.get('full', 0),
+ 'test_licenses': license_types.get('test', 0),
+ 'recent_licenses': recent_licenses,
+ 'expiring_licenses': expiring_licenses
+ }
+
+ return render_template("dashboard.html", stats=stats, username=session.get('username'))
+
+@app.route("/create", methods=["GET", "POST"])
+@login_required
+def create_license():
if request.method == "POST":
name = request.form["customer_name"]
email = request.form["email"]
@@ -96,7 +188,7 @@ def dashboard():
cur.close()
conn.close()
- return redirect("/")
+ return redirect("/create")
return render_template("index.html", username=session.get('username'))
diff --git a/v2_adminpanel/templates/customers.html b/v2_adminpanel/templates/customers.html
index ba784b0..32b85f0 100644
--- a/v2_adminpanel/templates/customers.html
+++ b/v2_adminpanel/templates/customers.html
@@ -20,7 +20,8 @@
Neue Lizenz erstellen
diff --git a/v2_adminpanel/templates/licenses.html b/v2_adminpanel/templates/licenses.html
index bd13050..17570a8 100644
--- a/v2_adminpanel/templates/licenses.html
+++ b/v2_adminpanel/templates/licenses.html
@@ -25,7 +25,8 @@
diff --git a/v2_testing/test_dashboard.py b/v2_testing/test_dashboard.py
new file mode 100644
index 0000000..4134c0a
--- /dev/null
+++ b/v2_testing/test_dashboard.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+import requests
+import urllib3
+import subprocess
+
+# Disable SSL warnings for self-signed certificate
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+# Test configuration
+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_dashboard():
+ """Test dashboard with statistics"""
+ session = requests.Session()
+
+ if not login(session):
+ return "✗ Failed to login"
+
+ response = session.get(f"{base_url}/", verify=False)
+ if response.status_code != 200:
+ return f"✗ Failed to access dashboard: Status {response.status_code}"
+
+ content = response.text
+ results = []
+
+ # Check for statistics elements
+ statistics_checks = [
+ ("Gesamtkunden", "Total customers statistic"),
+ ("Gesamtlizenzen", "Total licenses statistic"),
+ ("Aktive Lizenzen", "Active licenses statistic"),
+ ("Abgelaufene Lizenzen", "Expired licenses statistic"),
+ ("Läuft bald ab", "Expiring soon statistic"),
+ ("Letzte Lizenzen", "Recent licenses section"),
+ ("Bald ablaufende Lizenzen", "Expiring licenses section")
+ ]
+
+ for check_text, description in statistics_checks:
+ if check_text in content:
+ results.append(f"✓ Found: {description}")
+ else:
+ results.append(f"✗ Missing: {description}")
+
+ # Check if actual numbers are displayed
+ if any(char.isdigit() for char in content):
+ results.append("✓ Statistics numbers are displayed")
+ else:
+ results.append("✗ No statistics numbers found")
+
+ # Check for customer names in recent licenses
+ if "Müller" in content or "Schröder" in content or "Köhler" in content:
+ results.append("✓ Recent licenses show customer names")
+ else:
+ results.append("✗ No customer names in recent licenses")
+
+ return results
+
+# Rebuild and restart admin panel
+print("Rebuilding admin panel with dashboard...")
+subprocess.run(["docker-compose", "build", "admin-panel"], capture_output=True)
+subprocess.run(["docker-compose", "up", "-d", "admin-panel"], capture_output=True)
+print("Waiting for container to start...")
+subprocess.run(["sleep", "5"], capture_output=True)
+
+print("\nTesting Dashboard with Statistics")
+print("=" * 50)
+
+results = test_dashboard()
+for result in results:
+ print(result)
+
+# Get actual statistics from database for comparison
+print("\n" + "=" * 50)
+print("Actual Database Statistics:")
+print("-" * 50)
+
+# Total customers
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
+ "-c", "SELECT COUNT(*) FROM customers;"
+], capture_output=True, text=True)
+print(f"Total Customers: {result.stdout.strip()}")
+
+# Total licenses
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
+ "-c", "SELECT COUNT(*) FROM licenses;"
+], capture_output=True, text=True)
+print(f"Total Licenses: {result.stdout.strip()}")
+
+# Active licenses
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
+ "-c", "SELECT COUNT(*) FROM licenses WHERE valid_until >= CURRENT_DATE AND is_active = TRUE;"
+], capture_output=True, text=True)
+print(f"Active Licenses: {result.stdout.strip()}")
+
+# Expired licenses
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
+ "-c", "SELECT COUNT(*) FROM licenses WHERE valid_until < CURRENT_DATE;"
+], capture_output=True, text=True)
+print(f"Expired Licenses: {result.stdout.strip()}")
+
+# Expiring soon (30 days)
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
+ "-c", "SELECT COUNT(*) FROM licenses WHERE valid_until >= CURRENT_DATE AND valid_until < CURRENT_DATE + INTERVAL '30 days' AND is_active = TRUE;"
+], capture_output=True, text=True)
+print(f"Expiring Soon (30 days): {result.stdout.strip()}")
+
+# License types breakdown
+print("\nLicense Types:")
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
+ "-c", "SELECT license_type, COUNT(*) FROM licenses GROUP BY license_type ORDER BY COUNT(*) DESC;"
+], capture_output=True, text=True)
+print(result.stdout)
+
+# Recent licenses
+print("Recent Licenses:")
+result = subprocess.run([
+ "docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
+ "-c", """SELECT l.license_key, c.name, l.valid_until,
+ CASE
+ WHEN l.valid_until < CURRENT_DATE THEN 'abgelaufen'
+ WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'läuft bald ab'
+ ELSE 'aktiv'
+ END as status
+ FROM licenses l
+ JOIN customers c ON l.customer_id = c.id
+ ORDER BY l.id DESC
+ LIMIT 5;"""
+], capture_output=True, text=True)
+print(result.stdout)
\ No newline at end of file
diff --git a/v2_testing/test_dashboard_detail.py b/v2_testing/test_dashboard_detail.py
new file mode 100644
index 0000000..9ee04e7
--- /dev/null
+++ b/v2_testing/test_dashboard_detail.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+import requests
+import urllib3
+import re
+
+# Disable SSL warnings for self-signed certificate
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+# Test configuration
+base_url = "https://localhost:443"
+admin_user = {"username": "rac00n", "password": "1248163264"}
+
+def test_dashboard_detail():
+ """Test dashboard content in detail"""
+ session = requests.Session()
+
+ # Login
+ login_data = {
+ "username": admin_user["username"],
+ "password": admin_user["password"]
+ }
+ session.post(f"{base_url}/login", data=login_data, verify=False, allow_redirects=False)
+
+ # Get dashboard
+ response = session.get(f"{base_url}/", verify=False)
+ content = response.text
+
+ # Extract numbers from content
+ numbers = re.findall(r'\d+', content)
+ print(f"Numbers found on page: {numbers[:20]}") # First 20 numbers
+
+ # Check specific sections
+ if "8" in numbers: # Total customers
+ print("✓ Found total customers count (8)")
+
+ if "5" in numbers: # Total licenses
+ print("✓ Found total licenses count (5)")
+
+ if "3" in numbers: # Active licenses
+ print("✓ Found active licenses count (3)")
+
+ if "2" in numbers: # Expired licenses
+ print("✓ Found expired licenses count (2)")
+
+ # Print a snippet of the HTML to see structure
+ print("\nHTML snippet (first 1000 chars):")
+ print(content[:1000])
+
+ return response.status_code
+
+print("Detailed Dashboard Test")
+print("=" * 50)
+status = test_dashboard_detail()
\ No newline at end of file