FIX 3 für Codex

Dieser Commit ist enthalten in:
2025-06-18 01:35:54 +02:00
Ursprung a9cfecc699
Commit 231aa4caed
30 geänderte Dateien mit 573 neuen und 213 gelöschten Zeilen

Datei anzeigen

@@ -1,6 +1,6 @@
# Fehlersuche - v2_adminpanel Refactoring # Fehlersuche - v2_adminpanel Refactoring
## Aktueller Stand (17.06.2025) ## Aktueller Stand (17.06.2025 - 11:00 Uhr)
### Erfolgreiches Refactoring ### Erfolgreiches Refactoring
- Die ursprüngliche 5000+ Zeilen große app.py wurde erfolgreich in Module aufgeteilt: - Die ursprüngliche 5000+ Zeilen große app.py wurde erfolgreich in Module aufgeteilt:
@@ -15,7 +15,10 @@
- ✅ Blueprint-Registrierung funktioniert korrekt - ✅ Blueprint-Registrierung funktioniert korrekt
- ✅ /test-db Route funktioniert nach Docker-Rebuild - ✅ /test-db Route funktioniert nach Docker-Rebuild
- ✅ Kunden-Anzeige funktioniert mit Test-Daten-Filter - ✅ Kunden-Anzeige funktioniert mit Test-Daten-Filter
- ✅ Lizenzen-Anzeige funktioniert mit erweiterten Filtern
- ✅ Batch-Lizenzerstellung funktioniert - ✅ Batch-Lizenzerstellung funktioniert
- ✅ Ressourcen-Pool funktioniert vollständig
- ✅ Ressourcen hinzufügen funktioniert
### Gelöste Probleme ### Gelöste Probleme
@@ -136,4 +139,128 @@ Folgende Templates wurden von Tuple-Zugriff auf Dictionary-Zugriff umgestellt:
### Status ### Status
✅ **Alle bekannten Probleme wurden behoben** ✅ **Alle bekannten Probleme wurden behoben**
✅ **Admin-Panel ist vollständig funktionsfähig** ✅ **Admin-Panel ist vollständig funktionsfähig**
✅ **Docker Container läuft stabil** ✅ **Docker Container läuft stabil**
## Weitere gelöste Probleme (17.06.2025 - 11:00 Uhr)
### 1. **Test-Daten Checkbox funktioniert nicht** ✅ GELÖST
**Problem**: Die Checkbox zum Anzeigen von Test-Daten in Kunden- und Lizenzansicht funktionierte nicht
**Ursache**: Fehlende Blueprint-Präfixe in Template-URLs
**Lösung**:
- `customers.html`: Alle `url_for('customers')` → `url_for('customer.customers')`
- `licenses.html`: Alle `url_for('licenses')` → `url_for('license.licenses')`
- Formulare senden jetzt korrekt mit `show_test` Parameter
### 2. **Lizenz-Filter erweitert** ✅ GELÖST
**Problem**: Filter für Test-/Live-Daten fehlte in Lizenzansicht
**Lösung**: `license_routes.py` erweitert mit:
- Typ-Filter: `full`, `test`, `test_data`, `live_data`
- Status-Filter: `active`, `expiring`, `expired`, `inactive`
- Suche über Lizenzschlüssel, Kundenname und E-Mail
### 3. **Resource Pool Anzeige** ✅ GELÖST
**Problem**: Ressourcen-Pool Seite hatte fehlerhafte Links und Filter funktionierten nicht
**Lösung**:
- `resources.html`: Form-Action korrigiert zu `url_for('resources.resources')`
- JavaScript `toggleTestResources()` arbeitet jetzt mit URL-Parametern
- Alle Sortier- und Paginierungs-Links korrigiert
### 4. **Ressourcen hinzufügen fehlte** ✅ GELÖST
**Problem**: Route `/resources/add` existierte nicht
**Lösung**: Komplette `add_resources()` Funktion in `resource_routes.py` implementiert:
- Validierung für Domains, IPv4-Adressen und Telefonnummern
- Duplikat-Prüfung
- Bulk-Import mit detailliertem Feedback
- Test/Produktion Unterscheidung
### 5. **Navigation-Links** ✅ GELÖST
**Problem**: Sidebar-Links für Ressourcen verwendeten hardcodierte URLs
**Lösung**: `base.html` aktualisiert:
- Resource Pool Link: `href="{{ url_for('resources.resources') }}"`
- Add Resources Link: `href="{{ url_for('resources.add_resources') }}"`
- Active-Status Prüfung korrigiert für Blueprint-Endpunkte
## Routing-Analyse (17.06.2025 - 11:30 Uhr)
### Identifizierte Routing-Probleme
Nach systematischer Analyse wurden folgende Routing-Probleme gefunden:
#### 1. **Fehlende Blueprint-Präfixe** ⚠️ OFFEN
Viele `url_for()` Aufrufe fehlen Blueprint-Präfixe. Dies verursacht 500-Fehler:
**Betroffene Templates:**
- `profile.html`: 3 fehlerhafte Aufrufe (`change_password`, `disable_2fa`, `setup_2fa`)
- `setup_2fa.html`: 2 fehlerhafte Aufrufe (`profile`, `enable_2fa`)
- `backup_codes.html`: 1 fehlerhafter Aufruf (`profile`)
- `resource_history.html`: 2 fehlerhafte Aufrufe (`resources`, `edit_license`)
- `resource_metrics.html`: 2 fehlerhafte Aufrufe (`resources`, `resources_report`)
- `resource_report.html`: 2 fehlerhafte Aufrufe
- `sessions.html`: Mehrere fehlerhafte Aufrufe
- `audit_log.html`: Mehrere fehlerhafte Aufrufe
#### 2. **Hardcodierte URLs** ⚠️ OFFEN
Über 50 hardcodierte URLs gefunden, die mit `url_for()` ersetzt werden sollten:
**Hauptprobleme in `base.html`:**
- `href="/"` → `href="{{ url_for('admin.dashboard') }}"`
- `href="/profile"` → `href="{{ url_for('auth.profile') }}"`
- `href="/logout"` → `href="{{ url_for('auth.logout') }}"`
- `href="/customers-licenses"` → `href="{{ url_for('customer.customers_licenses') }}"`
- `href="/customer/create"` → `href="{{ url_for('customer.create_customer') }}"`
- `href="/create"` → `href="{{ url_for('license.create_license') }}"`
- `href="/batch"` → `href="{{ url_for('batch.batch_create') }}"`
- `href="/audit"` → `href="{{ url_for('admin.audit_log') }}"`
- `href="/sessions"` → `href="{{ url_for('session.sessions') }}"`
- `href="/backups"` → `href="{{ url_for('admin.backups') }}"`
#### 3. **Doppelte Route-Definitionen** ✅ GELÖST
- Entfernt: Doppelte `add_resource` Funktion in `resource_routes.py`
#### 4. **Route-Namenskonsistenz** ⚠️ OFFEN
- `resource_report` vs `resources_report` - inkonsistente Benennung
### Prioritäten für Fixes
1. **KRITISCH**: Fehlende Blueprint-Präfixe (verursachen 500-Fehler)
2. **HOCH**: Hardcodierte URLs in Navigation (`base.html`)
3. **MITTEL**: Andere hardcodierte URLs
4. **NIEDRIG**: Namenskonsistenz
### Vollständiger Report
Ein detaillierter Report wurde erstellt: `ROUTING_ISSUES_REPORT.md`
## Aktuelle Probleme (18.06.2025 - 01:30 Uhr)
### 1. **Resources Route funktioniert nicht** ❌ NICHT GELÖST
**Problem**: `/resources` Route leitet auf Dashboard um mit Fehlermeldung "Fehler beim Laden der Ressourcen!"
**Fehlermeldungen im Log**:
1. Ursprünglich: `FEHLER: Spalte l.customer_name existiert nicht`
2. Nach Fix: `'dict object' has no attribute 'total'`
**Versuchte Lösungen**:
1. SQL-Query in `resource_routes.py` korrigiert:
- JOIN mit customers Tabelle hinzugefügt für `c.name as customer_name`
- `l.customer_name` → `c.name` in WHERE-Klausel
2. Stats Dictionary erweitert um `'total': 0` und `stats[res_type]['total'] += count`
3. Template `resources.html` angepasst: `data.quarantine` → `data.quarantined`
**Status**: Trotz aller Fixes funktioniert die Route weiterhin nicht
### 2. **URL-Generierungsfehler** ✅ GELÖST
**Problem**: Mehrere `url_for()` Aufrufe mit falschen Endpunkt-Namen
**Gelöste Fehler**:
- `api.generate_license_key` → `api.api_generate_key`
- `api.customers` → `api.api_customers`
- `export.customers` → `export.export_customers`
- `export.licenses` → `export.export_licenses`
- `url_for()` mit leeren Parametern durch hardcodierte URLs ersetzt
### 3. **Customers-Licenses Route** ❌ NICHT GELÖST
**Problem**: `/customers-licenses` Route leitet auf Dashboard um
**Fehlermeldung im Log**: `ValueError: invalid literal for int() with base 10: ''`
**Ursache**: Template versucht `url_for('licenses.edit_license', license_id='')` mit leerem String aufzurufen
**Versuchte Lösungen**:
- `url_for('licenses.edit_license', license_id='')` durch hardcodierte URL ersetzt: `/license/edit/${license.id}`
- `url_for('customers.edit_customer', customer_id='')` durch hardcodierte URL ersetzt: `/customer/edit/${customerId}`
**Status**: Route funktioniert trotz Fixes nicht

Datei anzeigen

@@ -0,0 +1,118 @@
# V2 Admin Panel - Routing Issues Report
Generated: 2025-06-17
## Summary of Findings
After systematically analyzing the v2_adminpanel application, I've identified several routing issues that need to be addressed:
### 1. Missing Blueprint Prefixes in url_for() Calls
The following templates have `url_for()` calls that are missing the required blueprint prefix:
#### In `profile.html`:
- `url_for('change_password')` → Should be `url_for('auth.change_password')`
- `url_for('disable_2fa')` → Should be `url_for('auth.disable_2fa')`
- `url_for('setup_2fa')` → Should be `url_for('auth.setup_2fa')`
#### In `setup_2fa.html`:
- `url_for('profile')` → Should be `url_for('auth.profile')`
- `url_for('enable_2fa')` → Should be `url_for('auth.enable_2fa')`
#### In `backup_codes.html`:
- `url_for('profile')` → Should be `url_for('auth.profile')`
#### In `resource_history.html`:
- `url_for('resources')` → Should be `url_for('resources.resources')`
- `url_for('edit_license', license_id=...)` → Should be `url_for('licenses.edit_license', license_id=...)`
#### In `resource_metrics.html`:
- `url_for('resources')` → Should be `url_for('resources.resources')`
- `url_for('resources_report')` → Should be `url_for('resources.resource_report')`
#### In `resource_report.html`:
- `url_for('resources')` → Should be `url_for('resources.resources')`
- `url_for('resources_report')` → Should be `url_for('resources.resource_report')`
#### In `sessions.html`:
- `url_for('sessions', ...)` → Should be `url_for('sessions.sessions', ...)`
#### In `audit_log.html`:
- `url_for('audit_log', ...)` → Should be `url_for('admin.audit_log', ...)`
#### In `licenses.html`:
- `url_for('licenses', ...)` → Should be `url_for('licenses.licenses', ...)`
#### In `customers.html`:
- `url_for('customers', ...)` → Should be `url_for('customers.customers', ...)`
#### In `resources.html`:
- Several instances of incorrect references:
- `url_for('customers.customers_licenses', ...)` → Should be `url_for('customers.customers_licenses', ...)`
- `url_for('licenses.edit_license', ...)` → Correct
- `url_for('resource_history', ...)` → Should be `url_for('resources.resource_history', ...)`
- `url_for('edit_license', ...)` → Should be `url_for('licenses.edit_license', ...)`
- `url_for('customers_licenses', ...)` → Should be `url_for('customers.customers_licenses', ...)`
### 2. Hardcoded URLs That Need Replacement
Many templates contain hardcoded URLs that should be replaced with `url_for()` calls:
#### In `base.html`:
- `href="/"` → Should be `href="{{ url_for('admin.index') }}"`
- `href="/profile"` → Should be `href="{{ url_for('auth.profile') }}"`
- `href="/logout"` → Should be `href="{{ url_for('auth.logout') }}"`
- `href="/customers-licenses"` → Should be `href="{{ url_for('customers.customers_licenses') }}"`
- `href="/customer/create"` → Should be `href="{{ url_for('customers.create_customer') }}"`
- `href="/create"` → Should be `href="{{ url_for('licenses.create_license') }}"`
- `href="/batch"` → Should be `href="{{ url_for('batch.batch_licenses') }}"`
- `href="/audit"` → Should be `href="{{ url_for('admin.audit_log') }}"`
- `href="/sessions"` → Should be `href="{{ url_for('sessions.sessions') }}"`
- `href="/backups"` → Should be `href="{{ url_for('admin.backups') }}"`
- `href="/security/blocked-ips"` → Should be `href="{{ url_for('admin.blocked_ips') }}"`
#### In `customers_licenses.html` and `customers_licenses_old.html`:
- Multiple hardcoded URLs for editing, creating, and exporting that need to be replaced with proper `url_for()` calls
#### In `edit_license.html`, `create_customer.html`, `index.html`:
- `href="/customers-licenses"` → Should use `url_for()`
#### In `dashboard.html`:
- Multiple hardcoded URLs that should use `url_for()`
#### In error pages (`404.html`, `500.html`):
- `href="/"` → Should be `href="{{ url_for('admin.index') }}"`
### 3. Blueprint Configuration
Current blueprint configuration:
- `export_bp` has `url_prefix='/export'`
- `api_bp` has `url_prefix='/api'`
- All other blueprints have no url_prefix
### 4. Route Naming Inconsistencies
Some routes have inconsistent naming between the route definition and the function name:
- Route `/resources/report` has function name `resource_report` (note the singular vs plural)
- This causes confusion with `url_for()` calls
### 5. Duplicate Route Risk Areas
While no exact duplicates were found, there are potential conflicts:
- Both `admin_bp` and `customer_bp` might handle customer-related routes
- API routes in `api_bp` overlap with functionality in other blueprints
## Recommendations
1. **Fix all `url_for()` calls** to include the correct blueprint prefix
2. **Replace all hardcoded URLs** with `url_for()` calls
3. **Standardize route naming** to match function names
4. **Add url_prefix to blueprints** where appropriate to avoid conflicts
5. **Create a route mapping document** for developers to reference
## Priority Actions
1. **High Priority**: Fix missing blueprint prefixes in `url_for()` calls - these will cause runtime errors
2. **High Priority**: Replace hardcoded URLs in navigation (base.html) - affects site-wide navigation
3. **Medium Priority**: Fix other hardcoded URLs in individual templates
4. **Low Priority**: Refactor route naming for consistency

Datei anzeigen

@@ -21,11 +21,73 @@ license_bp = Blueprint('licenses', __name__)
@login_required @login_required
def licenses(): def licenses():
from datetime import datetime, timedelta from datetime import datetime, timedelta
show_test = request.args.get('show_test', 'false') == 'true'
# Get filter parameters
search = request.args.get('search', '').strip()
filter_type = request.args.get('type', '')
filter_status = request.args.get('status', '')
sort = request.args.get('sort', 'created_at')
order = request.args.get('order', 'desc')
page = request.args.get('page', 1, type=int)
per_page = 50
# Process type filter to determine show_test
show_test = filter_type in ['test_data', 'test']
# Get licenses based on filters
licenses_list = get_licenses(show_test=show_test) licenses_list = get_licenses(show_test=show_test)
# Additional filtering based on type and status
if filter_type:
if filter_type == 'full':
licenses_list = [l for l in licenses_list if l.get('license_type') == 'full' and not l.get('is_test')]
elif filter_type == 'test':
licenses_list = [l for l in licenses_list if l.get('license_type') == 'test' and not l.get('is_test')]
elif filter_type == 'test_data':
licenses_list = [l for l in licenses_list if l.get('is_test')]
elif filter_type == 'live_data':
licenses_list = [l for l in licenses_list if not l.get('is_test')]
# Status filtering
if filter_status:
now = datetime.now()
if filter_status == 'active':
licenses_list = [l for l in licenses_list if l.get('is_active') and l.get('valid_until') and l.get('valid_until') > now]
elif filter_status == 'expiring':
expiry_threshold = now + timedelta(days=30)
licenses_list = [l for l in licenses_list if l.get('valid_until') and now < l.get('valid_until') <= expiry_threshold]
elif filter_status == 'expired':
licenses_list = [l for l in licenses_list if l.get('valid_until') and l.get('valid_until') <= now]
elif filter_status == 'inactive':
licenses_list = [l for l in licenses_list if not l.get('is_active')]
# Search filtering
if search:
search_lower = search.lower()
licenses_list = [l for l in licenses_list if
search_lower in str(l.get('license_key', '')).lower() or
search_lower in str(l.get('customer_name', '')).lower() or
search_lower in str(l.get('customer_email', '')).lower()]
# Calculate pagination
total = len(licenses_list)
total_pages = (total + per_page - 1) // per_page
start = (page - 1) * per_page
end = start + per_page
licenses_list = licenses_list[start:end]
return render_template("licenses.html", return render_template("licenses.html",
licenses=licenses_list, licenses=licenses_list,
show_test=show_test, show_test=show_test,
search=search,
filter_type=filter_type,
filter_status=filter_status,
sort=sort,
order=order,
page=page,
total=total,
total_pages=total_pages,
per_page=per_page,
now=datetime.now, now=datetime.now,
timedelta=timedelta) timedelta=timedelta)

Datei anzeigen

@@ -1,7 +1,7 @@
import logging import logging
from datetime import datetime from datetime import datetime
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from flask import Blueprint, render_template, request, redirect, session, url_for, flash, jsonify from flask import Blueprint, render_template, request, redirect, session, url_for, flash, jsonify, send_file
import config import config
from auth.decorators import login_required from auth.decorators import login_required
@@ -39,10 +39,11 @@ def resources():
rp.created_at, rp.created_at,
rp.status_changed_at, rp.status_changed_at,
rp.status_changed_by, rp.status_changed_by,
l.customer_name, c.name as customer_name,
l.license_type l.license_type
FROM resource_pools rp FROM resource_pools rp
LEFT JOIN licenses l ON rp.allocated_to_license = l.id LEFT JOIN licenses l ON rp.allocated_to_license = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE 1=1 WHERE 1=1
""" """
@@ -58,7 +59,7 @@ def resources():
params.append(status_filter) params.append(status_filter)
if search_query: if search_query:
query += " AND (rp.resource_value ILIKE %s OR l.customer_name ILIKE %s)" query += " AND (rp.resource_value ILIKE %s OR c.name ILIKE %s)"
params.extend([f'%{search_query}%', f'%{search_query}%']) params.extend([f'%{search_query}%', f'%{search_query}%'])
if not show_test: if not show_test:
@@ -103,8 +104,9 @@ def resources():
count = row[3] count = row[3]
if res_type not in stats: if res_type not in stats:
stats[res_type] = {'available': 0, 'allocated': 0, 'quarantined': 0, 'test': 0, 'prod': 0} stats[res_type] = {'total': 0, 'available': 0, 'allocated': 0, 'quarantined': 0, 'test': 0, 'prod': 0}
stats[res_type]['total'] += count
stats[res_type][status] = stats[res_type].get(status, 0) + count stats[res_type][status] = stats[res_type].get(status, 0) + count
if is_test: if is_test:
stats[res_type]['test'] += count stats[res_type]['test'] += count
@@ -128,60 +130,7 @@ def resources():
conn.close() conn.close()
@resource_bp.route('/resources/add', methods=['GET', 'POST']) # Old add_resource function removed - using add_resources instead
@login_required
def add_resource():
"""Neue Ressource hinzufügen"""
if request.method == 'POST':
try:
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:
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'])
@@ -615,4 +564,108 @@ def resource_report():
return redirect(url_for('resources.resources')) return redirect(url_for('resources.resources'))
finally: finally:
cur.close() cur.close()
conn.close() conn.close()
@resource_bp.route('/resources/add', methods=['GET', 'POST'])
@login_required
def add_resources():
"""Fügt neue Ressourcen zum Pool hinzu"""
if request.method == 'POST':
conn = get_connection()
cur = conn.cursor()
try:
resource_type = request.form.get('resource_type')
resources_text = request.form.get('resources_text', '')
is_test = request.form.get('is_test', 'false') == 'true'
if not resource_type or not resources_text.strip():
flash('Bitte Ressourcentyp und Ressourcen angeben!', 'error')
return redirect(url_for('resources.add_resources'))
# Parse resources (one per line)
resources = [r.strip() for r in resources_text.strip().split('\n') if r.strip()]
# Validate resources based on type
valid_resources = []
invalid_resources = []
for resource in resources:
if resource_type == 'domain':
# Basic domain validation
import re
if re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.[a-zA-Z]{2,}$', resource):
valid_resources.append(resource)
else:
invalid_resources.append(resource)
elif resource_type == 'ipv4':
# IPv4 validation
parts = resource.split('.')
if len(parts) == 4 and all(p.isdigit() and 0 <= int(p) <= 255 for p in parts):
valid_resources.append(resource)
else:
invalid_resources.append(resource)
elif resource_type == 'phone':
# Phone number validation (basic)
import re
if re.match(r'^\+?[0-9]{7,15}$', resource.replace(' ', '').replace('-', '')):
valid_resources.append(resource)
else:
invalid_resources.append(resource)
else:
invalid_resources.append(resource)
# Check for duplicates
existing_resources = []
if valid_resources:
placeholders = ','.join(['%s'] * len(valid_resources))
cur.execute(f"""
SELECT resource_value
FROM resource_pools
WHERE resource_type = %s
AND resource_value IN ({placeholders})
""", [resource_type] + valid_resources)
existing_resources = [row[0] for row in cur.fetchall()]
# Filter out existing resources
new_resources = [r for r in valid_resources if r not in existing_resources]
# Insert new resources
added_count = 0
for resource in new_resources:
cur.execute("""
INSERT INTO resource_pools
(resource_type, resource_value, status, is_test, created_by)
VALUES (%s, %s, 'available', %s, %s)
""", (resource_type, resource, is_test, session['username']))
added_count += 1
conn.commit()
# Log audit
if added_count > 0:
log_audit('BULK_CREATE', 'resource',
additional_info=f"Added {added_count} {resource_type} resources")
# Flash messages
if added_count > 0:
flash(f'{added_count} neue Ressourcen erfolgreich hinzugefügt!', 'success')
if existing_resources:
flash(f'⚠️ {len(existing_resources)} Ressourcen existierten bereits und wurden übersprungen.', 'warning')
if invalid_resources:
flash(f'{len(invalid_resources)} ungültige Ressourcen wurden ignoriert.', 'error')
return redirect(url_for('resources.resources', show_test=request.form.get('show_test', 'false')))
except Exception as e:
conn.rollback()
logging.error(f"Fehler beim Hinzufügen von Ressourcen: {str(e)}")
flash('Fehler beim Hinzufügen der Ressourcen!', 'error')
finally:
cur.close()
conn.close()
# GET request - show form
show_test = request.args.get('show_test', 'false') == 'true'
return render_template('add_resources.html', show_test=show_test)

Datei anzeigen

@@ -11,7 +11,7 @@
<h1 class="display-1">404</h1> <h1 class="display-1">404</h1>
<h2>Seite nicht gefunden</h2> <h2>Seite nicht gefunden</h2>
<p>Die angeforderte Seite konnte nicht gefunden werden.</p> <p>Die angeforderte Seite konnte nicht gefunden werden.</p>
<a href="/" class="btn btn-primary">Zur Startseite</a> <a href="{{ url_for('admin.dashboard') }}" class="btn btn-primary">Zur Startseite</a>
</div> </div>
</div> </div>
</div> </div>

Datei anzeigen

@@ -11,7 +11,7 @@
<h1 class="display-1">500</h1> <h1 class="display-1">500</h1>
<h2>Interner Serverfehler</h2> <h2>Interner Serverfehler</h2>
<p>Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.</p> <p>Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.</p>
<a href="/" class="btn btn-primary">Zur Startseite</a> <a href="{{ url_for('admin.dashboard') }}" class="btn btn-primary">Zur Startseite</a>
</div> </div>
</div> </div>
</div> </div>

Datei anzeigen

@@ -115,12 +115,12 @@
<h1 class="mb-0">Ressourcen hinzufügen</h1> <h1 class="mb-0">Ressourcen hinzufügen</h1>
<p class="text-muted mb-0">Fügen Sie neue Domains, IPs oder Telefonnummern zum Pool hinzu</p> <p class="text-muted mb-0">Fügen Sie neue Domains, IPs oder Telefonnummern zum Pool hinzu</p>
</div> </div>
<a href="{{ url_for('resources', show_test=show_test) }}" class="btn btn-secondary"> <a href="{{ url_for('resources.resources', show_test=show_test) }}" class="btn btn-secondary">
← Zurück zur Übersicht ← Zurück zur Übersicht
</a> </a>
</div> </div>
<form method="post" action="{{ url_for('add_resources', show_test=show_test) }}" id="addResourceForm"> <form method="post" action="{{ url_for('resources.add_resources', show_test=show_test) }}" id="addResourceForm">
<!-- Resource Type Selection --> <!-- Resource Type Selection -->
<div class="card main-card mb-4"> <div class="card main-card mb-4">
<div class="card-header bg-white"> <div class="card-header bg-white">
@@ -278,7 +278,7 @@ my-website.io</pre>
<!-- Submit Buttons --> <!-- Submit Buttons -->
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<button type="button" class="btn btn-secondary" onclick="window.location.href='{{ url_for('resources', show_test=show_test) }}'"> <button type="button" class="btn btn-secondary" onclick="window.location.href='{{ url_for('resources.resources', show_test=show_test) }}'">
<i class="fas fa-times"></i> Abbrechen <i class="fas fa-times"></i> Abbrechen
</button> </button>
<button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled> <button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled>

Datei anzeigen

@@ -5,10 +5,10 @@
{% macro sortable_header(label, field, current_sort, current_order) %} {% macro sortable_header(label, field, current_sort, current_order) %}
<th> <th>
{% if current_sort == field %} {% if current_sort == field %}
<a href="{{ url_for('audit_log', sort=field, order='desc' if current_order == 'asc' else 'asc', user=filter_user, action=filter_action, entity=filter_entity, page=1) }}" <a href="{{ url_for('admin.audit_log', sort=field, order='desc' if current_order == 'asc' else 'asc', user=filter_user, action=filter_action, entity=filter_entity, page=1) }}"
class="server-sortable"> class="server-sortable">
{% else %} {% else %}
<a href="{{ url_for('audit_log', sort=field, order='asc', user=filter_user, action=filter_action, entity=filter_entity, page=1) }}" <a href="{{ url_for('admin.audit_log', sort=field, order='asc', user=filter_user, action=filter_action, entity=filter_entity, page=1) }}"
class="server-sortable"> class="server-sortable">
{% endif %} {% endif %}
{{ label }} {{ label }}
@@ -70,7 +70,7 @@
<!-- Filter --> <!-- Filter -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<form method="get" action="/audit" id="auditFilterForm"> <form method="get" action="{{ url_for('admin.audit_log') }}" id="auditFilterForm">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div class="col-md-3"> <div class="col-md-3">
<label for="user" class="form-label">Benutzer</label> <label for="user" class="form-label">Benutzer</label>
@@ -114,15 +114,15 @@
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="/audit" class="btn btn-outline-secondary">Zurücksetzen</a> <a href="{{ url_for('admin.audit_log') }}" class="btn btn-outline-secondary">Zurücksetzen</a>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> Export <i class="bi bi-download"></i> Export
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="/export/audit?format=excel&user={{ filter_user }}&action={{ filter_action }}&entity={{ filter_entity }}"> <li><a class="dropdown-item" href="{{ url_for('export.export_audit', format='excel', user=filter_user, action=filter_action, entity=filter_entity) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
<li><a class="dropdown-item" href="/export/audit?format=csv&user={{ filter_user }}&action={{ filter_action }}&entity={{ filter_entity }}"> <li><a class="dropdown-item" href="{{ url_for('export.export_audit', format='csv', user=filter_user, action=filter_action, entity=filter_entity) }}">
<i class="bi bi-file-earmark-text"></i> CSV Export</a></li> <i class="bi bi-file-earmark-text"></i> CSV Export</a></li>
</ul> </ul>
</div> </div>
@@ -248,31 +248,31 @@
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center">
<!-- Erste Seite --> <!-- Erste Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('audit_log', page=1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">Erste</a> <a class="page-link" href="{{ url_for('admin.audit_log', page=1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">Erste</a>
</li> </li>
<!-- Vorherige Seite --> <!-- Vorherige Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('audit_log', page=page-1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('admin.audit_log', page=page-1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}"></a>
</li> </li>
<!-- Seitenzahlen --> <!-- Seitenzahlen -->
{% for p in range(1, total_pages + 1) %} {% for p in range(1, total_pages + 1) %}
{% if p >= page - 2 and p <= page + 2 %} {% if p >= page - 2 and p <= page + 2 %}
<li class="page-item {% if p == page %}active{% endif %}"> <li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('audit_log', page=p, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">{{ p }}</a> <a class="page-link" href="{{ url_for('admin.audit_log', page=p, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">{{ p }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<!-- Nächste Seite --> <!-- Nächste Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('audit_log', page=page+1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('admin.audit_log', page=page+1, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}"></a>
</li> </li>
<!-- Letzte Seite --> <!-- Letzte Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('audit_log', page=total_pages, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">Letzte</a> <a class="page-link" href="{{ url_for('admin.audit_log', page=total_pages, user=filter_user, action=filter_action, entity=filter_entity, sort=sort, order=order) }}">Letzte</a>
</li> </li>
</ul> </ul>
<p class="text-center text-muted"> <p class="text-center text-muted">

Datei anzeigen

@@ -132,7 +132,7 @@
Ich habe die Backup-Codes sicher gespeichert Ich habe die Backup-Codes sicher gespeichert
</label> </label>
</div> </div>
<a href="{{ url_for('profile') }}" class="btn btn-lg btn-success" id="continueBtn" style="display: none;"> <a href="{{ url_for('auth.profile') }}" class="btn btn-lg btn-success" id="continueBtn" style="display: none;">
✅ Weiter zum Profil ✅ Weiter zum Profil
</a> </a>
</div> </div>

Datei anzeigen

@@ -120,7 +120,7 @@
<td class="backup-actions"> <td class="backup-actions">
{% if backup.status == 'success' %} {% if backup.status == 'success' %}
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
<a href="/backup/download/{{ backup.id }}" <a href="{{ url_for('admin.download_backup', backup_id=backup.id) }}"
class="btn btn-outline-primary" class="btn btn-outline-primary"
title="Backup herunterladen"> title="Backup herunterladen">
📥 Download 📥 Download
@@ -197,7 +197,7 @@ function createBackup() {
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '⏳ Backup wird erstellt...'; btn.innerHTML = '⏳ Backup wird erstellt...';
fetch('/backup/create', { fetch('{{ url_for('admin.create_backup') }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -252,7 +252,7 @@ function confirmRestore() {
loadingDiv.innerHTML = '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>'; loadingDiv.innerHTML = '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';
document.body.appendChild(loadingDiv); document.body.appendChild(loadingDiv);
fetch(`/backup/restore/${backupId}`, { fetch(`{{ url_for('admin.restore_backup', backup_id='') }}${backupId}`, {
method: 'POST', method: 'POST',
body: formData body: formData
}) })
@@ -260,7 +260,7 @@ function confirmRestore() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
alert('✅ ' + data.message + '\n\nDie Seite wird neu geladen...'); alert('✅ ' + data.message + '\n\nDie Seite wird neu geladen...');
window.location.href = '/'; window.location.href = '{{ url_for('admin.dashboard') }}';
} else { } else {
alert('❌ ' + data.message); alert('❌ ' + data.message);
} }
@@ -278,7 +278,7 @@ function deleteBackup(backupId, filename) {
return; return;
} }
fetch(`/backup/delete/${backupId}`, { fetch(`{{ url_for('admin.delete_backup', backup_id='') }}${backupId}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

Datei anzeigen

@@ -340,7 +340,7 @@
<body class="bg-light"> <body class="bg-light">
<nav class="navbar navbar-dark bg-dark navbar-expand-lg"> <nav class="navbar navbar-dark bg-dark navbar-expand-lg">
<div class="container-fluid"> <div class="container-fluid">
<a href="/" class="navbar-brand text-decoration-none">🎛️ AccountForger - Admin Panel</a> <a href="{{ url_for('admin.dashboard') }}" class="navbar-brand text-decoration-none">🎛️ AccountForger - Admin Panel</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -353,8 +353,8 @@
⏱️ <span id="timer-display">5:00</span> ⏱️ <span id="timer-display">5:00</span>
</div> </div>
<span class="text-white me-3">Angemeldet als: {{ username }}</span> <span class="text-white me-3">Angemeldet als: {{ username }}</span>
<a href="/profile" class="btn btn-outline-light btn-sm me-2">👤 Profil</a> <a href="{{ url_for('auth.profile') }}" class="btn btn-outline-light btn-sm me-2">👤 Profil</a>
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a> <a href="{{ url_for('auth.logout') }}" class="btn btn-outline-light btn-sm">Abmelden</a>
</div> </div>
</div> </div>
</div> </div>
@@ -363,40 +363,40 @@
<!-- Sidebar Navigation --> <!-- Sidebar Navigation -->
<aside class="sidebar" id="sidebar"> <aside class="sidebar" id="sidebar">
<ul class="sidebar-nav"> <ul class="sidebar-nav">
<li class="nav-item {% if request.endpoint in ['customers_licenses', 'edit_customer', 'create_customer', 'edit_license', 'create_license', 'batch_licenses'] %}has-active-child{% endif %}"> <li class="nav-item {% if request.endpoint in ['customers.customers_licenses', 'customers.edit_customer', 'customers.create_customer', 'licenses.edit_license', 'licenses.create_license', 'batch.batch_create'] %}has-active-child{% endif %}">
<a class="nav-link has-submenu {% if request.endpoint == 'customers_licenses' %}active{% endif %}" href="/customers-licenses"> <a class="nav-link has-submenu {% if request.endpoint == 'customers.customers_licenses' %}active{% endif %}" href="{{ url_for('customers.customers_licenses') }}">
<i class="bi bi-people"></i> <i class="bi bi-people"></i>
<span>Kunden & Lizenzen</span> <span>Kunden & Lizenzen</span>
</a> </a>
<ul class="sidebar-submenu"> <ul class="sidebar-submenu">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'create_customer' %}active{% endif %}" href="/customer/create"> <a class="nav-link {% if request.endpoint == 'customers.create_customer' %}active{% endif %}" href="{{ url_for('customers.create_customer') }}">
<i class="bi bi-person-plus"></i> <i class="bi bi-person-plus"></i>
<span>Neuer Kunde</span> <span>Neuer Kunde</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'create_license' %}active{% endif %}" href="/create"> <a class="nav-link {% if request.endpoint == 'licenses.create_license' %}active{% endif %}" href="{{ url_for('licenses.create_license') }}">
<i class="bi bi-plus-circle"></i> <i class="bi bi-plus-circle"></i>
<span>Neue Lizenz</span> <span>Neue Lizenz</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'batch_licenses' %}active{% endif %}" href="/batch"> <a class="nav-link {% if request.endpoint == 'batch.batch_create' %}active{% endif %}" href="{{ url_for('batch.batch_create') }}">
<i class="bi bi-stack"></i> <i class="bi bi-stack"></i>
<span>Batch-Erstellung</span> <span>Batch-Erstellung</span>
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
<li class="nav-item {% if request.endpoint in ['resources', 'add_resources'] %}has-active-child{% endif %}"> <li class="nav-item {% if request.endpoint in ['resources.resources', 'resources.add_resources'] %}has-active-child{% endif %}">
<a class="nav-link has-submenu {% if request.endpoint == 'resources' %}active{% endif %}" href="/resources"> <a class="nav-link has-submenu {% if request.endpoint == 'resources.resources' %}active{% endif %}" href="{{ url_for('resources.resources') }}">
<i class="bi bi-box-seam"></i> <i class="bi bi-box-seam"></i>
<span>Resource Pool</span> <span>Resource Pool</span>
</a> </a>
<ul class="sidebar-submenu"> <ul class="sidebar-submenu">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'add_resources' %}active{% endif %}" href="/resources/add"> <a class="nav-link {% if request.endpoint == 'resources.add_resources' %}active{% endif %}" href="{{ url_for('resources.add_resources') }}">
<i class="bi bi-plus-square"></i> <i class="bi bi-plus-square"></i>
<span>Ressourcen hinzufügen</span> <span>Ressourcen hinzufügen</span>
</a> </a>
@@ -404,25 +404,25 @@
</ul> </ul>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'audit_log' %}active{% endif %}" href="/audit"> <a class="nav-link {% if request.endpoint == 'admin.audit_log' %}active{% endif %}" href="{{ url_for('admin.audit_log') }}">
<i class="bi bi-journal-text"></i> <i class="bi bi-journal-text"></i>
<span>Audit-Log</span> <span>Audit-Log</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'sessions' %}active{% endif %}" href="/sessions"> <a class="nav-link {% if request.endpoint == 'sessions.sessions' %}active{% endif %}" href="{{ url_for('sessions.sessions') }}">
<i class="bi bi-people"></i> <i class="bi bi-people"></i>
<span>Sitzungen</span> <span>Sitzungen</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'backups' %}active{% endif %}" href="/backups"> <a class="nav-link {% if request.endpoint == 'admin.backups' %}active{% endif %}" href="{{ url_for('admin.backups') }}">
<i class="bi bi-cloud-download"></i> <i class="bi bi-cloud-download"></i>
<span>Backups</span> <span>Backups</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'blocked_ips' %}active{% endif %}" href="/security/blocked-ips"> <a class="nav-link {% if request.endpoint == 'admin.blocked_ips' %}active{% endif %}" href="{{ url_for('admin.blocked_ips') }}">
<i class="bi bi-shield-lock"></i> <i class="bi bi-shield-lock"></i>
<span>Sicherheit</span> <span>Sicherheit</span>
</a> </a>
@@ -509,7 +509,7 @@
// Session verlängern // Session verlängern
function extendSession() { function extendSession() {
fetch('/heartbeat', { fetch('{{ url_for('auth.heartbeat') }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -531,7 +531,7 @@
if (timeRemaining <= 0) { if (timeRemaining <= 0) {
clearInterval(timerInterval); clearInterval(timerInterval);
window.location.href = '/logout'; window.location.href = '{{ url_for('auth.logout') }}';
} }
} }

Datei anzeigen

@@ -6,7 +6,7 @@
<div class="container py-5"> <div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>🔑 Batch-Lizenzen erstellen</h2> <h2>🔑 Batch-Lizenzen erstellen</h2>
<a href="/customers-licenses" class="btn btn-secondary">← Zurück zur Übersicht</a> <a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">← Zurück zur Übersicht</a>
</div> </div>
<div class="alert alert-info"> <div class="alert alert-info">
@@ -26,7 +26,7 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<form method="post" action="/batch" accept-charset="UTF-8"> <form method="post" action="{{ url_for('batch.batch_create') }}" accept-charset="UTF-8">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-12"> <div class="col-md-12">
<label for="customerSelect" class="form-label">Kunde auswählen</label> <label for="customerSelect" class="form-label">Kunde auswählen</label>

Datei anzeigen

@@ -7,7 +7,7 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>✅ Batch-Lizenzen erfolgreich generiert</h2> <h2>✅ Batch-Lizenzen erfolgreich generiert</h2>
<div> <div>
<a href="/batch" class="btn btn-primary">🔑 Weitere Batch erstellen</a> <a href="{{ url_for('batch.batch_create') }}" class="btn btn-primary">🔑 Weitere Batch erstellen</a>
</div> </div>
</div> </div>
@@ -43,7 +43,7 @@
<div class="card-body"> <div class="card-body">
<p>Exportieren Sie die generierten Lizenzen für den Kunden:</p> <p>Exportieren Sie die generierten Lizenzen für den Kunden:</p>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="/batch/export" class="btn btn-success"> <a href="{{ url_for('batch.export_batch') }}" class="btn btn-success">
📄 Als CSV exportieren 📄 Als CSV exportieren
</a> </a>
<button class="btn btn-outline-primary" onclick="copyAllKeys()"> <button class="btn btn-outline-primary" onclick="copyAllKeys()">
@@ -102,7 +102,7 @@
<!-- Hinweis --> <!-- Hinweis -->
<div class="alert alert-info mt-4"> <div class="alert alert-info mt-4">
<strong>💡 Tipp:</strong> Die generierten Lizenzen sind sofort aktiv und können verwendet werden. <strong>💡 Tipp:</strong> Die generierten Lizenzen sind sofort aktiv und können verwendet werden.
Sie finden alle Lizenzen auch in der <a href="/licenses?search={{ customer }}">Lizenzübersicht</a>. Sie finden alle Lizenzen auch in der <a href="{{ url_for('licenses.licenses', search=customer) }}">Lizenzübersicht</a>.
</div> </div>
</div> </div>

Datei anzeigen

@@ -51,7 +51,7 @@
<td> <td>
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
{% if ip.is_active %} {% if ip.is_active %}
<form method="post" action="/security/unblock-ip" class="d-inline"> <form method="post" action="{{ url_for('admin.unblock_ip') }}" class="d-inline">
<input type="hidden" name="ip_address" value="{{ ip.ip_address }}"> <input type="hidden" name="ip_address" value="{{ ip.ip_address }}">
<button type="submit" class="btn btn-success" <button type="submit" class="btn btn-success"
onclick="return confirm('IP {{ ip.ip_address }} wirklich entsperren?')"> onclick="return confirm('IP {{ ip.ip_address }} wirklich entsperren?')">
@@ -59,7 +59,7 @@
</button> </button>
</form> </form>
{% endif %} {% endif %}
<form method="post" action="/security/clear-attempts" class="d-inline ms-1"> <form method="post" action="{{ url_for('admin.clear_attempts') }}" class="d-inline ms-1">
<input type="hidden" name="ip_address" value="{{ ip.ip_address }}"> <input type="hidden" name="ip_address" value="{{ ip.ip_address }}">
<button type="submit" class="btn btn-warning" <button type="submit" class="btn btn-warning"
onclick="return confirm('Alle Versuche für IP {{ ip.ip_address }} zurücksetzen?')"> onclick="return confirm('Alle Versuche für IP {{ ip.ip_address }} zurücksetzen?')">

Datei anzeigen

@@ -6,12 +6,12 @@
<div class="container py-5"> <div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>👤 Neuer Kunde anlegen</h2> <h2>👤 Neuer Kunde anlegen</h2>
<a href="/customers-licenses" class="btn btn-secondary">← Zurück zur Übersicht</a> <a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">← Zurück zur Übersicht</a>
</div> </div>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form method="post" action="/customer/create" accept-charset="UTF-8"> <form method="post" action="{{ url_for('customers.create_customer') }}" accept-charset="UTF-8">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="name" class="form-label">Kundenname <span class="text-danger">*</span></label> <label for="name" class="form-label">Kundenname <span class="text-danger">*</span></label>
@@ -44,7 +44,7 @@
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary">💾 Kunde anlegen</button> <button type="submit" class="btn btn-primary">💾 Kunde anlegen</button>
<a href="/customers-licenses" class="btn btn-secondary">Abbrechen</a> <a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">Abbrechen</a>
</div> </div>
</form> </form>
</div> </div>

Datei anzeigen

@@ -5,10 +5,10 @@
{% macro sortable_header(label, field, current_sort, current_order) %} {% macro sortable_header(label, field, current_sort, current_order) %}
<th> <th>
{% if current_sort == field %} {% if current_sort == field %}
<a href="{{ url_for('customers', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, page=1) }}" <a href="{{ url_for('customers.customers', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, show_test=show_test, page=1) }}"
class="server-sortable"> class="server-sortable">
{% else %} {% else %}
<a href="{{ url_for('customers', sort=field, order='asc', search=search, page=1) }}" <a href="{{ url_for('customers.customers', sort=field, order='asc', search=search, show_test=show_test, page=1) }}"
class="server-sortable"> class="server-sortable">
{% endif %} {% endif %}
{{ label }} {{ label }}
@@ -32,7 +32,7 @@
<!-- Suchformular --> <!-- Suchformular -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<form method="get" action="/customers" id="customerSearchForm" class="row g-3 align-items-end"> <form method="get" action="{{ url_for('customers.customers') }}" id="customerSearchForm" class="row g-3 align-items-end">
<div class="col-md-8"> <div class="col-md-8">
<label for="search" class="form-label">🔍 Suchen</label> <label for="search" class="form-label">🔍 Suchen</label>
<input type="text" class="form-control" id="search" name="search" <input type="text" class="form-control" id="search" name="search"
@@ -49,13 +49,13 @@
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<a href="/customers" class="btn btn-outline-secondary w-100">Zurücksetzen</a> <a href="{{ url_for('customers.customers') }}" class="btn btn-outline-secondary w-100">Zurücksetzen</a>
</div> </div>
</form> </form>
{% if search %} {% if search %}
<div class="mt-2"> <div class="mt-2">
<small class="text-muted">Suchergebnisse für: <strong>{{ search }}</strong></small> <small class="text-muted">Suchergebnisse für: <strong>{{ search }}</strong></small>
<a href="/customers" class="btn btn-sm btn-outline-secondary ms-2">✖ Suche zurücksetzen</a> <a href="{{ url_for('customers.customers') }}" class="btn btn-sm btn-outline-secondary ms-2">✖ Suche zurücksetzen</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@@ -92,9 +92,9 @@
</td> </td>
<td> <td>
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
<a href="/customer/edit/{{ customer.id }}" class="btn btn-outline-primary">✏️ Bearbeiten</a> <a href="{{ url_for('customers.edit_customer', customer_id=customer.id) }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
{% if customer.license_count == 0 %} {% if customer.license_count == 0 %}
<form method="post" action="/customer/delete/{{ customer.id }}" style="display: inline;" onsubmit="return confirm('Kunde wirklich löschen?');"> <form method="post" action="{{ url_for('customers.delete_customer', customer_id=customer.id) }}" style="display: inline;" onsubmit="return confirm('Kunde wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button> <button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button>
</form> </form>
{% else %} {% else %}
@@ -111,10 +111,10 @@
<div class="text-center py-5"> <div class="text-center py-5">
{% if search %} {% if search %}
<p class="text-muted">Keine Kunden gefunden für: <strong>{{ search }}</strong></p> <p class="text-muted">Keine Kunden gefunden für: <strong>{{ search }}</strong></p>
<a href="/customers" class="btn btn-secondary">Alle Kunden anzeigen</a> <a href="{{ url_for('customers.customers') }}" class="btn btn-secondary">Alle Kunden anzeigen</a>
{% else %} {% else %}
<p class="text-muted">Noch keine Kunden vorhanden.</p> <p class="text-muted">Noch keine Kunden vorhanden.</p>
<a href="/create" class="btn btn-primary">Erste Lizenz erstellen</a> <a href="{{ url_for('licenses.create_license') }}" class="btn btn-primary">Erste Lizenz erstellen</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -126,31 +126,31 @@
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center">
<!-- Erste Seite --> <!-- Erste Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers', page=1, search=search, sort=sort, order=order) }}">Erste</a> <a class="page-link" href="{{ url_for('customers.customers', page=1, search=search, sort=sort, order=order, show_test=show_test) }}">Erste</a>
</li> </li>
<!-- Vorherige Seite --> <!-- Vorherige Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers', page=page-1, search=search, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('customers.customers', page=page-1, search=search, sort=sort, order=order, show_test=show_test) }}"></a>
</li> </li>
<!-- Seitenzahlen --> <!-- Seitenzahlen -->
{% for p in range(1, total_pages + 1) %} {% for p in range(1, total_pages + 1) %}
{% if p >= page - 2 and p <= page + 2 %} {% if p >= page - 2 and p <= page + 2 %}
<li class="page-item {% if p == page %}active{% endif %}"> <li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('customers', page=p, search=search, sort=sort, order=order) }}">{{ p }}</a> <a class="page-link" href="{{ url_for('customers.customers', page=p, search=search, sort=sort, order=order, show_test=show_test) }}">{{ p }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<!-- Nächste Seite --> <!-- Nächste Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers', page=page+1, search=search, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('customers.customers', page=page+1, search=search, sort=sort, order=order, show_test=show_test) }}"></a>
</li> </li>
<!-- Letzte Seite --> <!-- Letzte Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers', page=total_pages, search=search, sort=sort, order=order) }}">Letzte</a> <a class="page-link" href="{{ url_for('customers.customers', page=total_pages, search=search, sort=sort, order=order, show_test=show_test) }}">Letzte</a>
</li> </li>
</ul> </ul>
<p class="text-center text-muted"> <p class="text-center text-muted">

Datei anzeigen

@@ -13,14 +13,14 @@
<i class="bi bi-download"></i> Export <i class="bi bi-download"></i> Export
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="/export/customers?format=excel{% if request.args.get('show_test') %}&include_test=true{% endif %}"> <li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='excel', include_test=request.args.get('show_test')) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Kunden (Excel)</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Kunden (Excel)</a></li>
<li><a class="dropdown-item" href="/export/customers?format=csv{% if request.args.get('show_test') %}&include_test=true{% endif %}"> <li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='csv', include_test=request.args.get('show_test')) }}">
<i class="bi bi-file-earmark-text"></i> Kunden (CSV)</a></li> <i class="bi bi-file-earmark-text"></i> Kunden (CSV)</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/export/licenses?format=excel{% if request.args.get('show_test') %}&include_test=true{% endif %}{% if selected_customer_id %}&customer_id={{ selected_customer_id }}{% endif %}"> <li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='excel', include_test=request.args.get('show_test'), customer_id=selected_customer_id) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Lizenzen (Excel)</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Lizenzen (Excel)</a></li>
<li><a class="dropdown-item" href="/export/licenses?format=csv{% if request.args.get('show_test') %}&include_test=true{% endif %}{% if selected_customer_id %}&customer_id={{ selected_customer_id }}{% endif %}"> <li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='csv', include_test=request.args.get('show_test'), customer_id=selected_customer_id) }}">
<i class="bi bi-file-earmark-text"></i> Lizenzen (CSV)</a></li> <i class="bi bi-file-earmark-text"></i> Lizenzen (CSV)</a></li>
</ul> </ul>
</div> </div>
@@ -83,7 +83,7 @@
<i class="bi bi-inbox" style="font-size: 3rem; opacity: 0.3;"></i> <i class="bi bi-inbox" style="font-size: 3rem; opacity: 0.3;"></i>
<p class="mt-3 mb-2">Keine Kunden vorhanden</p> <p class="mt-3 mb-2">Keine Kunden vorhanden</p>
<small class="d-block mb-3">Erstellen Sie eine neue Lizenz, um automatisch einen Kunden anzulegen.</small> <small class="d-block mb-3">Erstellen Sie eine neue Lizenz, um automatisch einen Kunden anzulegen.</small>
<a href="/create" class="btn btn-sm btn-primary"> <a href="{{ url_for('licenses.create_license') }}" class="btn btn-sm btn-primary">
<i class="bi bi-plus-circle"></i> Neue Lizenz erstellen <i class="bi bi-plus-circle"></i> Neue Lizenz erstellen
</a> </a>
</div> </div>
@@ -104,7 +104,7 @@
<small class="text-muted">{{ selected_customer[2] }}</small> <small class="text-muted">{{ selected_customer[2] }}</small>
</div> </div>
<div> <div>
<a href="/customer/edit/{{ selected_customer[0] }}?ref=customers-licenses{% if request.args.get('show_test') %}&show_test=true{% endif %}" class="btn btn-sm btn-outline-primary"> <a href="{{ url_for('customers.edit_customer', customer_id=selected_customer[0], ref='customers-licenses', show_test=request.args.get('show_test')) }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Bearbeiten <i class="bi bi-pencil"></i> Bearbeiten
</a> </a>
</div> </div>
@@ -179,7 +179,7 @@
<button class="btn btn-outline-info" onclick="showDeviceManagement({{ license[0] }})" title="Geräte verwalten"> <button class="btn btn-outline-info" onclick="showDeviceManagement({{ license[0] }})" title="Geräte verwalten">
<i class="bi bi-laptop"></i> <i class="bi bi-laptop"></i>
</button> </button>
<a href="/license/edit/{{ license[0] }}{% if request.args.get('show_test') %}?ref=customers-licenses&show_test=true{% endif %}" class="btn btn-outline-secondary" title="Bearbeiten"> <a href="{{ url_for('licenses.edit_license', license_id=license[0], ref='customers-licenses', show_test=request.args.get('show_test')) }}" class="btn btn-outline-secondary" title="Bearbeiten">
<i class="bi bi-pencil"></i> <i class="bi bi-pencil"></i>
</a> </a>
</div> </div>
@@ -544,7 +544,7 @@ function updateLicenseView(customerId, licenses) {
function toggleLicenseStatus(licenseId, currentStatus) { function toggleLicenseStatus(licenseId, currentStatus) {
const newStatus = !currentStatus; const newStatus = !currentStatus;
fetch(`/api/license/${licenseId}/toggle`, { fetch(`{{ url_for('api.toggle_license', license_id='') }}${licenseId}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -565,7 +565,7 @@ function toggleLicenseStatus(licenseId, currentStatus) {
// Direkt zur Lizenzerstellung navigieren // Direkt zur Lizenzerstellung navigieren
function showNewLicenseModal(customerId) { function showNewLicenseModal(customerId) {
window.location.href = `/create?customer_id=${customerId}${window.location.search ? '&' + window.location.search.substring(1) : ''}`; window.location.href = `{{ url_for('licenses.create_license') }}?customer_id=${customerId}${window.location.search ? '&' + window.location.search.substring(1) : ''}`;
} }
// Copy to clipboard // Copy to clipboard
@@ -686,7 +686,7 @@ function showResourceDetails(licenseId, resourceType) {
</td> </td>
<td>${resource.assigned_at}</td> <td>${resource.assigned_at}</td>
<td> <td>
<a href="/resources?search=${encodeURIComponent(resource.value)}" <a href="{{ url_for('resources.resources') }}?search=${encodeURIComponent(resource.value)}"
class="btn btn-sm btn-outline-primary" target="_blank"> class="btn btn-sm btn-outline-primary" target="_blank">
<i class="bi bi-box-arrow-up-right"></i> Details <i class="bi bi-box-arrow-up-right"></i> Details
</a> </a>
@@ -1052,7 +1052,7 @@ function saveResourceChanges() {
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Wird gespeichert...'; saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Wird gespeichert...';
// API-Call zum Zuweisen der Ressourcen // API-Call zum Zuweisen der Ressourcen
fetch('/api/resources/allocate', { fetch('{{ url_for('api.allocate_resources') }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

Datei anzeigen

@@ -72,7 +72,7 @@
<!-- Statistik-Karten --> <!-- Statistik-Karten -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-4"> <div class="col-md-4">
<a href="/customers-licenses" class="text-decoration-none"> <a href="{{ url_for('customers.customers_licenses') }}" class="text-decoration-none">
<div class="card stat-card h-100"> <div class="card stat-card h-100">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="card-icon text-primary">👥</div> <div class="card-icon text-primary">👥</div>
@@ -83,7 +83,7 @@
</a> </a>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<a href="/customers-licenses" class="text-decoration-none"> <a href="{{ url_for('customers.customers_licenses') }}" class="text-decoration-none">
<div class="card stat-card h-100"> <div class="card stat-card h-100">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="card-icon text-info">📋</div> <div class="card-icon text-info">📋</div>
@@ -94,7 +94,7 @@
</a> </a>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<a href="/sessions" class="text-decoration-none"> <a href="{{ url_for('sessions.sessions') }}" class="text-decoration-none">
<div class="card stat-card h-100"> <div class="card stat-card h-100">
<div class="card-body text-center"> <div class="card-body text-center">
<div class="card-icon text-success{% if stats.active_sessions > 0 %} pulse-effect{% endif %}">🟢</div> <div class="card-icon text-success{% if stats.active_sessions > 0 %} pulse-effect{% endif %}">🟢</div>
@@ -161,7 +161,7 @@
<!-- Backup-Status und Sicherheit nebeneinander --> <!-- Backup-Status und Sicherheit nebeneinander -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-6"> <div class="col-md-6">
<a href="/backups" class="text-decoration-none"> <a href="{{ url_for('admin.backups') }}" class="text-decoration-none">
<div class="card stat-card h-100"> <div class="card stat-card h-100">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">💾 Backup-Status</h5> <h5 class="card-title">💾 Backup-Status</h5>
@@ -210,7 +210,7 @@
<small class="text-muted">Fehlversuche heute</small> <small class="text-muted">Fehlversuche heute</small>
</div> </div>
</div> </div>
<a href="/security/blocked-ips" class="btn btn-sm btn-outline-danger mt-3">IP-Verwaltung →</a> <a href="{{ url_for('admin.blocked_ips') }}" class="btn btn-sm btn-outline-danger mt-3">IP-Verwaltung →</a>
</div> </div>
</div> </div>
</div> </div>
@@ -277,14 +277,14 @@
<div class="col-md-4 mb-3"> <div class="col-md-4 mb-3">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="me-3"> <div class="me-3">
<a href="/resources?type={{ type }}{{ '&show_test=true' if request.args.get('show_test') == 'true' else '' }}" <a href="{{ url_for('resources.resources', type=type, show_test=request.args.get('show_test')) }}"
class="text-decoration-none"> class="text-decoration-none">
<i class="fas fa-{{ 'globe' if type == 'domain' else ('network-wired' if type == 'ipv4' else 'phone') }} fa-2x text-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}"></i> <i class="fas fa-{{ 'globe' if type == 'domain' else ('network-wired' if type == 'ipv4' else 'phone') }} fa-2x text-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}"></i>
</a> </a>
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
<h6 class="mb-1"> <h6 class="mb-1">
<a href="/resources?type={{ type }}{{ '&show_test=true' if request.args.get('show_test') == 'true' else '' }}" <a href="{{ url_for('resources.resources', type=type, show_test=request.args.get('show_test')) }}"
class="text-decoration-none text-dark">{{ type|upper }}</a> class="text-decoration-none text-dark">{{ type|upper }}</a>
</h6> </h6>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
@@ -303,14 +303,14 @@
</div> </div>
<div class="d-flex justify-content-between mt-1"> <div class="d-flex justify-content-between mt-1">
<small class="text-muted"> <small class="text-muted">
<a href="/resources?type={{ type }}&status=allocated{{ '&show_test=true' if request.args.get('show_test') == 'true' else '' }}" <a href="{{ url_for('resources.resources', type=type, status='allocated', show_test=request.args.get('show_test')) }}"
class="text-decoration-none text-muted"> class="text-decoration-none text-muted">
{{ data.allocated }} zugeteilt {{ data.allocated }} zugeteilt
</a> </a>
</small> </small>
{% if data.quarantine > 0 %} {% if data.quarantine > 0 %}
<small> <small>
<a href="/resources?type={{ type }}&status=quarantine{{ '&show_test=true' if request.args.get('show_test') == 'true' else '' }}" <a href="{{ url_for('resources.resources', type=type, status='quarantine', show_test=request.args.get('show_test')) }}"
class="text-decoration-none text-warning"> class="text-decoration-none text-warning">
<i class="bi bi-exclamation-triangle"></i> {{ data.quarantine }} in Quarantäne <i class="bi bi-exclamation-triangle"></i> {{ data.quarantine }} in Quarantäne
</a> </a>
@@ -325,7 +325,7 @@
<div class="col-12 text-center text-muted"> <div class="col-12 text-center text-muted">
<i class="fas fa-inbox fa-3x mb-3"></i> <i class="fas fa-inbox fa-3x mb-3"></i>
<p>Keine Ressourcen im Pool vorhanden.</p> <p>Keine Ressourcen im Pool vorhanden.</p>
<a href="/resources/add{{ '?show_test=true' if request.args.get('show_test') == 'true' else '' }}" class="btn btn-primary"> <a href="{{ url_for('resources.add_resources', show_test=request.args.get('show_test')) }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Ressourcen hinzufügen <i class="fas fa-plus"></i> Ressourcen hinzufügen
</a> </a>
</div> </div>
@@ -337,7 +337,7 @@
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
<strong>Kritisch:</strong> {{ resource_warning }} <strong>Kritisch:</strong> {{ resource_warning }}
</div> </div>
<a href="/resources/add{{ '?show_test=true' if request.args.get('show_test') == 'true' else '' }}" <a href="{{ url_for('resources.add_resources', show_test=request.args.get('show_test')) }}"
class="btn btn-sm btn-danger"> class="btn btn-sm btn-danger">
<i class="bi bi-plus"></i> Ressourcen auffüllen <i class="bi bi-plus"></i> Ressourcen auffüllen
</a> </a>

Datei anzeigen

@@ -7,13 +7,13 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Kunde bearbeiten</h2> <h2>Kunde bearbeiten</h2>
<div> <div>
<a href="/customers-licenses{% if request.args.get('show_test') == 'true' %}?show_test=true{% endif %}" class="btn btn-secondary">👥 Zurück zur Übersicht</a> <a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">👥 Zurück zur Übersicht</a>
</div> </div>
</div> </div>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-body"> <div class="card-body">
<form method="post" action="/customer/edit/{{ customer.id }}" accept-charset="UTF-8"> <form method="post" action="{{ url_for('customers.edit_customer', customer_id=customer.id) }}" accept-charset="UTF-8">
{% if request.args.get('show_test') == 'true' %} {% if request.args.get('show_test') == 'true' %}
<input type="hidden" name="show_test" value="true"> <input type="hidden" name="show_test" value="true">
{% endif %} {% endif %}
@@ -42,7 +42,7 @@
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary">💾 Änderungen speichern</button> <button type="submit" class="btn btn-primary">💾 Änderungen speichern</button>
<a href="/customers-licenses{% if request.args.get('show_test') == 'true' %}?show_test=true{% endif %}" class="btn btn-secondary">Abbrechen</a> <a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">Abbrechen</a>
</div> </div>
</form> </form>
</div> </div>
@@ -87,7 +87,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<a href="/license/edit/{{ license[0] }}" class="btn btn-outline-primary btn-sm">Bearbeiten</a> <a href="{{ url_for('licenses.edit_license', license_id=license[0]) }}" class="btn btn-outline-primary btn-sm">Bearbeiten</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

Datei anzeigen

@@ -7,13 +7,13 @@
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Lizenz bearbeiten</h2> <h2>Lizenz bearbeiten</h2>
<div> <div>
<a href="/customers-licenses{% if request.args.get('show_test') == 'true' %}?show_test=true{% endif %}" class="btn btn-secondary">📋 Zurück zur Übersicht</a> <a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">📋 Zurück zur Übersicht</a>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form method="post" action="/license/edit/{{ license.id }}" accept-charset="UTF-8"> <form method="post" action="{{ url_for('licenses.edit_license', license_id=license.id) }}" accept-charset="UTF-8">
{% if request.args.get('show_test') == 'true' %} {% if request.args.get('show_test') == 'true' %}
<input type="hidden" name="show_test" value="true"> <input type="hidden" name="show_test" value="true">
{% endif %} {% endif %}
@@ -75,7 +75,7 @@
<div class="mt-4"> <div class="mt-4">
<button type="submit" class="btn btn-primary">💾 Änderungen speichern</button> <button type="submit" class="btn btn-primary">💾 Änderungen speichern</button>
<a href="/customers-licenses{% if request.args.get('show_test') == 'true' %}?show_test=true{% endif %}" class="btn btn-secondary">Abbrechen</a> <a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">Abbrechen</a>
</div> </div>
</form> </form>
</div> </div>

Datei anzeigen

@@ -6,10 +6,10 @@
<div class="container py-5"> <div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Neue Lizenz erstellen</h2> <h2>Neue Lizenz erstellen</h2>
<a href="/customers-licenses" class="btn btn-secondary">← Zurück zur Übersicht</a> <a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">← Zurück zur Übersicht</a>
</div> </div>
<form method="post" action="/create" accept-charset="UTF-8"> <form method="post" action="{{ url_for('licenses.create_license') }}" accept-charset="UTF-8">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-12"> <div class="col-md-12">
<label for="customerSelect" class="form-label">Kunde auswählen</label> <label for="customerSelect" class="form-label">Kunde auswählen</label>
@@ -197,7 +197,7 @@ function generateLicenseKey() {
button.innerHTML = '⏳ Generiere...'; button.innerHTML = '⏳ Generiere...';
// API-Call // API-Call
fetch('/api/generate-license-key', { fetch('{{ url_for('api.api_generate_key') }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -298,7 +298,7 @@ document.addEventListener('DOMContentLoaded', function() {
placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...', placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...',
allowClear: true, allowClear: true,
ajax: { ajax: {
url: '/api/customers', url: '{{ url_for('api.api_customers') }}',
dataType: 'json', dataType: 'json',
delay: 250, delay: 250,
data: function (params) { data: function (params) {
@@ -339,7 +339,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Vorausgewählten Kunden setzen (falls von kombinierter Ansicht kommend) // Vorausgewählten Kunden setzen (falls von kombinierter Ansicht kommend)
{% if preselected_customer_id %} {% if preselected_customer_id %}
// Lade Kundendetails und setze Auswahl // Lade Kundendetails und setze Auswahl
fetch('/api/customers?id={{ preselected_customer_id }}') fetch('{{ url_for('api.api_customers') }}?id={{ preselected_customer_id }}')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.results && data.results.length > 0) { if (data.results && data.results.length > 0) {
@@ -403,7 +403,7 @@ function checkResourceAvailability() {
const phoneCount = parseInt(document.getElementById('phoneCount').value) || 0; const phoneCount = parseInt(document.getElementById('phoneCount').value) || 0;
// API-Call zur Verfügbarkeitsprüfung // API-Call zur Verfügbarkeitsprüfung
fetch(`/api/resources/check-availability?domain=${domainCount}&ipv4=${ipv4Count}&phone=${phoneCount}`) fetch(`{{ url_for('api.check_resource_availability') }}?domain=${domainCount}&ipv4=${ipv4Count}&phone=${phoneCount}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
// Update der Verfügbarkeitsanzeigen // Update der Verfügbarkeitsanzeigen

Datei anzeigen

@@ -5,10 +5,10 @@
{% macro sortable_header(label, field, current_sort, current_order) %} {% macro sortable_header(label, field, current_sort, current_order) %}
<th> <th>
{% if current_sort == field %} {% if current_sort == field %}
<a href="{{ url_for('licenses', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, type=filter_type, status=filter_status, page=1) }}" <a href="{{ url_for('licenses.licenses', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, type=filter_type, status=filter_status, page=1) }}"
class="server-sortable"> class="server-sortable">
{% else %} {% else %}
<a href="{{ url_for('licenses', sort=field, order='asc', search=search, type=filter_type, status=filter_status, page=1) }}" <a href="{{ url_for('licenses.licenses', sort=field, order='asc', search=search, type=filter_type, status=filter_status, page=1) }}"
class="server-sortable"> class="server-sortable">
{% endif %} {% endif %}
{{ label }} {{ label }}
@@ -35,7 +35,7 @@
<!-- Such- und Filterformular --> <!-- Such- und Filterformular -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<form method="get" action="/licenses" id="filterForm"> <form method="get" action="{{ url_for('licenses.licenses') }}" id="filterForm">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div class="col-md-4"> <div class="col-md-4">
<label for="search" class="form-label">🔍 Suchen</label> <label for="search" class="form-label">🔍 Suchen</label>
@@ -64,7 +64,7 @@
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<a href="/licenses" class="btn btn-outline-secondary">Zurücksetzen</a> <a href="{{ url_for('licenses.licenses') }}" class="btn btn-outline-secondary">Zurücksetzen</a>
</div> </div>
</div> </div>
</form> </form>
@@ -154,8 +154,8 @@
</td> </td>
<td> <td>
<div class="btn-group btn-group-sm" role="group"> <div class="btn-group btn-group-sm" role="group">
<a href="/license/edit/{{ license.id }}" class="btn btn-outline-primary">✏️ Bearbeiten</a> <a href="{{ url_for('licenses.edit_license', license_id=license.id) }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
<form method="post" action="/license/delete/{{ license.id }}" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');"> <form method="post" action="{{ url_for('licenses.delete_license', license_id=license.id) }}" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button> <button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button>
</form> </form>
</div> </div>
@@ -169,10 +169,10 @@
<div class="text-center py-5"> <div class="text-center py-5">
{% if search %} {% if search %}
<p class="text-muted">Keine Lizenzen gefunden für: <strong>{{ search }}</strong></p> <p class="text-muted">Keine Lizenzen gefunden für: <strong>{{ search }}</strong></p>
<a href="/licenses" class="btn btn-secondary">Alle Lizenzen anzeigen</a> <a href="{{ url_for('licenses.licenses') }}" class="btn btn-secondary">Alle Lizenzen anzeigen</a>
{% else %} {% else %}
<p class="text-muted">Noch keine Lizenzen vorhanden.</p> <p class="text-muted">Noch keine Lizenzen vorhanden.</p>
<a href="/create" class="btn btn-primary">Erste Lizenz erstellen</a> <a href="{{ url_for('licenses.create_license') }}" class="btn btn-primary">Erste Lizenz erstellen</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@@ -184,31 +184,31 @@
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center">
<!-- Erste Seite --> <!-- Erste Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses', page=1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Erste</a> <a class="page-link" href="{{ url_for('licenses.licenses', page=1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Erste</a>
</li> </li>
<!-- Vorherige Seite --> <!-- Vorherige Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}"> <li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses', page=page-1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('licenses.licenses', page=page-1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a>
</li> </li>
<!-- Seitenzahlen --> <!-- Seitenzahlen -->
{% for p in range(1, total_pages + 1) %} {% for p in range(1, total_pages + 1) %}
{% if p >= page - 2 and p <= page + 2 %} {% if p >= page - 2 and p <= page + 2 %}
<li class="page-item {% if p == page %}active{% endif %}"> <li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('licenses', page=p, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">{{ p }}</a> <a class="page-link" href="{{ url_for('licenses.licenses', page=p, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">{{ p }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<!-- Nächste Seite --> <!-- Nächste Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses', page=page+1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a> <a class="page-link" href="{{ url_for('licenses.licenses', page=page+1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a>
</li> </li>
<!-- Letzte Seite --> <!-- Letzte Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}"> <li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses', page=total_pages, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Letzte</a> <a class="page-link" href="{{ url_for('licenses.licenses', page=total_pages, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Letzte</a>
</li> </li>
</ul> </ul>
<p class="text-center text-muted"> <p class="text-center text-muted">
@@ -277,7 +277,7 @@ function copyToClipboard(text, button) {
// Toggle License Status // Toggle License Status
function toggleLicenseStatus(licenseId, isActive) { function toggleLicenseStatus(licenseId, isActive) {
fetch(`/api/license/${licenseId}/toggle`, { fetch(`{{ url_for('api.toggle_license', license_id='') }}${licenseId}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -336,21 +336,21 @@ function getSelectedIds() {
function bulkActivate() { function bulkActivate() {
const ids = getSelectedIds(); const ids = getSelectedIds();
if (confirm(`${ids.length} Lizenzen aktivieren?`)) { if (confirm(`${ids.length} Lizenzen aktivieren?`)) {
performBulkAction('/api/licenses/bulk-activate', ids); performBulkAction('{{ url_for('api.bulk_activate_licenses') }}', ids);
} }
} }
function bulkDeactivate() { function bulkDeactivate() {
const ids = getSelectedIds(); const ids = getSelectedIds();
if (confirm(`${ids.length} Lizenzen deaktivieren?`)) { if (confirm(`${ids.length} Lizenzen deaktivieren?`)) {
performBulkAction('/api/licenses/bulk-deactivate', ids); performBulkAction('{{ url_for('api.bulk_deactivate_licenses') }}', ids);
} }
} }
function bulkDelete() { function bulkDelete() {
const ids = getSelectedIds(); const ids = getSelectedIds();
if (confirm(`${ids.length} Lizenzen wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden!`)) { if (confirm(`${ids.length} Lizenzen wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden!`)) {
performBulkAction('/api/licenses/bulk-delete', ids); performBulkAction('{{ url_for('api.bulk_delete_licenses') }}', ids);
} }
} }

Datei anzeigen

@@ -94,7 +94,7 @@
Passwort ändern Passwort ändern
</h5> </h5>
<hr> <hr>
<form method="POST" action="{{ url_for('change_password') }}"> <form method="POST" action="{{ url_for('auth.change_password') }}">
<div class="mb-3"> <div class="mb-3">
<label for="current_password" class="form-label">Aktuelles Passwort</label> <label for="current_password" class="form-label">Aktuelles Passwort</label>
<input type="password" class="form-control" id="current_password" name="current_password" required> <input type="password" class="form-control" id="current_password" name="current_password" required>
@@ -131,7 +131,7 @@
</div> </div>
<div class="text-success" style="font-size: 3rem;"></div> <div class="text-success" style="font-size: 3rem;"></div>
</div> </div>
<form method="POST" action="{{ url_for('disable_2fa') }}" onsubmit="return confirm('Sind Sie sicher, dass Sie 2FA deaktivieren möchten? Dies verringert die Sicherheit Ihres Accounts.');"> <form method="POST" action="{{ url_for('auth.disable_2fa') }}" onsubmit="return confirm('Sind Sie sicher, dass Sie 2FA deaktivieren möchten? Dies verringert die Sicherheit Ihres Accounts.');">
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Passwort zur Bestätigung</label> <label for="password" class="form-label">Passwort zur Bestätigung</label>
<input type="password" class="form-control" id="password" name="password" required placeholder="Ihr aktuelles Passwort"> <input type="password" class="form-control" id="password" name="password" required placeholder="Ihr aktuelles Passwort">
@@ -150,7 +150,7 @@
Mit 2FA wird bei jeder Anmeldung zusätzlich ein Code aus Ihrer Authenticator-App benötigt. Mit 2FA wird bei jeder Anmeldung zusätzlich ein Code aus Ihrer Authenticator-App benötigt.
Dies schützt Ihren Account auch bei kompromittiertem Passwort. Dies schützt Ihren Account auch bei kompromittiertem Passwort.
</p> </p>
<a href="{{ url_for('setup_2fa') }}" class="btn btn-success">✨ 2FA einrichten</a> <a href="{{ url_for('auth.setup_2fa') }}" class="btn btn-success">✨ 2FA einrichten</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

Datei anzeigen

@@ -158,7 +158,7 @@
<h1 class="mb-0">Resource Historie</h1> <h1 class="mb-0">Resource Historie</h1>
<p class="text-muted mb-0">Detaillierte Aktivitätshistorie</p> <p class="text-muted mb-0">Detaillierte Aktivitätshistorie</p>
</div> </div>
<a href="{{ url_for('resources') }}" class="btn btn-secondary"> <a href="{{ url_for('resources.resources') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Zurück zur Übersicht <i class="fas fa-arrow-left"></i> Zurück zur Übersicht
</a> </a>
</div> </div>
@@ -223,7 +223,7 @@
<div class="info-item"> <div class="info-item">
<div class="info-label">Zugewiesen an Lizenz</div> <div class="info-label">Zugewiesen an Lizenz</div>
<div class="info-value"> <div class="info-value">
<a href="{{ url_for('edit_license', license_id=resource.allocated_to_license) }}" <a href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}"
class="text-decoration-none"> class="text-decoration-none">
{{ license_info.license_key if license_info else 'ID: ' + resource.allocated_to_license|string }} {{ license_info.license_key if license_info else 'ID: ' + resource.allocated_to_license|string }}
</a> </a>
@@ -327,7 +327,7 @@
{% if event.license_id %} {% if event.license_id %}
&nbsp;&bull;&nbsp; &nbsp;&bull;&nbsp;
<i class="fas fa-key"></i> <i class="fas fa-key"></i>
<a href="{{ url_for('edit_license', license_id=event.license_id) }}"> <a href="{{ url_for('licenses.edit_license', license_id=event.license_id) }}">
Lizenz #{{ event.license_id }} Lizenz #{{ event.license_id }}
</a> </a>
{% endif %} {% endif %}

Datei anzeigen

@@ -131,10 +131,10 @@
<p class="text-muted mb-0">Resource Pool Metriken und Analysen</p> <p class="text-muted mb-0">Resource Pool Metriken und Analysen</p>
</div> </div>
<div> <div>
<a href="{{ url_for('resources_report') }}" class="btn btn-info"> <a href="{{ url_for('resources.resource_report') }}" class="btn btn-info">
📄 Report generieren 📄 Report generieren
</a> </a>
<a href="{{ url_for('resources') }}" class="btn btn-secondary"> <a href="{{ url_for('resources.resources') }}" class="btn btn-secondary">
← Zurück ← Zurück
</a> </a>
</div> </div>
@@ -262,7 +262,7 @@
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<code class="me-2">{{ resource.resource_value }}</code> <code class="me-2">{{ resource.resource_value }}</code>
<a href="{{ url_for('resource_history', resource_id=resource.id) }}" <a href="{{ url_for('resources.resource_history', resource_id=resource.id) }}"
class="resource-link" title="Historie anzeigen"> class="resource-link" title="Historie anzeigen">
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</a> </a>
@@ -325,7 +325,7 @@
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<code class="me-2">{{ resource.resource_value }}</code> <code class="me-2">{{ resource.resource_value }}</code>
<a href="{{ url_for('resource_history', resource_id=resource.id) }}" <a href="{{ url_for('resources.resource_history', resource_id=resource.id) }}"
class="resource-link" title="Historie anzeigen"> class="resource-link" title="Historie anzeigen">
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</a> </a>

Datei anzeigen

@@ -6,7 +6,7 @@
<div class="container mt-4"> <div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1>Resource Report Generator</h1> <h1>Resource Report Generator</h1>
<a href="{{ url_for('resources') }}" class="btn btn-secondary"> <a href="{{ url_for('resources.resources') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Zurück <i class="fas fa-arrow-left"></i> Zurück
</a> </a>
</div> </div>
@@ -18,7 +18,7 @@
<h5 class="mb-0">Report-Einstellungen</h5> <h5 class="mb-0">Report-Einstellungen</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="get" action="{{ url_for('resources_report') }}"> <form method="get" action="{{ url_for('resources.resource_report') }}">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="report_type" class="form-label">Report-Typ</label> <label for="report_type" class="form-label">Report-Typ</label>

Datei anzeigen

@@ -267,10 +267,10 @@
{% if data.allocated > 0 %}{{ data.allocated }}{% endif %} {% if data.allocated > 0 %}{{ data.allocated }}{% endif %}
</div> </div>
<div class="progress-bar bg-warning progress-bar-custom" <div class="progress-bar bg-warning progress-bar-custom"
style="width: {{ (data.quarantine / data.total * 100) if data.total > 0 else 0 }}%" style="width: {{ (data.quarantined / data.total * 100) if data.total > 0 else 0 }}%"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
title="{{ data.quarantine }} in Quarantäne"> title="{{ data.quarantined }} in Quarantäne">
{% if data.quarantine > 0 %}{{ data.quarantine }}{% endif %} {% if data.quarantined > 0 %}{{ data.quarantined }}{% endif %}
</div> </div>
</div> </div>
@@ -292,7 +292,7 @@
<!-- Filter --> <!-- Filter -->
<div class="card filter-card mb-4"> <div class="card filter-card mb-4">
<div class="card-body"> <div class="card-body">
<form method="get" action="{{ url_for('resources') }}" id="filterForm"> <form method="get" action="{{ url_for('resources.resources') }}" id="filterForm">
<input type="hidden" name="show_test" value="{{ 'true' if show_test else 'false' }}"> <input type="hidden" name="show_test" value="{{ 'true' if show_test else 'false' }}">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-3"> <div class="col-md-3">
@@ -344,9 +344,9 @@
<i class="bi bi-download"></i> Export <i class="bi bi-download"></i> Export
</button> </button>
<ul class="dropdown-menu" aria-labelledby="exportDropdown"> <ul class="dropdown-menu" aria-labelledby="exportDropdown">
<li><a class="dropdown-item" href="/export/resources?format=excel&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}&show_test={{ show_test }}"> <li><a class="dropdown-item" href="{{ url_for('export.resources', format='excel', type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
<li><a class="dropdown-item" href="/export/resources?format=csv&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}&show_test={{ show_test }}"> <li><a class="dropdown-item" href="{{ url_for('export.resources', format='csv', type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
<i class="bi bi-file-earmark-text"></i> CSV Export</a></li> <i class="bi bi-file-earmark-text"></i> CSV Export</a></li>
</ul> </ul>
</div> </div>
@@ -504,7 +504,7 @@
<td class="text-center"> <td class="text-center">
{% if resource.status == 'quarantine' %} {% if resource.status == 'quarantine' %}
<!-- Quick Action für Quarantäne --> <!-- Quick Action für Quarantäne -->
<form method="post" action="/resources/release?show_test={{ show_test }}&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}" <form method="post" action="{{ url_for('resources.release', show_test=show_test, type=resource_type, status=status_filter, search=search) }}"
style="display: inline-block; margin-right: 5px;"> style="display: inline-block; margin-right: 5px;">
<input type="hidden" name="resource_ids" value="{{ resource.id }}"> <input type="hidden" name="resource_ids" value="{{ resource.id }}">
<input type="hidden" name="show_test" value="{{ show_test }}"> <input type="hidden" name="show_test" value="{{ show_test }}">
@@ -528,7 +528,7 @@
<!-- Historie immer verfügbar --> <!-- Historie immer verfügbar -->
<li> <li>
<a class="dropdown-item" <a class="dropdown-item"
href="{{ url_for('resource_history', resource_id=resource.id) }}"> href="{{ url_for('resources.resource_history', resource_id=resource.id) }}">
<i class="bi bi-clock-history text-info"></i> Historie anzeigen <i class="bi bi-clock-history text-info"></i> Historie anzeigen
</a> </a>
</li> </li>
@@ -548,7 +548,7 @@
{% if resource.allocated_to_license %} {% if resource.allocated_to_license %}
<li> <li>
<a class="dropdown-item" <a class="dropdown-item"
href="{{ url_for('edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}"> href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}">
<i class="bi bi-file-text text-primary"></i> Lizenz bearbeiten <i class="bi bi-file-text text-primary"></i> Lizenz bearbeiten
</a> </a>
</li> </li>
@@ -556,7 +556,7 @@
{% if resource.id %} {% if resource.id %}
<li> <li>
<a class="dropdown-item" <a class="dropdown-item"
href="{{ url_for('customers_licenses', customer_id=resource.id, show_test=show_test) }}"> href="{{ url_for('customers.customers_licenses', customer_id=resource.id, show_test=show_test) }}">
<i class="bi bi-person text-primary"></i> Kunde anzeigen <i class="bi bi-person text-primary"></i> Kunde anzeigen
</a> </a>
</li> </li>
@@ -564,7 +564,7 @@
{% elif resource.status == 'quarantine' %} {% elif resource.status == 'quarantine' %}
<!-- Aktionen für Quarantäne-Ressourcen --> <!-- Aktionen für Quarantäne-Ressourcen -->
<li> <li>
<form method="post" action="/resources/release?show_test={{ show_test }}&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}" <form method="post" action="{{ url_for('resources.release', show_test=show_test, type=resource_type, status=status_filter, search=search) }}"
style="display: contents;"> style="display: contents;">
<input type="hidden" name="resource_ids" value="{{ resource.id }}"> <input type="hidden" name="resource_ids" value="{{ resource.id }}">
<input type="hidden" name="show_test" value="{{ show_test }}"> <input type="hidden" name="show_test" value="{{ show_test }}">
@@ -845,7 +845,7 @@ function showQuarantineModal(resourceId) {
// URL mit aktuellen Filtern // URL mit aktuellen Filtern
const currentUrl = new URL(window.location); const currentUrl = new URL(window.location);
const params = new URLSearchParams(currentUrl.search); const params = new URLSearchParams(currentUrl.search);
form.setAttribute('action', `/resources/quarantine/${resourceId}?${params.toString()}`); form.setAttribute('action', `{{ url_for('resources.quarantine', resource_id='') }}${resourceId}?${params.toString()}`);
modal.show(); modal.show();
} }

Datei anzeigen

@@ -5,10 +5,10 @@
{% macro active_sortable_header(label, field, current_sort, current_order) %} {% macro active_sortable_header(label, field, current_sort, current_order) %}
<th> <th>
{% if current_sort == field %} {% if current_sort == field %}
<a href="{{ url_for('sessions', active_sort=field, active_order='desc' if current_order == 'asc' else 'asc', ended_sort=ended_sort, ended_order=ended_order) }}" <a href="{{ url_for('sessions.sessions', active_sort=field, active_order='desc' if current_order == 'asc' else 'asc', ended_sort=ended_sort, ended_order=ended_order) }}"
class="server-sortable"> class="server-sortable">
{% else %} {% else %}
<a href="{{ url_for('sessions', active_sort=field, active_order='asc', ended_sort=ended_sort, ended_order=ended_order) }}" <a href="{{ url_for('sessions.sessions', active_sort=field, active_order='asc', ended_sort=ended_sort, ended_order=ended_order) }}"
class="server-sortable"> class="server-sortable">
{% endif %} {% endif %}
{{ label }} {{ label }}
@@ -26,10 +26,10 @@
{% macro ended_sortable_header(label, field, current_sort, current_order) %} {% macro ended_sortable_header(label, field, current_sort, current_order) %}
<th> <th>
{% if current_sort == field %} {% if current_sort == field %}
<a href="{{ url_for('sessions', active_sort=active_sort, active_order=active_order, ended_sort=field, ended_order='desc' if current_order == 'asc' else 'asc') }}" <a href="{{ url_for('sessions.sessions', active_sort=active_sort, active_order=active_order, ended_sort=field, ended_order='desc' if current_order == 'asc' else 'asc') }}"
class="server-sortable"> class="server-sortable">
{% else %} {% else %}
<a href="{{ url_for('sessions', active_sort=active_sort, active_order=active_order, ended_sort=field, ended_order='asc') }}" <a href="{{ url_for('sessions.sessions', active_sort=active_sort, active_order=active_order, ended_sort=field, ended_order='asc') }}"
class="server-sortable"> class="server-sortable">
{% endif %} {% endif %}
{{ label }} {{ label }}
@@ -62,15 +62,15 @@
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><h6 class="dropdown-header">Aktive Sessions</h6></li> <li><h6 class="dropdown-header">Aktive Sessions</h6></li>
<li><a class="dropdown-item" href="/export/sessions?type=active&format=excel"> <li><a class="dropdown-item" href="{{ url_for('export.export_sessions', type='active', format='excel') }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
<li><a class="dropdown-item" href="/export/sessions?type=active&format=csv"> <li><a class="dropdown-item" href="{{ url_for('export.export_sessions', type='active', format='csv') }}">
<i class="bi bi-file-earmark-text"></i> CSV Export</a></li> <i class="bi bi-file-earmark-text"></i> CSV Export</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Beendete Sessions</h6></li> <li><h6 class="dropdown-header">Beendete Sessions</h6></li>
<li><a class="dropdown-item" href="/export/sessions?type=ended&format=excel"> <li><a class="dropdown-item" href="{{ url_for('export.export_sessions', type='ended', format='excel') }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li> <i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
<li><a class="dropdown-item" href="/export/sessions?type=ended&format=csv"> <li><a class="dropdown-item" href="{{ url_for('export.export_sessions', type='ended', format='csv') }}">
<i class="bi bi-file-earmark-text"></i> CSV Export</a></li> <i class="bi bi-file-earmark-text"></i> CSV Export</a></li>
</ul> </ul>
</div> </div>
@@ -114,7 +114,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<form method="post" action="/session/end/{{ session[0] }}" style="display: inline;"> <form method="post" action="{{ url_for('sessions.end_session', session_id=session[0]) }}" style="display: inline;">
<button type="submit" class="btn btn-sm btn-outline-danger" <button type="submit" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Session wirklich beenden?');"> onclick="return confirm('Session wirklich beenden?');">
⏹️ Beenden ⏹️ Beenden

Datei anzeigen

@@ -60,7 +60,7 @@
<div class="container py-4"> <div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1>🔐 2FA einrichten</h1> <h1>🔐 2FA einrichten</h1>
<a href="{{ url_for('profile') }}" class="btn btn-secondary">← Zurück zum Profil</a> <a href="{{ url_for('auth.profile') }}" class="btn btn-secondary">← Zurück zum Profil</a>
</div> </div>
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
@@ -167,7 +167,7 @@
</h5> </h5>
<p class="ms-5">Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein:</p> <p class="ms-5">Geben Sie den 6-stelligen Code aus Ihrer Authenticator-App ein:</p>
<form method="POST" action="{{ url_for('enable_2fa') }}" class="ms-5 me-5"> <form method="POST" action="{{ url_for('auth.enable_2fa') }}" class="ms-5 me-5">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<input type="text" <input type="text"

Datei anzeigen

@@ -94,7 +94,7 @@
<hr class="my-4"> <hr class="my-4">
<div class="text-center"> <div class="text-center">
<a href="/logout" class="text-muted">Abbrechen und zur Anmeldung zurück</a> <a href="{{ url_for('auth.logout') }}" class="text-muted">Abbrechen und zur Anmeldung zurück</a>
</div> </div>
</form> </form>
</div> </div>