From 0ec0d2c267f338bd261578a90f79c0ef762b129a Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Tue, 17 Jun 2025 22:59:34 +0200 Subject: [PATCH] Refactoring - Fix1 --- v2/cookies.txt | 1 + v2_adminpanel/FEHLERSUCHE.md | 111 ++++++++++ v2_adminpanel/MIGRATION_DISCREPANCIES.md | 156 ++++++++++++++ v2_adminpanel/app.py | 86 ++++++-- v2_adminpanel/auth/decorators.py | 4 +- .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 183 bytes .../customer_routes.cpython-312.pyc | Bin 0 -> 15807 bytes v2_adminpanel/routes/admin_routes.py | 8 +- v2_adminpanel/routes/customer_routes.py | 204 +++++++++--------- v2_adminpanel/routes/resource_routes.py | 89 ++++---- v2_adminpanel/templates/404.html | 20 ++ v2_adminpanel/templates/500.html | 20 ++ .../templates/customers_licenses.html | 22 +- 13 files changed, 553 insertions(+), 168 deletions(-) create mode 100644 v2_adminpanel/FEHLERSUCHE.md create mode 100644 v2_adminpanel/MIGRATION_DISCREPANCIES.md create mode 100644 v2_adminpanel/routes/__pycache__/__init__.cpython-312.pyc create mode 100644 v2_adminpanel/routes/__pycache__/customer_routes.cpython-312.pyc create mode 100644 v2_adminpanel/templates/404.html create mode 100644 v2_adminpanel/templates/500.html diff --git a/v2/cookies.txt b/v2/cookies.txt index c31d989..32e94c9 100644 --- a/v2/cookies.txt +++ b/v2/cookies.txt @@ -2,3 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. +#HttpOnly_admin-panel-undso.z5m7q9dk3ah2v1plx6ju.com FALSE / FALSE 1750193106 admin_session AwO_9xkBcSaqhYwpkjUTL1bNPOMWUZ5qMXGUAwdTpNM diff --git a/v2_adminpanel/FEHLERSUCHE.md b/v2_adminpanel/FEHLERSUCHE.md new file mode 100644 index 0000000..6bee7fa --- /dev/null +++ b/v2_adminpanel/FEHLERSUCHE.md @@ -0,0 +1,111 @@ +# Fehlersuche - v2_adminpanel Refactoring + +## Aktueller Stand (17.06.2025) + +### Erfolgreiches Refactoring +- Die ursprüngliche 5000+ Zeilen große app.py wurde erfolgreich in Module aufgeteilt: + - 9 Blueprint-Module in `routes/` + - Separate Module für auth/, utils/, config.py, db.py, models.py + - Hauptdatei app.py nur noch 178 Zeilen + +### Funktionierende Teile +- ✅ Routing-System funktioniert (alle Routen sind registriert) +- ✅ Login-System funktioniert +- ✅ Einfache Test-Routen funktionieren (/simple-test) +- ✅ Blueprint-Registrierung funktioniert korrekt + +### Aktuelle Probleme + +#### 1. **404 bei /test-db Route** +**Problem**: Die Route wird nicht gefunden, obwohl sie in app.py definiert ist +**Ursache**: Docker Container hat die Code-Änderungen noch nicht übernommen +**Lösung**: +```bash +docker-compose down +docker-compose build --no-cache admin-panel +docker-compose up -d +``` + +#### 2. **Redirect zu /login bei /customers-licenses** +**Problem**: Beim Aufruf von /customers-licenses wird man zum Login umgeleitet +**Ursache**: Die Route ist mit `@login_required` geschützt +**Status**: Das ist korrektes Verhalten - man muss eingeloggt sein + +#### 3. **"dict object has no element 5" Fehler** +**Problem**: Nach erfolgreichem Login und Zugriff auf /customers-licenses kommt dieser Fehler +**Ursache**: Die Datenbankabfrage gibt ein Dictionary zurück, aber der Code erwartet ein Tuple +**Bereits implementierte Lösung**: +- customers_licenses() verwendet jetzt direkte psycopg2 Verbindung ohne Helper +- Expliziter normaler Cursor statt möglicherweise Dictionary-Cursor + +#### 4. **Fehlende Templates** +**Problem**: 404.html und 500.html fehlten +**Status**: ✅ Bereits erstellt und hinzugefügt + +## Debugging-Schritte + +### 1. Container komplett neu bauen +```bash +cd C:\Users\Administrator\Documents\GitHub\v2-Docker\v2 +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` + +### 2. Logs überprüfen +```bash +docker logs admin-panel --tail 100 +``` + +### 3. Test-Routen +- `/simple-test` - Sollte "Simple test works!" zeigen +- `/debug-routes` - Zeigt alle registrierten Routen +- `/test-db` - Testet Datenbankverbindung + +### 4. Login-Test +1. Gehe zu https://admin-panel-undso.z5m7q9dk3ah2v1plx6ju.com/ +2. Logge dich mit den Admin-Credentials ein +3. Versuche dann /customers-licenses aufzurufen + +## Code-Fixes bereits implementiert + +### 1. Datenbankverbindungen +- Alle kritischen Funktionen verwenden jetzt `conn = get_connection()` mit normalem Cursor +- Verhindert Dictionary/Tuple Konflikte + +### 2. Spaltennamen korrigiert +- `is_active` statt `active` für licenses Tabelle +- `is_active` statt `active` für sessions Tabelle +- `is_test` statt `is_test_license` +- Entfernt: phone, address, notes aus customers (existieren nicht) + +### 3. Blueprint-Referenzen +- Alle url_for() Aufrufe haben korrekte Blueprint-Präfixe +- z.B. `url_for('auth.login')` statt `url_for('login')` + +## Nächste Schritte + +1. **Container neu bauen** (siehe oben) +2. **Einloggen** und testen ob /customers-licenses funktioniert +3. **Falls weiterhin Fehler**: Docker Logs nach "CUSTOMERS-LICENSES ROUTE CALLED" durchsuchen +4. **Alternative**: Temporär auf die große app.py.backup zurückwechseln: + ```bash + cp app.py.backup app.py + docker-compose restart admin-panel + ``` + +## Bekannte funktionierende Routen (nach Login) +- `/` - Dashboard +- `/customers` - Kundenliste +- `/licenses` - Lizenzliste +- `/resources` - Ressourcen +- `/audit` - Audit Log +- `/sessions` - Sessions + +## Debug-Informationen in customer_routes.py +Die customers_licenses Funktion hat erweiterte Logging-Ausgaben: +- "=== CUSTOMERS-LICENSES ROUTE CALLED ===" +- "=== QUERY RETURNED X ROWS ===" +- Details über Datentypen der Ergebnisse + +Diese erscheinen in den Docker Logs und helfen bei der Fehlersuche. \ No newline at end of file diff --git a/v2_adminpanel/MIGRATION_DISCREPANCIES.md b/v2_adminpanel/MIGRATION_DISCREPANCIES.md new file mode 100644 index 0000000..7aa7632 --- /dev/null +++ b/v2_adminpanel/MIGRATION_DISCREPANCIES.md @@ -0,0 +1,156 @@ +# Migration Discrepancies - Backup vs Current Blueprint Structure + +## 1. Missing Routes + +### Authentication/Profile Routes (Not in any blueprint) +- `/profile` - User profile page +- `/profile/change-password` - Change password endpoint +- `/profile/setup-2fa` - Setup 2FA page +- `/profile/enable-2fa` - Enable 2FA endpoint +- `/profile/disable-2fa` - Disable 2FA endpoint +- `/heartbeat` - Session heartbeat endpoint + +### Customer API Routes (Missing from api_routes.py) +- `/api/customer//licenses` - Get licenses for a customer +- `/api/customer//quick-stats` - Get quick stats for a customer + +### Resource Routes (Missing from resource_routes.py) +- `/resources` - Main resources page +- `/resources/add` - Add new resources page +- `/resources/quarantine/` - Quarantine a resource +- `/resources/release` - Release resources from quarantine +- `/resources/history/` - View resource history +- `/resources/metrics` - Resource metrics page +- `/resources/report` - Resource report page + +### Main Dashboard Route (Missing) +- `/` - Main dashboard (currently in backup shows dashboard with stats) + +## 2. Database Column Discrepancies + +### Column Name Differences +- **created_by** - Used in backup_history table but not consistently referenced +- **is_test_license** vs **is_test** - The database uses `is_test` but some code might reference `is_test_license` + +### Session Table Aliases +The sessions table has multiple column aliases that need to be handled: +- `login_time` (alias for `started_at`) +- `last_activity` (alias for `last_heartbeat`) +- `logout_time` (alias for `ended_at`) +- `active` (alias for `is_active`) + +## 3. Template Name Mismatches + +### Templates Referenced in Backup +- `login.html` - Login page +- `verify_2fa.html` - 2FA verification +- `profile.html` - User profile +- `setup_2fa.html` - 2FA setup +- `backup_codes.html` - 2FA backup codes +- `dashboard.html` - Main dashboard +- `index.html` - Create license form +- `batch_result.html` - Batch operation results +- `batch_form.html` - Batch form +- `edit_license.html` - Edit license +- `edit_customer.html` - Edit customer +- `create_customer.html` - Create customer +- `customers_licenses.html` - Customer-license overview +- `sessions.html` - Sessions list +- `audit_log.html` - Audit log +- `backups.html` - Backup management +- `blocked_ips.html` - Blocked IPs +- `resources.html` - Resources list +- `add_resources.html` - Add resources form +- `resource_history.html` - Resource history +- `resource_metrics.html` - Resource metrics +- `resource_report.html` - Resource report + +## 4. URL_FOR References That Need Blueprint Prefixes + +### In Templates and Redirects +- `url_for('login')` → `url_for('auth.login')` +- `url_for('logout')` → `url_for('auth.logout')` +- `url_for('verify_2fa')` → `url_for('auth.verify_2fa')` +- `url_for('profile')` → `url_for('auth.profile')` (needs implementation) +- `url_for('index')` → `url_for('main.index')` or appropriate blueprint +- `url_for('blocked_ips')` → `url_for('admin.blocked_ips')` +- `url_for('audit_log')` → `url_for('admin.audit_log')` +- `url_for('backups')` → `url_for('admin.backups')` + +## 5. Missing Functions/Middleware + +### Authentication Decorators +- `@login_required` decorator implementation needs to be verified +- `@require_2fa` decorator (if used) + +### Helper Functions +- `get_connection()` - Database connection helper +- `log_audit()` - Audit logging function +- `create_backup()` - Backup creation function +- Rate limiting functions for login attempts + +### Session Management +- Session timeout handling +- Heartbeat mechanism for active sessions + +## 6. API Endpoint Inconsistencies + +### URL Prefix Issues +- API routes in backup don't use `/api` prefix consistently +- Some use `/api/...` while others are at root level + +### Missing API Endpoints +- `/api/generate-license-key` - Generate license key +- `/api/global-search` - Global search functionality + +## 7. Export Routes Organization + +### Current vs Expected +- Export routes might need different URL structure +- Check if all export types are covered: + - `/export/licenses` + - `/export/audit` + - `/export/customers` + - `/export/sessions` + - `/export/resources` + +## 8. Special Configurations + +### Missing Configurations +- TOTP/2FA configuration +- Backup encryption settings +- Rate limiting configuration +- Session timeout settings + +### Environment Variables +- Check if all required environment variables are properly loaded +- Database connection parameters +- Secret keys and encryption keys + +## 9. JavaScript/AJAX Endpoints + +### API calls that might be broken +- Device management endpoints +- Quick edit functionality +- Bulk operations +- Resource allocation checks + +## 10. Permission/Access Control + +### Missing or Incorrect Access Control +- All routes need `@login_required` decorator +- Some routes might need additional permission checks +- API routes need proper authentication + +## Action Items + +1. **Implement missing profile/auth routes** in auth_routes.py +2. **Add missing customer API routes** to api_routes.py +3. **Create complete resource management blueprint** with all routes +4. **Fix main dashboard route** - decide which blueprint should handle "/" +5. **Update all url_for() calls** in templates to use blueprint prefixes +6. **Verify database column names** are consistent throughout +7. **Check template names** match between routes and actual files +8. **Implement heartbeat mechanism** for session management +9. **Add missing helper functions** to appropriate modules +10. **Test all export routes** work correctly with new structure \ No newline at end of file diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py index e1ef39a..4513b67 100644 --- a/v2_adminpanel/app.py +++ b/v2_adminpanel/app.py @@ -44,15 +44,21 @@ scheduler.start() logging.basicConfig(level=logging.INFO) # Import and register blueprints -from routes.auth_routes import auth_bp -from routes.admin_routes import admin_bp -from routes.api_routes import api_bp -from routes.batch_routes import batch_bp -from routes.customer_routes import customer_bp -from routes.export_routes import export_bp -from routes.license_routes import license_bp -from routes.resource_routes import resource_bp -from routes.session_routes import session_bp +try: + from routes.auth_routes import auth_bp + from routes.admin_routes import admin_bp + from routes.api_routes import api_bp + from routes.batch_routes import batch_bp + from routes.customer_routes import customer_bp + from routes.export_routes import export_bp + from routes.license_routes import license_bp + from routes.resource_routes import resource_bp + from routes.session_routes import session_bp + print("All blueprints imported successfully!") +except Exception as e: + print(f"Blueprint import error: {str(e)}") + import traceback + traceback.print_exc() # Register all blueprints app.register_blueprint(auth_bp) @@ -66,6 +72,27 @@ app.register_blueprint(resource_bp) app.register_blueprint(session_bp) +# Debug routes to test +@app.route('/test-customers-licenses') +def test_route(): + return "Test route works! If you see this, routing is working." + +@app.route('/direct-customers-licenses') +def direct_customers_licenses(): + """Direct route without blueprint""" + try: + return render_template("customers_licenses.html", customers=[]) + except Exception as e: + return f"Error: {str(e)}" + +@app.route('/debug-routes') +def debug_routes(): + """Show all registered routes""" + routes = [] + for rule in app.url_map.iter_rules(): + routes.append(f"{rule.endpoint}: {rule.rule}") + return "
".join(sorted(routes)) + # Scheduled Backup Job def scheduled_backup(): """Erstellt ein automatisches Backup""" @@ -91,13 +118,21 @@ scheduler.add_job( # Error handlers @app.errorhandler(404) def not_found(e): - return render_template('404.html'), 404 + try: + return render_template('404.html'), 404 + except: + return "404 - Page not found", 404 @app.errorhandler(500) def server_error(e): - logging.error(f"Server error: {str(e)}") - return render_template('500.html'), 500 + import traceback + error_msg = f"Server error: {str(e)}\n{traceback.format_exc()}" + logging.error(error_msg) + try: + return render_template('500.html'), 500 + except: + return f"500 - Internal Server Error\n\n{error_msg}", 500 # Context processors @@ -112,5 +147,32 @@ def inject_global_vars(): } +# Simple test route that should always work +@app.route('/simple-test') +def simple_test(): + return "Simple test works!" + +@app.route('/test-db') +def test_db(): + """Test database connection""" + try: + import psycopg2 + conn = psycopg2.connect( + host=os.getenv("POSTGRES_HOST", "postgres"), + port=os.getenv("POSTGRES_PORT", "5432"), + dbname=os.getenv("POSTGRES_DB"), + user=os.getenv("POSTGRES_USER"), + password=os.getenv("POSTGRES_PASSWORD") + ) + cur = conn.cursor() + cur.execute("SELECT COUNT(*) FROM customers") + count = cur.fetchone()[0] + cur.close() + conn.close() + return f"Database works! Customers count: {count}" + except Exception as e: + import traceback + return f"Database error: {str(e)}
{traceback.format_exc()}
" + if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/v2_adminpanel/auth/decorators.py b/v2_adminpanel/auth/decorators.py index fda9c05..9e0b004 100644 --- a/v2_adminpanel/auth/decorators.py +++ b/v2_adminpanel/auth/decorators.py @@ -12,7 +12,7 @@ def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'logged_in' not in session: - return redirect(url_for('login')) + return redirect(url_for('auth.login')) # Check if session has expired if 'last_activity' in session: @@ -36,7 +36,7 @@ def login_required(f): pass session.clear() flash('Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.', 'warning') - return redirect(url_for('login')) + return redirect(url_for('auth.login')) # Activity is NOT automatically updated # Only on explicit user actions (done by heartbeat) diff --git a/v2_adminpanel/routes/__pycache__/__init__.cpython-312.pyc b/v2_adminpanel/routes/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d28b39db590c1b399a1eeafc8b4bedf67fc2d7d3 GIT binary patch literal 183 zcmX@j%ge<81WR)RGE#x`V-N=&d}aZPOlPQM&}8&m$xy@un39{Bmswm=lvt8qr0}5inxtQ5qze?wPAvkm;uAq?3lj5E zbM%YyOG{FV_2c7#noHv2^$IF~aoFVMrVi4maGb1Bo5i^hl01IC* AWdHyG literal 0 HcmV?d00001 diff --git a/v2_adminpanel/routes/__pycache__/customer_routes.cpython-312.pyc b/v2_adminpanel/routes/__pycache__/customer_routes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccb85f500cb0dae521efb774800d340346d71900 GIT binary patch literal 15807 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 literal 0 HcmV?d00001 diff --git a/v2_adminpanel/routes/admin_routes.py b/v2_adminpanel/routes/admin_routes.py index e26c12e..10f683f 100644 --- a/v2_adminpanel/routes/admin_routes.py +++ b/v2_adminpanel/routes/admin_routes.py @@ -20,8 +20,9 @@ admin_bp = Blueprint('admin', __name__) @login_required def dashboard(): try: - with get_db_connection() as conn: - with get_db_cursor(conn) as cur: + conn = get_connection() + cur = conn.cursor() + try: # Hole Statistiken mit sicheren Defaults # Anzahl aktiver Lizenzen cur.execute("SELECT COUNT(*) FROM licenses WHERE is_active = true") @@ -123,6 +124,9 @@ def dashboard(): hourly_sessions=hourly_sessions, resource_stats=resource_stats, username=session.get('username')) + finally: + cur.close() + conn.close() except Exception as e: current_app.logger.error(f"Dashboard error: {str(e)}") diff --git a/v2_adminpanel/routes/customer_routes.py b/v2_adminpanel/routes/customer_routes.py index 2f84c22..e99a6a9 100644 --- a/v2_adminpanel/routes/customer_routes.py +++ b/v2_adminpanel/routes/customer_routes.py @@ -1,3 +1,4 @@ +import os import logging from datetime import datetime from zoneinfo import ZoneInfo @@ -12,6 +13,11 @@ from models import get_customers, get_customer_by_id # Create Blueprint customer_bp = Blueprint('customers', __name__) +# Test route +@customer_bp.route("/test-customers") +def test_customers(): + return "Customer blueprint is working!" + @customer_bp.route("/customers") @login_required @@ -23,9 +29,6 @@ def customers(): @customer_bp.route("/customer/edit/", methods=["GET", "POST"]) @login_required def edit_customer(customer_id): - conn = get_connection() - cur = conn.cursor() - if request.method == "POST": try: # Get current customer data for comparison @@ -34,51 +37,44 @@ def edit_customer(customer_id): flash('Kunde nicht gefunden!', 'error') return redirect(url_for('customers.customers')) - # Update customer data - new_values = { - 'name': request.form['name'], - 'email': request.form['email'], - 'phone': request.form.get('phone', ''), - 'address': request.form.get('address', ''), - 'notes': request.form.get('notes', '') - } - - cur.execute(""" - UPDATE customers - SET name = %s, email = %s, phone = %s, address = %s, notes = %s - WHERE id = %s - """, ( - new_values['name'], - new_values['email'], - new_values['phone'], - new_values['address'], - new_values['notes'], - customer_id - )) - - conn.commit() - - # Log changes - log_audit('UPDATE', 'customer', customer_id, - old_values={ - 'name': current_customer['name'], - 'email': current_customer['email'], - 'phone': current_customer.get('phone', ''), - 'address': current_customer.get('address', ''), - 'notes': current_customer.get('notes', '') - }, - new_values=new_values) - - flash('Kunde erfolgreich aktualisiert!', 'success') - return redirect(url_for('customers.customers')) - + with get_db_connection() as conn: + cur = conn.cursor() + try: + # Update customer data (nur name und email existieren in der DB) + new_values = { + 'name': request.form['name'], + 'email': request.form['email'] + } + + cur.execute(""" + UPDATE customers + SET name = %s, email = %s + WHERE id = %s + """, ( + new_values['name'], + new_values['email'], + customer_id + )) + + conn.commit() + + # Log changes + log_audit('UPDATE', 'customer', customer_id, + old_values={ + 'name': current_customer['name'], + 'email': current_customer['email'] + }, + new_values=new_values) + + flash('Kunde erfolgreich aktualisiert!', 'success') + return redirect(url_for('customers.customers')) + finally: + cur.close() + except Exception as e: - conn.rollback() logging.error(f"Fehler beim Aktualisieren des Kunden: {str(e)}") flash('Fehler beim Aktualisieren des Kunden!', 'error') - finally: - cur.close() - conn.close() + return redirect(url_for('customers.customers')) # GET request customer_data = get_customer_by_id(customer_id) @@ -100,15 +96,12 @@ def create_customer(): # Insert new customer name = request.form['name'] email = request.form['email'] - phone = request.form.get('phone', '') - address = request.form.get('address', '') - notes = request.form.get('notes', '') cur.execute(""" - INSERT INTO customers (name, email, phone, address, notes, created_at) - VALUES (%s, %s, %s, %s, %s, %s) + INSERT INTO customers (name, email, created_at) + VALUES (%s, %s, %s) RETURNING id - """, (name, email, phone, address, notes, datetime.now())) + """, (name, email, datetime.now())) customer_id = cur.fetchone()[0] conn.commit() @@ -117,10 +110,7 @@ def create_customer(): log_audit('CREATE', 'customer', customer_id, new_values={ 'name': name, - 'email': email, - 'phone': phone, - 'address': address, - 'notes': notes + 'email': email }) flash(f'Kunde {name} erfolgreich erstellt!', 'success') @@ -187,49 +177,69 @@ def delete_customer(customer_id): @login_required def customers_licenses(): """Zeigt die Übersicht von Kunden und deren Lizenzen""" - conn = get_connection() - cur = conn.cursor() + import logging + import psycopg2 + logging.info("=== CUSTOMERS-LICENSES ROUTE CALLED ===") try: - # Hole alle Kunden mit ihren Lizenzen - cur.execute(""" - SELECT - c.id as customer_id, - c.name as customer_name, - c.email as customer_email, - c.created_at as customer_created, - COUNT(l.id) as license_count, - COUNT(CASE WHEN l.active = true THEN 1 END) as active_licenses, - COUNT(CASE WHEN l.is_test = true THEN 1 END) as test_licenses, - MAX(l.created_at) as last_license_created - FROM customers c - LEFT JOIN licenses l ON c.id = l.customer_id - GROUP BY c.id, c.name, c.email, c.created_at - ORDER BY c.name - """) + # Direkte Verbindung ohne Helper-Funktionen + conn = psycopg2.connect( + host=os.getenv("POSTGRES_HOST", "postgres"), + port=os.getenv("POSTGRES_PORT", "5432"), + dbname=os.getenv("POSTGRES_DB"), + user=os.getenv("POSTGRES_USER"), + password=os.getenv("POSTGRES_PASSWORD") + ) + conn.set_client_encoding('UTF8') + cur = conn.cursor() - customers = [] - for row in cur.fetchall(): - customers.append({ - 'id': row[0], - 'name': row[1], - 'email': row[2], - 'created_at': row[3], - 'license_count': row[4], - 'active_licenses': row[5], - 'test_licenses': row[6], - 'last_license_created': row[7] - }) - - return render_template("customers_licenses.html", customers=customers) + try: + # Hole alle Kunden mit ihren Lizenzen + cur.execute(""" + SELECT + c.id, + c.name, + c.email, + c.created_at, + COUNT(l.id), + COUNT(CASE WHEN l.is_active = true THEN 1 END), + COUNT(CASE WHEN l.is_test = true THEN 1 END), + MAX(l.created_at) + FROM customers c + LEFT JOIN licenses l ON c.id = l.customer_id + GROUP BY c.id, c.name, c.email, c.created_at + ORDER BY c.name + """) + + customers = [] + results = cur.fetchall() + logging.info(f"=== QUERY RETURNED {len(results)} ROWS ===") + + for idx, row in enumerate(results): + logging.info(f"Row {idx}: Type={type(row)}, Length={len(row) if hasattr(row, '__len__') else 'N/A'}") + customers.append({ + 'id': row[0], + 'name': row[1], + 'email': row[2], + 'created_at': row[3], + 'license_count': row[4], + 'active_licenses': row[5], + 'test_licenses': row[6], + 'last_license_created': row[7] + }) + + return render_template("customers_licenses.html", customers=customers) + + finally: + cur.close() + conn.close() except Exception as e: - logging.error(f"Fehler beim Laden der Kunden-Lizenz-Übersicht: {str(e)}") - flash('Fehler beim Laden der Daten!', 'error') + import traceback + error_details = f"Fehler beim Laden der Kunden-Lizenz-Übersicht: {str(e)}\nType: {type(e)}\nTraceback: {traceback.format_exc()}" + logging.error(error_details) + flash(f'Datenbankfehler: {str(e)}', 'error') return redirect(url_for('admin.dashboard')) - finally: - cur.close() - conn.close() @customer_bp.route("/api/customer//licenses") @@ -251,17 +261,17 @@ def api_customer_licenses(customer_id): l.id, l.license_key, l.license_type, - l.active, + l.is_active, l.is_test, l.valid_from, l.valid_until, l.device_limit, l.created_at, - (SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.active = true) as active_sessions, - (SELECT COUNT(DISTINCT device_id) FROM sessions s WHERE s.license_key = l.license_key) as registered_devices, + (SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.is_active = true) as active_sessions, + (SELECT COUNT(DISTINCT hardware_id) FROM sessions s WHERE s.license_key = l.license_key) as registered_devices, CASE WHEN l.valid_until < CURRENT_DATE THEN 'expired' - WHEN l.active = false THEN 'inactive' + WHEN l.is_active = false THEN 'inactive' ELSE 'active' END as status FROM licenses l @@ -314,7 +324,7 @@ def api_customer_quick_stats(customer_id): cur.execute(""" SELECT COUNT(l.id) as total_licenses, - COUNT(CASE WHEN l.active = true THEN 1 END) as active_licenses, + COUNT(CASE WHEN l.is_active = true THEN 1 END) as active_licenses, COUNT(CASE WHEN l.is_test = true THEN 1 END) as test_licenses, SUM(l.device_limit) as total_device_limit FROM licenses l diff --git a/v2_adminpanel/routes/resource_routes.py b/v2_adminpanel/routes/resource_routes.py index fe2dc0b..dcc43ef 100644 --- a/v2_adminpanel/routes/resource_routes.py +++ b/v2_adminpanel/routes/resource_routes.py @@ -133,54 +133,55 @@ def resources(): def add_resource(): """Neue Ressource hinzufügen""" if request.method == 'POST': - conn = get_connection() - cur = conn.cursor() - try: - resource_type = request.form['resource_type'] - resource_value = request.form['resource_value'].strip() - is_test = 'is_test' in request.form - - # Prüfe ob Ressource bereits existiert - cur.execute(""" - SELECT id FROM resource_pools - WHERE resource_type = %s AND resource_value = %s - """, (resource_type, resource_value)) - - if cur.fetchone(): - flash(f'Ressource {resource_value} existiert bereits!', 'error') - return redirect(url_for('resources.add_resource')) - - # Füge neue Ressource hinzu - cur.execute(""" - INSERT INTO resource_pools (resource_type, resource_value, status, is_test, created_by) - VALUES (%s, %s, 'available', %s, %s) - RETURNING id - """, (resource_type, resource_value, is_test, session['username'])) - - resource_id = cur.fetchone()[0] - conn.commit() - - # Audit-Log - log_audit('CREATE', 'resource', resource_id, - new_values={ - 'resource_type': resource_type, - 'resource_value': resource_value, - 'is_test': is_test - }) - - flash(f'Ressource {resource_value} erfolgreich hinzugefügt!', 'success') - return redirect(url_for('resources.resources')) - + with get_db_connection() as conn: + cur = conn.cursor() + try: + resource_type = request.form['resource_type'] + resource_value = request.form['resource_value'].strip() + is_test = 'is_test' in request.form + + # Prüfe ob Ressource bereits existiert + cur.execute(""" + SELECT id FROM resource_pools + WHERE resource_type = %s AND resource_value = %s + """, (resource_type, resource_value)) + + if cur.fetchone(): + flash(f'Ressource {resource_value} existiert bereits!', 'error') + return redirect(url_for('resources.add_resource')) + + # Füge neue Ressource hinzu (ohne created_by) + cur.execute(""" + INSERT INTO resource_pools (resource_type, resource_value, status, is_test, status_changed_by) + VALUES (%s, %s, 'available', %s, %s) + RETURNING id + """, (resource_type, resource_value, is_test, session.get('username', 'system'))) + + resource_id = cur.fetchone()[0] + conn.commit() + + # Audit-Log + log_audit('CREATE', 'resource', resource_id, + new_values={ + 'resource_type': resource_type, + 'resource_value': resource_value, + 'is_test': is_test + }) + + flash(f'Ressource {resource_value} erfolgreich hinzugefügt!', 'success') + return redirect(url_for('resources.resources')) + finally: + cur.close() + except Exception as e: - conn.rollback() + import traceback logging.error(f"Fehler beim Hinzufügen der Ressource: {str(e)}") - flash('Fehler beim Hinzufügen der Ressource!', 'error') - finally: - cur.close() - conn.close() + logging.error(f"Traceback: {traceback.format_exc()}") + flash(f'Fehler: {str(e)}', 'error') + return redirect(url_for('resources.resources')) - return render_template('add_resource.html') + return render_template('add_resources.html') @resource_bp.route('/resources/quarantine/', methods=['POST']) diff --git a/v2_adminpanel/templates/404.html b/v2_adminpanel/templates/404.html new file mode 100644 index 0000000..a36c93b --- /dev/null +++ b/v2_adminpanel/templates/404.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block title %}Seite nicht gefunden{% endblock %} + +{% block content %} +
+
+
+
+
+

404

+

Seite nicht gefunden

+

Die angeforderte Seite konnte nicht gefunden werden.

+ Zur Startseite +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/v2_adminpanel/templates/500.html b/v2_adminpanel/templates/500.html new file mode 100644 index 0000000..0185023 --- /dev/null +++ b/v2_adminpanel/templates/500.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block title %}Serverfehler{% endblock %} + +{% block content %} +
+
+
+
+
+

500

+

Interner Serverfehler

+

Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.

+ Zur Startseite +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/v2_adminpanel/templates/customers_licenses.html b/v2_adminpanel/templates/customers_licenses.html index ff92edd..94eeb9e 100644 --- a/v2_adminpanel/templates/customers_licenses.html +++ b/v2_adminpanel/templates/customers_licenses.html @@ -56,23 +56,23 @@ {% if customers %} {% for customer in customers %}
-
{{ customer[1] }}
- {{ customer[2] }} +
{{ customer.name }}
+ {{ customer.email }}
- {{ customer[4] }} - {% if customer[5] > 0 %} - {{ customer[5] }} + {{ customer.license_count }} + {% if customer.active_licenses > 0 %} + {{ customer.active_licenses }} {% endif %} - {% if customer[6] > 0 %} - {{ customer[6] }} + {% if customer.test_licenses > 0 %} + {{ customer.test_licenses }} {% endif %}