Lizenz bearbeiten und löschen
Dieser Commit ist enthalten in:
26
JOURNAL.md
26
JOURNAL.md
@@ -144,4 +144,28 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker
|
||||
**Nächster Test:**
|
||||
- Container neu starten
|
||||
- Mehrere Lizenzen mit verschiedenen Ablaufdaten erstellen
|
||||
- Lizenzübersicht unter /licenses aufrufen
|
||||
- Lizenzübersicht unter /licenses aufrufen
|
||||
|
||||
### 2025-01-06 - Lizenz bearbeiten/löschen implementiert
|
||||
- Neue Routen für Bearbeiten und Löschen von Lizenzen
|
||||
- Bearbeitungsformular mit vorausgefüllten Werten
|
||||
- Aktiv/Inaktiv-Status kann geändert werden
|
||||
- Lösch-Bestätigung per JavaScript confirm()
|
||||
- Kunde kann nicht geändert werden (nur Lizenzdetails)
|
||||
|
||||
**Neue Features:**
|
||||
- `/license/edit/<id>` - Bearbeitungsformular
|
||||
- `/license/delete/<id>` - Lizenz löschen (POST)
|
||||
- Aktionen-Spalte in der Lizenzübersicht
|
||||
- Buttons für Bearbeiten und Löschen
|
||||
- Checkbox für Aktiv-Status
|
||||
|
||||
**Geänderte/Neue Dateien:**
|
||||
- v2_adminpanel/app.py (edit_license und delete_license Routen)
|
||||
- v2_adminpanel/templates/licenses.html (Aktionen-Spalte hinzugefügt)
|
||||
- v2_adminpanel/templates/edit_license.html (neu erstellt)
|
||||
|
||||
**Sicherheit:**
|
||||
- Login-Required für alle Aktionen
|
||||
- POST-only für Löschvorgänge
|
||||
- Bestätigungsdialog vor dem Löschen
|
||||
@@ -126,5 +126,64 @@ def licenses():
|
||||
|
||||
return render_template("licenses.html", licenses=licenses, username=session.get('username'))
|
||||
|
||||
@app.route("/license/edit/<int:license_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def edit_license(license_id):
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
if request.method == "POST":
|
||||
# Update license
|
||||
license_key = request.form["license_key"]
|
||||
license_type = request.form["license_type"]
|
||||
valid_from = request.form["valid_from"]
|
||||
valid_until = request.form["valid_until"]
|
||||
is_active = request.form.get("is_active") == "on"
|
||||
|
||||
cur.execute("""
|
||||
UPDATE licenses
|
||||
SET license_key = %s, license_type = %s, valid_from = %s,
|
||||
valid_until = %s, is_active = %s
|
||||
WHERE id = %s
|
||||
""", (license_key, license_type, valid_from, valid_until, is_active, license_id))
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
return redirect("/licenses")
|
||||
|
||||
# Get license data
|
||||
cur.execute("""
|
||||
SELECT l.id, l.license_key, c.name, c.email, l.license_type,
|
||||
l.valid_from, l.valid_until, l.is_active, c.id
|
||||
FROM licenses l
|
||||
JOIN customers c ON l.customer_id = c.id
|
||||
WHERE l.id = %s
|
||||
""", (license_id,))
|
||||
|
||||
license = cur.fetchone()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
if not license:
|
||||
return redirect("/licenses")
|
||||
|
||||
return render_template("edit_license.html", license=license, username=session.get('username'))
|
||||
|
||||
@app.route("/license/delete/<int:license_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_license(license_id):
|
||||
conn = get_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,))
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
return redirect("/licenses")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=443, ssl_context='adhoc')
|
||||
|
||||
76
v2_adminpanel/templates/edit_license.html
Normale Datei
76
v2_adminpanel/templates/edit_license.html
Normale Datei
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Lizenz bearbeiten - Admin Panel</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</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 bearbeiten</h2>
|
||||
<a href="/licenses" class="btn btn-secondary">← Zurück zur Übersicht</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" action="/license/edit/{{ license[0] }}" accept-charset="UTF-8">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Kunde</label>
|
||||
<input type="text" class="form-control" value="{{ license[2] }}" disabled>
|
||||
<small class="text-muted">Kunde kann nicht geändert werden</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" value="{{ license[3] or '-' }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="licenseKey" class="form-label">Lizenzschlüssel</label>
|
||||
<input type="text" class="form-control" id="licenseKey" name="license_key" value="{{ license[1] }}" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="licenseType" class="form-label">Lizenztyp</label>
|
||||
<select class="form-select" id="licenseType" name="license_type" required>
|
||||
<option value="full" {% if license[4] == 'full' %}selected{% endif %}>Vollversion</option>
|
||||
<option value="test" {% if license[4] == 'test' %}selected{% endif %}>Testversion</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validFrom" class="form-label">Gültig von</label>
|
||||
<input type="date" class="form-control" id="validFrom" name="valid_from" value="{{ license[5].strftime('%Y-%m-%d') }}" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validUntil" class="form-label">Gültig bis</label>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" value="{{ license[6].strftime('%Y-%m-%d') }}" required>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="isActive" name="is_active" {% if license[7] %}checked{% endif %}>
|
||||
<label class="form-check-label" for="isActive">
|
||||
Lizenz ist aktiv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">💾 Änderungen speichern</button>
|
||||
<a href="/licenses" class="btn btn-secondary">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -42,6 +42,7 @@
|
||||
<th>Gültig bis</th>
|
||||
<th>Status</th>
|
||||
<th>Aktiv</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -76,6 +77,14 @@
|
||||
<span class="text-danger">✗</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="/license/edit/{{ license[0] }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
<form method="post" action="/license/delete/{{ license[0] }}" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');">
|
||||
<button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
150
v2_testing/test_license_edit_delete.py
Normale Datei
150
v2_testing/test_license_edit_delete.py
Normale Datei
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
import requests
|
||||
import urllib3
|
||||
from datetime import datetime, timedelta
|
||||
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_edit_license():
|
||||
"""Test editing a license"""
|
||||
session = requests.Session()
|
||||
|
||||
if not login(session):
|
||||
return "✗ Failed to login"
|
||||
|
||||
# First, get a license ID to edit (let's edit license ID 5 - "Bli-bla-blub")
|
||||
license_id = 5
|
||||
|
||||
# Test GET edit page
|
||||
response = session.get(f"{base_url}/license/edit/{license_id}", verify=False)
|
||||
if response.status_code != 200:
|
||||
return f"✗ Failed to access edit page: Status {response.status_code}"
|
||||
|
||||
# Check if edit form is displayed
|
||||
if "Bli-bla-blub" not in response.text:
|
||||
return "✗ Edit page doesn't show current license data"
|
||||
|
||||
# Test POST edit with new data
|
||||
new_license_data = {
|
||||
"license_key": "UPDATED-KEY-2025",
|
||||
"license_type": "Enterprise-Lösung",
|
||||
"valid_from": "2025-01-01",
|
||||
"valid_until": "2025-12-31",
|
||||
"is_active": "on"
|
||||
}
|
||||
|
||||
response = session.post(f"{base_url}/license/edit/{license_id}",
|
||||
data=new_license_data,
|
||||
verify=False,
|
||||
allow_redirects=False)
|
||||
|
||||
if response.status_code == 302 and response.headers.get('Location') == '/licenses':
|
||||
return "✓ License edited successfully"
|
||||
else:
|
||||
return f"✗ Failed to edit license: Status {response.status_code}"
|
||||
|
||||
def test_delete_license():
|
||||
"""Test deleting a license"""
|
||||
session = requests.Session()
|
||||
|
||||
if not login(session):
|
||||
return "✗ Failed to login"
|
||||
|
||||
# Create a test license to delete
|
||||
test_license = {
|
||||
"customer_name": "Delete Test Company",
|
||||
"email": "delete@test.com",
|
||||
"license_key": "DELETE-TEST-KEY",
|
||||
"license_type": "Test",
|
||||
"valid_from": datetime.now().strftime("%Y-%m-%d"),
|
||||
"valid_until": (datetime.now() + timedelta(days=30)).strftime("%Y-%m-%d")
|
||||
}
|
||||
|
||||
# Create the license
|
||||
response = session.post(f"{base_url}/", data=test_license, verify=False, allow_redirects=False)
|
||||
if response.status_code != 302:
|
||||
return "✗ Failed to create test license for deletion"
|
||||
|
||||
# Get the ID of the newly created license
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
|
||||
"-c", "SELECT id FROM licenses WHERE license_key = 'DELETE-TEST-KEY';"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
license_id = result.stdout.strip()
|
||||
if not license_id:
|
||||
return "✗ Failed to find test license ID"
|
||||
|
||||
# Delete the license
|
||||
response = session.post(f"{base_url}/license/delete/{license_id}",
|
||||
verify=False,
|
||||
allow_redirects=False)
|
||||
|
||||
if response.status_code == 302 and response.headers.get('Location') == '/licenses':
|
||||
# Verify it's really deleted
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank", "-t",
|
||||
"-c", f"SELECT COUNT(*) FROM licenses WHERE id = {license_id};"
|
||||
], capture_output=True, text=True)
|
||||
|
||||
count = int(result.stdout.strip())
|
||||
if count == 0:
|
||||
return "✓ License deleted successfully"
|
||||
else:
|
||||
return "✗ License still exists in database"
|
||||
else:
|
||||
return f"✗ Failed to delete license: Status {response.status_code}"
|
||||
|
||||
# Rebuild and restart admin panel
|
||||
print("Rebuilding admin panel with new features...")
|
||||
subprocess.run(["docker-compose", "build", "admin-panel"], capture_output=True)
|
||||
subprocess.run(["docker-compose", "up", "-d", "admin-panel"], capture_output=True)
|
||||
subprocess.run(["sleep", "5"], capture_output=True)
|
||||
|
||||
print("\nTesting License Edit/Delete Functionality")
|
||||
print("=" * 50)
|
||||
|
||||
# Test edit functionality
|
||||
print("\n1. Testing License Edit:")
|
||||
print("-" * 30)
|
||||
edit_result = test_edit_license()
|
||||
print(edit_result)
|
||||
|
||||
# Verify the edit worked
|
||||
print("\nVerifying edited license in database:")
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||
"-c", "SELECT license_key, license_type, valid_from, valid_until, is_active FROM licenses WHERE license_key = 'UPDATED-KEY-2025';"
|
||||
], capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
|
||||
# Test delete functionality
|
||||
print("\n2. Testing License Delete:")
|
||||
print("-" * 30)
|
||||
delete_result = test_delete_license()
|
||||
print(delete_result)
|
||||
|
||||
# Show current licenses
|
||||
print("\n" + "=" * 50)
|
||||
print("Current licenses in database:")
|
||||
print("-" * 50)
|
||||
result = subprocess.run([
|
||||
"docker", "exec", "db", "psql", "-U", "adminuser", "-d", "meinedatenbank",
|
||||
"-c", "SELECT l.id, l.license_key, c.name, l.license_type FROM licenses l JOIN customers c ON l.customer_id = c.id ORDER BY l.id;"
|
||||
], capture_output=True, text=True)
|
||||
print(result.stdout)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren