Refactoring - Fix1
Dieser Commit ist enthalten in:
111
v2_adminpanel/FEHLERSUCHE.md
Normale Datei
111
v2_adminpanel/FEHLERSUCHE.md
Normale Datei
@@ -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.
|
||||
156
v2_adminpanel/MIGRATION_DISCREPANCIES.md
Normale Datei
156
v2_adminpanel/MIGRATION_DISCREPANCIES.md
Normale Datei
@@ -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/<int:customer_id>/licenses` - Get licenses for a customer
|
||||
- `/api/customer/<int:customer_id>/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/<int:resource_id>` - Quarantine a resource
|
||||
- `/resources/release` - Release resources from quarantine
|
||||
- `/resources/history/<int:resource_id>` - 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
|
||||
@@ -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 "<br>".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)}<br><pre>{traceback.format_exc()}</pre>"
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
@@ -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)
|
||||
|
||||
BIN
v2_adminpanel/routes/__pycache__/__init__.cpython-312.pyc
Normale Datei
BIN
v2_adminpanel/routes/__pycache__/__init__.cpython-312.pyc
Normale Datei
Binäre Datei nicht angezeigt.
BIN
v2_adminpanel/routes/__pycache__/customer_routes.cpython-312.pyc
Normale Datei
BIN
v2_adminpanel/routes/__pycache__/customer_routes.cpython-312.pyc
Normale Datei
Binäre Datei nicht angezeigt.
@@ -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)}")
|
||||
|
||||
@@ -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/<int:customer_id>", 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/<int:customer_id>/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
|
||||
|
||||
@@ -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/<int:resource_id>', methods=['POST'])
|
||||
|
||||
20
v2_adminpanel/templates/404.html
Normale Datei
20
v2_adminpanel/templates/404.html
Normale Datei
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Seite nicht gefunden{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="display-1">404</h1>
|
||||
<h2>Seite nicht gefunden</h2>
|
||||
<p>Die angeforderte Seite konnte nicht gefunden werden.</p>
|
||||
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
20
v2_adminpanel/templates/500.html
Normale Datei
20
v2_adminpanel/templates/500.html
Normale Datei
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Serverfehler{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="display-1">500</h1>
|
||||
<h2>Interner Serverfehler</h2>
|
||||
<p>Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.</p>
|
||||
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -56,23 +56,23 @@
|
||||
{% if customers %}
|
||||
{% for customer in customers %}
|
||||
<div class="customer-item p-3 border-bottom {% if customer[0] == selected_customer_id %}active{% endif %}"
|
||||
data-customer-id="{{ customer[0] }}"
|
||||
data-customer-name="{{ customer[1]|lower }}"
|
||||
data-customer-email="{{ customer[2]|lower }}"
|
||||
onclick="loadCustomerLicenses({{ customer[0] }})"
|
||||
data-customer-id="{{ customer.id }}"
|
||||
data-customer-name="{{ customer.name|lower }}"
|
||||
data-customer-email="{{ customer.email|lower }}"
|
||||
onclick="loadCustomerLicenses({{ customer.id }})"
|
||||
style="cursor: pointer;">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ customer[1] }}</h6>
|
||||
<small class="text-muted">{{ customer[2] }}</small>
|
||||
<h6 class="mb-1">{{ customer.name }}</h6>
|
||||
<small class="text-muted">{{ customer.email }}</small>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<span class="badge bg-primary">{{ customer[4] }}</span>
|
||||
{% if customer[5] > 0 %}
|
||||
<span class="badge bg-success">{{ customer[5] }}</span>
|
||||
<span class="badge bg-primary">{{ customer.license_count }}</span>
|
||||
{% if customer.active_licenses > 0 %}
|
||||
<span class="badge bg-success">{{ customer.active_licenses }}</span>
|
||||
{% endif %}
|
||||
{% if customer[6] > 0 %}
|
||||
<span class="badge bg-danger">{{ customer[6] }}</span>
|
||||
{% if customer.test_licenses > 0 %}
|
||||
<span class="badge bg-danger">{{ customer.test_licenses }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren