From 68d6cc346b22147d72a29ae2c08ffe6e68da66e2 Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Sat, 7 Jun 2025 13:59:32 +0200 Subject: [PATCH] Dashboard mit Statistiken --- JOURNAL.md | 31 +++- v2_adminpanel/app.py | 96 +++++++++++- v2_adminpanel/templates/customers.html | 3 +- v2_adminpanel/templates/dashboard.html | 199 +++++++++++++++++++++++++ v2_adminpanel/templates/index.html | 1 + v2_adminpanel/templates/licenses.html | 3 +- v2_testing/test_dashboard.py | 144 ++++++++++++++++++ v2_testing/test_dashboard_detail.py | 53 +++++++ 8 files changed, 525 insertions(+), 5 deletions(-) create mode 100644 v2_adminpanel/templates/dashboard.html create mode 100644 v2_testing/test_dashboard.py create mode 100644 v2_testing/test_dashboard_detail.py 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 @@

Kundenverwaltung

- ➕ Neue Lizenz + 📊 Dashboard + ➕ Neue Lizenz 📋 Lizenzen
diff --git a/v2_adminpanel/templates/dashboard.html b/v2_adminpanel/templates/dashboard.html new file mode 100644 index 0000000..d503c97 --- /dev/null +++ b/v2_adminpanel/templates/dashboard.html @@ -0,0 +1,199 @@ + + + + + Dashboard - Admin Panel + + + + + + +
+
+

Dashboard

+
+ ➕ Neue Lizenz + 📋 Lizenzen + 👥 Kunden +
+
+ + +
+
+
+
+
👥 Kunden
+

{{ stats.total_customers }}

+

Gesamt

+
+
+
+
+
+
+
📋 Lizenzen
+

{{ stats.total_licenses }}

+

Gesamt

+
+
+
+
+
+
+
✅ Aktiv
+

{{ stats.active_licenses }}

+

Gültige Lizenzen

+
+
+
+
+
+
+
⚠️ Ablaufend
+

{{ stats.expiring_soon }}

+

Nächste 30 Tage

+
+
+
+
+ + +
+
+
+
+
Lizenztypen
+
+
+

{{ stats.full_licenses }}

+

Vollversionen

+
+
+

{{ stats.test_licenses }}

+

Testversionen

+
+
+
+
+
+
+
+
+
Lizenzstatus
+
+
+

{{ stats.active_licenses }}

+

Aktiv

+
+
+

{{ stats.expired_licenses }}

+

Abgelaufen

+
+
+
+
+
+
+ +
+ +
+
+
+
⏰ Bald ablaufende Lizenzen
+
+
+ {% if stats.expiring_licenses %} +
+ + + + + + + + + + {% for license in stats.expiring_licenses %} + + + + + + {% endfor %} + +
KundeLizenzTage
{{ license[2] }}{{ license[1][:8] }}...{{ license[4] }} Tage
+
+ {% else %} +

Keine Lizenzen laufen in den nächsten 30 Tagen ab.

+ {% endif %} +
+
+
+ + +
+
+
+
🆕 Zuletzt erstellte Lizenzen
+
+
+ {% if stats.recent_licenses %} +
+ + + + + + + + + + {% for license in stats.recent_licenses %} + + + + + + {% endfor %} + +
KundeLizenzStatus
{{ license[2] }}{{ license[1][:8] }}... + {% if license[4] == 'abgelaufen' %} + ⚠️ Abgelaufen + {% elif license[4] == 'läuft bald ab' %} + ⏰ Läuft bald ab + {% else %} + ✅ Aktiv + {% endif %} +
+
+ {% else %} +

Noch keine Lizenzen erstellt.

+ {% endif %} +
+
+
+
+
+ + \ No newline at end of file diff --git a/v2_adminpanel/templates/index.html b/v2_adminpanel/templates/index.html index 630ec4c..3825b87 100644 --- a/v2_adminpanel/templates/index.html +++ b/v2_adminpanel/templates/index.html @@ -20,6 +20,7 @@

Neue Lizenz erstellen

+ 📊 Dashboard 📋 Lizenzen 👥 Kunden
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