Refactoring - Fix1
Dieser Commit ist enthalten in:
@@ -2,3 +2,4 @@
|
|||||||
# https://curl.se/docs/http-cookies.html
|
# https://curl.se/docs/http-cookies.html
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
|
#HttpOnly_admin-panel-undso.z5m7q9dk3ah2v1plx6ju.com FALSE / FALSE 1750193106 admin_session AwO_9xkBcSaqhYwpkjUTL1bNPOMWUZ5qMXGUAwdTpNM
|
||||||
|
|||||||
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)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Import and register blueprints
|
# Import and register blueprints
|
||||||
from routes.auth_routes import auth_bp
|
try:
|
||||||
from routes.admin_routes import admin_bp
|
from routes.auth_routes import auth_bp
|
||||||
from routes.api_routes import api_bp
|
from routes.admin_routes import admin_bp
|
||||||
from routes.batch_routes import batch_bp
|
from routes.api_routes import api_bp
|
||||||
from routes.customer_routes import customer_bp
|
from routes.batch_routes import batch_bp
|
||||||
from routes.export_routes import export_bp
|
from routes.customer_routes import customer_bp
|
||||||
from routes.license_routes import license_bp
|
from routes.export_routes import export_bp
|
||||||
from routes.resource_routes import resource_bp
|
from routes.license_routes import license_bp
|
||||||
from routes.session_routes import session_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
|
# Register all blueprints
|
||||||
app.register_blueprint(auth_bp)
|
app.register_blueprint(auth_bp)
|
||||||
@@ -66,6 +72,27 @@ app.register_blueprint(resource_bp)
|
|||||||
app.register_blueprint(session_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
|
# Scheduled Backup Job
|
||||||
def scheduled_backup():
|
def scheduled_backup():
|
||||||
"""Erstellt ein automatisches Backup"""
|
"""Erstellt ein automatisches Backup"""
|
||||||
@@ -91,13 +118,21 @@ scheduler.add_job(
|
|||||||
# Error handlers
|
# Error handlers
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def not_found(e):
|
def not_found(e):
|
||||||
|
try:
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
except:
|
||||||
|
return "404 - Page not found", 404
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(500)
|
@app.errorhandler(500)
|
||||||
def server_error(e):
|
def server_error(e):
|
||||||
logging.error(f"Server error: {str(e)}")
|
import traceback
|
||||||
|
error_msg = f"Server error: {str(e)}\n{traceback.format_exc()}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
try:
|
||||||
return render_template('500.html'), 500
|
return render_template('500.html'), 500
|
||||||
|
except:
|
||||||
|
return f"500 - Internal Server Error\n\n{error_msg}", 500
|
||||||
|
|
||||||
|
|
||||||
# Context processors
|
# 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__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000)
|
app.run(host="0.0.0.0", port=5000)
|
||||||
@@ -12,7 +12,7 @@ def login_required(f):
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if 'logged_in' not in session:
|
if 'logged_in' not in session:
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
# Check if session has expired
|
# Check if session has expired
|
||||||
if 'last_activity' in session:
|
if 'last_activity' in session:
|
||||||
@@ -36,7 +36,7 @@ def login_required(f):
|
|||||||
pass
|
pass
|
||||||
session.clear()
|
session.clear()
|
||||||
flash('Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.', 'warning')
|
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
|
# Activity is NOT automatically updated
|
||||||
# Only on explicit user actions (done by heartbeat)
|
# 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
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
try:
|
try:
|
||||||
with get_db_connection() as conn:
|
conn = get_connection()
|
||||||
with get_db_cursor(conn) as cur:
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
# Hole Statistiken mit sicheren Defaults
|
# Hole Statistiken mit sicheren Defaults
|
||||||
# Anzahl aktiver Lizenzen
|
# Anzahl aktiver Lizenzen
|
||||||
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_active = true")
|
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_active = true")
|
||||||
@@ -123,6 +124,9 @@ def dashboard():
|
|||||||
hourly_sessions=hourly_sessions,
|
hourly_sessions=hourly_sessions,
|
||||||
resource_stats=resource_stats,
|
resource_stats=resource_stats,
|
||||||
username=session.get('username'))
|
username=session.get('username'))
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Dashboard error: {str(e)}")
|
current_app.logger.error(f"Dashboard error: {str(e)}")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
@@ -12,6 +13,11 @@ from models import get_customers, get_customer_by_id
|
|||||||
# Create Blueprint
|
# Create Blueprint
|
||||||
customer_bp = Blueprint('customers', __name__)
|
customer_bp = Blueprint('customers', __name__)
|
||||||
|
|
||||||
|
# Test route
|
||||||
|
@customer_bp.route("/test-customers")
|
||||||
|
def test_customers():
|
||||||
|
return "Customer blueprint is working!"
|
||||||
|
|
||||||
|
|
||||||
@customer_bp.route("/customers")
|
@customer_bp.route("/customers")
|
||||||
@login_required
|
@login_required
|
||||||
@@ -23,9 +29,6 @@ def customers():
|
|||||||
@customer_bp.route("/customer/edit/<int:customer_id>", methods=["GET", "POST"])
|
@customer_bp.route("/customer/edit/<int:customer_id>", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def edit_customer(customer_id):
|
def edit_customer(customer_id):
|
||||||
conn = get_connection()
|
|
||||||
cur = conn.cursor()
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
# Get current customer data for comparison
|
# Get current customer data for comparison
|
||||||
@@ -34,25 +37,22 @@ def edit_customer(customer_id):
|
|||||||
flash('Kunde nicht gefunden!', 'error')
|
flash('Kunde nicht gefunden!', 'error')
|
||||||
return redirect(url_for('customers.customers'))
|
return redirect(url_for('customers.customers'))
|
||||||
|
|
||||||
# Update customer data
|
with get_db_connection() as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
# Update customer data (nur name und email existieren in der DB)
|
||||||
new_values = {
|
new_values = {
|
||||||
'name': request.form['name'],
|
'name': request.form['name'],
|
||||||
'email': request.form['email'],
|
'email': request.form['email']
|
||||||
'phone': request.form.get('phone', ''),
|
|
||||||
'address': request.form.get('address', ''),
|
|
||||||
'notes': request.form.get('notes', '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
UPDATE customers
|
UPDATE customers
|
||||||
SET name = %s, email = %s, phone = %s, address = %s, notes = %s
|
SET name = %s, email = %s
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
""", (
|
""", (
|
||||||
new_values['name'],
|
new_values['name'],
|
||||||
new_values['email'],
|
new_values['email'],
|
||||||
new_values['phone'],
|
|
||||||
new_values['address'],
|
|
||||||
new_values['notes'],
|
|
||||||
customer_id
|
customer_id
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -62,23 +62,19 @@ def edit_customer(customer_id):
|
|||||||
log_audit('UPDATE', 'customer', customer_id,
|
log_audit('UPDATE', 'customer', customer_id,
|
||||||
old_values={
|
old_values={
|
||||||
'name': current_customer['name'],
|
'name': current_customer['name'],
|
||||||
'email': current_customer['email'],
|
'email': current_customer['email']
|
||||||
'phone': current_customer.get('phone', ''),
|
|
||||||
'address': current_customer.get('address', ''),
|
|
||||||
'notes': current_customer.get('notes', '')
|
|
||||||
},
|
},
|
||||||
new_values=new_values)
|
new_values=new_values)
|
||||||
|
|
||||||
flash('Kunde erfolgreich aktualisiert!', 'success')
|
flash('Kunde erfolgreich aktualisiert!', 'success')
|
||||||
return redirect(url_for('customers.customers'))
|
return redirect(url_for('customers.customers'))
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
conn.rollback()
|
|
||||||
logging.error(f"Fehler beim Aktualisieren des Kunden: {str(e)}")
|
|
||||||
flash('Fehler beim Aktualisieren des Kunden!', 'error')
|
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Fehler beim Aktualisieren des Kunden: {str(e)}")
|
||||||
|
flash('Fehler beim Aktualisieren des Kunden!', 'error')
|
||||||
|
return redirect(url_for('customers.customers'))
|
||||||
|
|
||||||
# GET request
|
# GET request
|
||||||
customer_data = get_customer_by_id(customer_id)
|
customer_data = get_customer_by_id(customer_id)
|
||||||
@@ -100,15 +96,12 @@ def create_customer():
|
|||||||
# Insert new customer
|
# Insert new customer
|
||||||
name = request.form['name']
|
name = request.form['name']
|
||||||
email = request.form['email']
|
email = request.form['email']
|
||||||
phone = request.form.get('phone', '')
|
|
||||||
address = request.form.get('address', '')
|
|
||||||
notes = request.form.get('notes', '')
|
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO customers (name, email, phone, address, notes, created_at)
|
INSERT INTO customers (name, email, created_at)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""", (name, email, phone, address, notes, datetime.now()))
|
""", (name, email, datetime.now()))
|
||||||
|
|
||||||
customer_id = cur.fetchone()[0]
|
customer_id = cur.fetchone()[0]
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -117,10 +110,7 @@ def create_customer():
|
|||||||
log_audit('CREATE', 'customer', customer_id,
|
log_audit('CREATE', 'customer', customer_id,
|
||||||
new_values={
|
new_values={
|
||||||
'name': name,
|
'name': name,
|
||||||
'email': email,
|
'email': email
|
||||||
'phone': phone,
|
|
||||||
'address': address,
|
|
||||||
'notes': notes
|
|
||||||
})
|
})
|
||||||
|
|
||||||
flash(f'Kunde {name} erfolgreich erstellt!', 'success')
|
flash(f'Kunde {name} erfolgreich erstellt!', 'success')
|
||||||
@@ -187,21 +177,34 @@ def delete_customer(customer_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def customers_licenses():
|
def customers_licenses():
|
||||||
"""Zeigt die Übersicht von Kunden und deren Lizenzen"""
|
"""Zeigt die Übersicht von Kunden und deren Lizenzen"""
|
||||||
conn = get_connection()
|
import logging
|
||||||
|
import psycopg2
|
||||||
|
logging.info("=== CUSTOMERS-LICENSES ROUTE CALLED ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 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()
|
cur = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Hole alle Kunden mit ihren Lizenzen
|
# Hole alle Kunden mit ihren Lizenzen
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
c.id as customer_id,
|
c.id,
|
||||||
c.name as customer_name,
|
c.name,
|
||||||
c.email as customer_email,
|
c.email,
|
||||||
c.created_at as customer_created,
|
c.created_at,
|
||||||
COUNT(l.id) as license_count,
|
COUNT(l.id),
|
||||||
COUNT(CASE WHEN l.active = true THEN 1 END) as active_licenses,
|
COUNT(CASE WHEN l.is_active = true THEN 1 END),
|
||||||
COUNT(CASE WHEN l.is_test = true THEN 1 END) as test_licenses,
|
COUNT(CASE WHEN l.is_test = true THEN 1 END),
|
||||||
MAX(l.created_at) as last_license_created
|
MAX(l.created_at)
|
||||||
FROM customers c
|
FROM customers c
|
||||||
LEFT JOIN licenses l ON c.id = l.customer_id
|
LEFT JOIN licenses l ON c.id = l.customer_id
|
||||||
GROUP BY c.id, c.name, c.email, c.created_at
|
GROUP BY c.id, c.name, c.email, c.created_at
|
||||||
@@ -209,7 +212,11 @@ def customers_licenses():
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
customers = []
|
customers = []
|
||||||
for row in cur.fetchall():
|
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({
|
customers.append({
|
||||||
'id': row[0],
|
'id': row[0],
|
||||||
'name': row[1],
|
'name': row[1],
|
||||||
@@ -223,14 +230,17 @@ def customers_licenses():
|
|||||||
|
|
||||||
return render_template("customers_licenses.html", customers=customers)
|
return render_template("customers_licenses.html", customers=customers)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Fehler beim Laden der Kunden-Lizenz-Übersicht: {str(e)}")
|
|
||||||
flash('Fehler beim Laden der Daten!', 'error')
|
|
||||||
return redirect(url_for('admin.dashboard'))
|
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
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'))
|
||||||
|
|
||||||
|
|
||||||
@customer_bp.route("/api/customer/<int:customer_id>/licenses")
|
@customer_bp.route("/api/customer/<int:customer_id>/licenses")
|
||||||
@login_required
|
@login_required
|
||||||
@@ -251,17 +261,17 @@ def api_customer_licenses(customer_id):
|
|||||||
l.id,
|
l.id,
|
||||||
l.license_key,
|
l.license_key,
|
||||||
l.license_type,
|
l.license_type,
|
||||||
l.active,
|
l.is_active,
|
||||||
l.is_test,
|
l.is_test,
|
||||||
l.valid_from,
|
l.valid_from,
|
||||||
l.valid_until,
|
l.valid_until,
|
||||||
l.device_limit,
|
l.device_limit,
|
||||||
l.created_at,
|
l.created_at,
|
||||||
(SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.active = true) as active_sessions,
|
(SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.is_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(DISTINCT hardware_id) FROM sessions s WHERE s.license_key = l.license_key) as registered_devices,
|
||||||
CASE
|
CASE
|
||||||
WHEN l.valid_until < CURRENT_DATE THEN 'expired'
|
WHEN l.valid_until < CURRENT_DATE THEN 'expired'
|
||||||
WHEN l.active = false THEN 'inactive'
|
WHEN l.is_active = false THEN 'inactive'
|
||||||
ELSE 'active'
|
ELSE 'active'
|
||||||
END as status
|
END as status
|
||||||
FROM licenses l
|
FROM licenses l
|
||||||
@@ -314,7 +324,7 @@ def api_customer_quick_stats(customer_id):
|
|||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(l.id) as total_licenses,
|
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,
|
COUNT(CASE WHEN l.is_test = true THEN 1 END) as test_licenses,
|
||||||
SUM(l.device_limit) as total_device_limit
|
SUM(l.device_limit) as total_device_limit
|
||||||
FROM licenses l
|
FROM licenses l
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ def resources():
|
|||||||
def add_resource():
|
def add_resource():
|
||||||
"""Neue Ressource hinzufügen"""
|
"""Neue Ressource hinzufügen"""
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
conn = get_connection()
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resource_type = request.form['resource_type']
|
resource_type = request.form['resource_type']
|
||||||
resource_value = request.form['resource_value'].strip()
|
resource_value = request.form['resource_value'].strip()
|
||||||
@@ -151,12 +151,12 @@ def add_resource():
|
|||||||
flash(f'Ressource {resource_value} existiert bereits!', 'error')
|
flash(f'Ressource {resource_value} existiert bereits!', 'error')
|
||||||
return redirect(url_for('resources.add_resource'))
|
return redirect(url_for('resources.add_resource'))
|
||||||
|
|
||||||
# Füge neue Ressource hinzu
|
# Füge neue Ressource hinzu (ohne created_by)
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO resource_pools (resource_type, resource_value, status, is_test, created_by)
|
INSERT INTO resource_pools (resource_type, resource_value, status, is_test, status_changed_by)
|
||||||
VALUES (%s, %s, 'available', %s, %s)
|
VALUES (%s, %s, 'available', %s, %s)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""", (resource_type, resource_value, is_test, session['username']))
|
""", (resource_type, resource_value, is_test, session.get('username', 'system')))
|
||||||
|
|
||||||
resource_id = cur.fetchone()[0]
|
resource_id = cur.fetchone()[0]
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -171,16 +171,17 @@ def add_resource():
|
|||||||
|
|
||||||
flash(f'Ressource {resource_value} erfolgreich hinzugefügt!', 'success')
|
flash(f'Ressource {resource_value} erfolgreich hinzugefügt!', 'success')
|
||||||
return redirect(url_for('resources.resources'))
|
return redirect(url_for('resources.resources'))
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
conn.rollback()
|
|
||||||
logging.error(f"Fehler beim Hinzufügen der Ressource: {str(e)}")
|
|
||||||
flash('Fehler beim Hinzufügen der Ressource!', 'error')
|
|
||||||
finally:
|
finally:
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return render_template('add_resource.html')
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
logging.error(f"Fehler beim Hinzufügen der Ressource: {str(e)}")
|
||||||
|
logging.error(f"Traceback: {traceback.format_exc()}")
|
||||||
|
flash(f'Fehler: {str(e)}', 'error')
|
||||||
|
return redirect(url_for('resources.resources'))
|
||||||
|
|
||||||
|
return render_template('add_resources.html')
|
||||||
|
|
||||||
|
|
||||||
@resource_bp.route('/resources/quarantine/<int:resource_id>', methods=['POST'])
|
@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 %}
|
{% if customers %}
|
||||||
{% for customer in customers %}
|
{% for customer in customers %}
|
||||||
<div class="customer-item p-3 border-bottom {% if customer[0] == selected_customer_id %}active{% endif %}"
|
<div class="customer-item p-3 border-bottom {% if customer[0] == selected_customer_id %}active{% endif %}"
|
||||||
data-customer-id="{{ customer[0] }}"
|
data-customer-id="{{ customer.id }}"
|
||||||
data-customer-name="{{ customer[1]|lower }}"
|
data-customer-name="{{ customer.name|lower }}"
|
||||||
data-customer-email="{{ customer[2]|lower }}"
|
data-customer-email="{{ customer.email|lower }}"
|
||||||
onclick="loadCustomerLicenses({{ customer[0] }})"
|
onclick="loadCustomerLicenses({{ customer.id }})"
|
||||||
style="cursor: pointer;">
|
style="cursor: pointer;">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h6 class="mb-1">{{ customer[1] }}</h6>
|
<h6 class="mb-1">{{ customer.name }}</h6>
|
||||||
<small class="text-muted">{{ customer[2] }}</small>
|
<small class="text-muted">{{ customer.email }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<span class="badge bg-primary">{{ customer[4] }}</span>
|
<span class="badge bg-primary">{{ customer.license_count }}</span>
|
||||||
{% if customer[5] > 0 %}
|
{% if customer.active_licenses > 0 %}
|
||||||
<span class="badge bg-success">{{ customer[5] }}</span>
|
<span class="badge bg-success">{{ customer.active_licenses }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if customer[6] > 0 %}
|
{% if customer.test_licenses > 0 %}
|
||||||
<span class="badge bg-danger">{{ customer[6] }}</span>
|
<span class="badge bg-danger">{{ customer.test_licenses }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren