Refactoring - Fix1

Dieser Commit ist enthalten in:
2025-06-17 22:59:34 +02:00
Ursprung dbc8904b2c
Commit 0ec0d2c267
13 geänderte Dateien mit 553 neuen und 168 gelöschten Zeilen

Datei anzeigen

@@ -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

111
v2_adminpanel/FEHLERSUCHE.md Normale Datei
Datei anzeigen

@@ -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.

Datei anzeigen

@@ -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

Datei anzeigen

@@ -44,6 +44,7 @@ scheduler.start()
logging.basicConfig(level=logging.INFO)
# Import and register blueprints
try:
from routes.auth_routes import auth_bp
from routes.admin_routes import admin_bp
from routes.api_routes import api_bp
@@ -53,6 +54,11 @@ 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):
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)}")
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)

Datei anzeigen

@@ -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äre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@@ -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)}")

Datei anzeigen

@@ -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,25 +37,22 @@ def edit_customer(customer_id):
flash('Kunde nicht gefunden!', 'error')
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 = {
'name': request.form['name'],
'email': request.form['email'],
'phone': request.form.get('phone', ''),
'address': request.form.get('address', ''),
'notes': request.form.get('notes', '')
'email': request.form['email']
}
cur.execute("""
UPDATE customers
SET name = %s, email = %s, phone = %s, address = %s, notes = %s
SET name = %s, email = %s
WHERE id = %s
""", (
new_values['name'],
new_values['email'],
new_values['phone'],
new_values['address'],
new_values['notes'],
customer_id
))
@@ -62,23 +62,19 @@ def edit_customer(customer_id):
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', '')
'email': current_customer['email']
},
new_values=new_values)
flash('Kunde erfolgreich aktualisiert!', 'success')
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:
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
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,21 +177,34 @@ def delete_customer(customer_id):
@login_required
def customers_licenses():
"""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()
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
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
@@ -209,7 +212,11 @@ def customers_licenses():
""")
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({
'id': row[0],
'name': row[1],
@@ -223,14 +230,17 @@ def customers_licenses():
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:
cur.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")
@login_required
@@ -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

Datei anzeigen

@@ -133,9 +133,9 @@ def resources():
def add_resource():
"""Neue Ressource hinzufügen"""
if request.method == 'POST':
conn = get_connection()
try:
with get_db_connection() as conn:
cur = conn.cursor()
try:
resource_type = request.form['resource_type']
resource_value = request.form['resource_value'].strip()
@@ -151,12 +151,12 @@ def add_resource():
flash(f'Ressource {resource_value} existiert bereits!', 'error')
return redirect(url_for('resources.add_resource'))
# Füge neue Ressource hinzu
# Füge neue Ressource hinzu (ohne created_by)
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)
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]
conn.commit()
@@ -171,16 +171,17 @@ def add_resource():
flash(f'Ressource {resource_value} erfolgreich hinzugefügt!', 'success')
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:
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'])

Datei anzeigen

@@ -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 %}

Datei anzeigen

@@ -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 %}

Datei anzeigen

@@ -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>