Lizenzübersichtsseite
Dieser Commit ist enthalten in:
@@ -10,7 +10,8 @@
|
|||||||
"Bash(docker logs:*)",
|
"Bash(docker logs:*)",
|
||||||
"Bash(docker exec:*)",
|
"Bash(docker exec:*)",
|
||||||
"Bash(python3:*)",
|
"Bash(python3:*)",
|
||||||
"Bash(docker-compose restart:*)"
|
"Bash(docker-compose restart:*)",
|
||||||
|
"Bash(docker-compose build:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
24
JOURNAL.md
24
JOURNAL.md
@@ -121,3 +121,27 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker
|
|||||||
- Container neu bauen und starten
|
- Container neu bauen und starten
|
||||||
- Kundennamen mit Umlauten testen (z.B. "Müller GmbH", "Björn Schäfer")
|
- Kundennamen mit Umlauten testen (z.B. "Müller GmbH", "Björn Schäfer")
|
||||||
- Email mit Umlauten testen
|
- Email mit Umlauten testen
|
||||||
|
|
||||||
|
### 2025-01-06 - Lizenzübersicht implementiert
|
||||||
|
- Neue Route `/licenses` für Lizenzübersicht
|
||||||
|
- SQL-Query mit JOIN zwischen licenses und customers
|
||||||
|
- Status-Berechnung (aktiv, läuft bald ab, abgelaufen)
|
||||||
|
- Farbcodierung für verschiedene Status
|
||||||
|
- Navigation zwischen Lizenz erstellen und Übersicht
|
||||||
|
|
||||||
|
**Neue Features:**
|
||||||
|
- Anzeige aller Lizenzen mit Kundeninformationen
|
||||||
|
- Status-Anzeige basierend auf Ablaufdatum
|
||||||
|
- Unterscheidung zwischen Voll- und Testversion
|
||||||
|
- Responsive Tabelle mit Bootstrap
|
||||||
|
- Link von Dashboard zur Übersicht und zurück
|
||||||
|
|
||||||
|
**Geänderte/Neue Dateien:**
|
||||||
|
- v2_adminpanel/app.py (neue Route hinzugefügt)
|
||||||
|
- v2_adminpanel/templates/licenses.html (neu erstellt)
|
||||||
|
- v2_adminpanel/templates/index.html (Navigation ergänzt)
|
||||||
|
|
||||||
|
**Nächster Test:**
|
||||||
|
- Container neu starten
|
||||||
|
- Mehrere Lizenzen mit verschiedenen Ablaufdaten erstellen
|
||||||
|
- Lizenzübersicht unter /licenses aufrufen
|
||||||
@@ -100,5 +100,31 @@ def dashboard():
|
|||||||
|
|
||||||
return render_template("index.html", username=session.get('username'))
|
return render_template("index.html", username=session.get('username'))
|
||||||
|
|
||||||
|
@app.route("/licenses")
|
||||||
|
@login_required
|
||||||
|
def licenses():
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Alle Lizenzen mit Kundeninformationen abrufen
|
||||||
|
cur.execute("""
|
||||||
|
SELECT l.id, l.license_key, c.name, c.email, l.license_type,
|
||||||
|
l.valid_from, l.valid_until, l.is_active,
|
||||||
|
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.valid_until DESC
|
||||||
|
""")
|
||||||
|
|
||||||
|
licenses = cur.fetchall()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template("licenses.html", licenses=licenses, username=session.get('username'))
|
||||||
|
|
||||||
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,7 +17,10 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<h2 class="mb-4">Neue Lizenz erstellen</h2>
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Neue Lizenz erstellen</h2>
|
||||||
|
<a href="/licenses" class="btn btn-secondary">📋 Lizenzübersicht</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post" action="/" accept-charset="UTF-8">
|
<form method="post" action="/" accept-charset="UTF-8">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
|
|||||||
95
v2_adminpanel/templates/licenses.html
Normale Datei
95
v2_adminpanel/templates/licenses.html
Normale Datei
@@ -0,0 +1,95 @@
|
|||||||
|
<!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>
|
||||||
|
|
||||||
|
<div class="container py-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Lizenzübersicht</h2>
|
||||||
|
<a href="/" class="btn btn-primary">➕ Neue Lizenz erstellen</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Lizenzschlüssel</th>
|
||||||
|
<th>Kunde</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
<th>Typ</th>
|
||||||
|
<th>Gültig von</th>
|
||||||
|
<th>Gültig bis</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Aktiv</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for license in licenses %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ license[0] }}</td>
|
||||||
|
<td><code>{{ license[1] }}</code></td>
|
||||||
|
<td>{{ license[2] }}</td>
|
||||||
|
<td>{{ license[3] or '-' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if license[4] == 'full' %}
|
||||||
|
<span class="badge bg-success">Vollversion</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-warning">Testversion</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ license[5].strftime('%d.%m.%Y') }}</td>
|
||||||
|
<td>{{ license[6].strftime('%d.%m.%Y') }}</td>
|
||||||
|
<td>
|
||||||
|
{% if license[8] == 'abgelaufen' %}
|
||||||
|
<span class="status-abgelaufen">⚠️ Abgelaufen</span>
|
||||||
|
{% elif license[8] == 'läuft bald ab' %}
|
||||||
|
<span class="status-ablaufend">⏰ Läuft bald ab</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="status-aktiv">✅ Aktiv</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if license[7] %}
|
||||||
|
<span class="text-success">✓</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">✗</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if not licenses %}
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<p class="text-muted">Noch keine Lizenzen vorhanden.</p>
|
||||||
|
<a href="/" class="btn btn-primary">Erste Lizenz erstellen</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
104
v2_testing/test_license_overview.py
Normale Datei
104
v2_testing/test_license_overview.py
Normale Datei
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
# 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_license_overview():
|
||||||
|
"""Test the new license overview page"""
|
||||||
|
session = requests.Session()
|
||||||
|
|
||||||
|
# Login first
|
||||||
|
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)
|
||||||
|
if response.status_code != 302:
|
||||||
|
return "Failed to login"
|
||||||
|
|
||||||
|
# Access the licenses page
|
||||||
|
response = session.get(f"{base_url}/licenses", verify=False)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
content = response.text
|
||||||
|
|
||||||
|
# Check if we have the expected licenses
|
||||||
|
test_results = []
|
||||||
|
|
||||||
|
# Check for previously created licenses
|
||||||
|
if "Müller GmbH & Co. KG" in content:
|
||||||
|
test_results.append("✓ Found: Müller GmbH & Co. KG")
|
||||||
|
else:
|
||||||
|
test_results.append("✗ Missing: Müller GmbH & Co. KG")
|
||||||
|
|
||||||
|
if "Schröder Süßwaren AG" in content:
|
||||||
|
test_results.append("✓ Found: Schröder Süßwaren AG")
|
||||||
|
else:
|
||||||
|
test_results.append("✗ Missing: Schröder Süßwaren AG")
|
||||||
|
|
||||||
|
if "Björn Köhler Einzelunternehmen" in content:
|
||||||
|
test_results.append("✓ Found: Björn Köhler Einzelunternehmen")
|
||||||
|
else:
|
||||||
|
test_results.append("✗ Missing: Björn Köhler Einzelunternehmen")
|
||||||
|
|
||||||
|
# Check for license status indicators
|
||||||
|
if "aktiv" in content or "abgelaufen" in content or "läuft bald ab" in content:
|
||||||
|
test_results.append("✓ Status indicators found")
|
||||||
|
else:
|
||||||
|
test_results.append("✗ No status indicators found")
|
||||||
|
|
||||||
|
# Check for UTF-8 characters
|
||||||
|
if "ä" in content or "ö" in content or "ü" in content or "ß" in content:
|
||||||
|
test_results.append("✓ UTF-8 characters displayed correctly")
|
||||||
|
else:
|
||||||
|
test_results.append("✗ No UTF-8 characters found")
|
||||||
|
|
||||||
|
return test_results, response.status_code
|
||||||
|
else:
|
||||||
|
return [f"✗ Failed to access licenses page: Status {response.status_code}"], response.status_code
|
||||||
|
|
||||||
|
print("Testing License Overview Page")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# First restart admin panel to get the new code
|
||||||
|
print("Restarting admin panel container...")
|
||||||
|
import subprocess
|
||||||
|
subprocess.run(["docker", "restart", "admin-panel"], capture_output=True)
|
||||||
|
print("Waiting for container to start...")
|
||||||
|
subprocess.run(["sleep", "5"], capture_output=True)
|
||||||
|
|
||||||
|
results, status_code = test_license_overview()
|
||||||
|
|
||||||
|
print(f"\nAccessing /licenses endpoint - Status Code: {status_code}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("Database content check:")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Check what's actually in the database
|
||||||
|
result = subprocess.run([
|
||||||
|
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||||
|
"-c", """SELECT l.id, l.license_key, c.name, l.license_type,
|
||||||
|
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)
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren