FIX 3 für Codex
Dieser Commit ist enthalten in:
@@ -1,6 +1,6 @@
|
||||
# Fehlersuche - v2_adminpanel Refactoring
|
||||
|
||||
## Aktueller Stand (17.06.2025)
|
||||
## Aktueller Stand (17.06.2025 - 11:00 Uhr)
|
||||
|
||||
### Erfolgreiches Refactoring
|
||||
- Die ursprüngliche 5000+ Zeilen große app.py wurde erfolgreich in Module aufgeteilt:
|
||||
@@ -15,7 +15,10 @@
|
||||
- ✅ Blueprint-Registrierung funktioniert korrekt
|
||||
- ✅ /test-db Route funktioniert nach Docker-Rebuild
|
||||
- ✅ Kunden-Anzeige funktioniert mit Test-Daten-Filter
|
||||
- ✅ Lizenzen-Anzeige funktioniert mit erweiterten Filtern
|
||||
- ✅ Batch-Lizenzerstellung funktioniert
|
||||
- ✅ Ressourcen-Pool funktioniert vollständig
|
||||
- ✅ Ressourcen hinzufügen funktioniert
|
||||
|
||||
### Gelöste Probleme
|
||||
|
||||
@@ -136,4 +139,128 @@ Folgende Templates wurden von Tuple-Zugriff auf Dictionary-Zugriff umgestellt:
|
||||
### Status
|
||||
✅ **Alle bekannten Probleme wurden behoben**
|
||||
✅ **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
|
||||
118
v2_adminpanel/ROUTING_ISSUES_REPORT.md
Normale Datei
118
v2_adminpanel/ROUTING_ISSUES_REPORT.md
Normale Datei
@@ -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
|
||||
@@ -21,11 +21,73 @@ license_bp = Blueprint('licenses', __name__)
|
||||
@login_required
|
||||
def licenses():
|
||||
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)
|
||||
|
||||
# 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",
|
||||
licenses=licenses_list,
|
||||
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,
|
||||
timedelta=timedelta)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
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
|
||||
from auth.decorators import login_required
|
||||
@@ -39,10 +39,11 @@ def resources():
|
||||
rp.created_at,
|
||||
rp.status_changed_at,
|
||||
rp.status_changed_by,
|
||||
l.customer_name,
|
||||
c.name as customer_name,
|
||||
l.license_type
|
||||
FROM resource_pools rp
|
||||
LEFT JOIN licenses l ON rp.allocated_to_license = l.id
|
||||
LEFT JOIN customers c ON l.customer_id = c.id
|
||||
WHERE 1=1
|
||||
"""
|
||||
|
||||
@@ -58,7 +59,7 @@ def resources():
|
||||
params.append(status_filter)
|
||||
|
||||
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}%'])
|
||||
|
||||
if not show_test:
|
||||
@@ -103,8 +104,9 @@ def resources():
|
||||
count = row[3]
|
||||
|
||||
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
|
||||
if is_test:
|
||||
stats[res_type]['test'] += count
|
||||
@@ -128,60 +130,7 @@ def resources():
|
||||
conn.close()
|
||||
|
||||
|
||||
@resource_bp.route('/resources/add', methods=['GET', 'POST'])
|
||||
@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')
|
||||
# Old add_resource function removed - using add_resources instead
|
||||
|
||||
|
||||
@resource_bp.route('/resources/quarantine/<int:resource_id>', methods=['POST'])
|
||||
@@ -615,4 +564,108 @@ def resource_report():
|
||||
return redirect(url_for('resources.resources'))
|
||||
finally:
|
||||
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)
|
||||
@@ -11,7 +11,7 @@
|
||||
<h1 class="display-1">404</h1>
|
||||
<h2>Seite nicht gefunden</h2>
|
||||
<p>Die angeforderte Seite konnte nicht gefunden werden.</p>
|
||||
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-primary">Zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h1 class="display-1">500</h1>
|
||||
<h2>Interner Serverfehler</h2>
|
||||
<p>Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.</p>
|
||||
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
||||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-primary">Zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,12 +115,12 @@
|
||||
<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>
|
||||
</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
|
||||
</a>
|
||||
</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 -->
|
||||
<div class="card main-card mb-4">
|
||||
<div class="card-header bg-white">
|
||||
@@ -278,7 +278,7 @@ my-website.io</pre>
|
||||
|
||||
<!-- Submit Buttons -->
|
||||
<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
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success btn-lg" id="submitBtn" disabled>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{% macro sortable_header(label, field, current_sort, current_order) %}
|
||||
<th>
|
||||
{% 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">
|
||||
{% 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">
|
||||
{% endif %}
|
||||
{{ label }}
|
||||
@@ -70,7 +70,7 @@
|
||||
<!-- Filter -->
|
||||
<div class="card mb-3">
|
||||
<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="col-md-3">
|
||||
<label for="user" class="form-label">Benutzer</label>
|
||||
@@ -114,15 +114,15 @@
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<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">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-download"></i> Export
|
||||
</button>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -248,31 +248,31 @@
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Erste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Vorherige Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Seitenzahlen -->
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p >= page - 2 and p <= page + 2 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Nächste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Letzte Seite -->
|
||||
<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>
|
||||
</ul>
|
||||
<p class="text-center text-muted">
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
Ich habe die Backup-Codes sicher gespeichert
|
||||
</label>
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
<td class="backup-actions">
|
||||
{% if backup.status == 'success' %}
|
||||
<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"
|
||||
title="Backup herunterladen">
|
||||
📥 Download
|
||||
@@ -197,7 +197,7 @@ function createBackup() {
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '⏳ Backup wird erstellt...';
|
||||
|
||||
fetch('/backup/create', {
|
||||
fetch('{{ url_for('admin.create_backup') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'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>';
|
||||
document.body.appendChild(loadingDiv);
|
||||
|
||||
fetch(`/backup/restore/${backupId}`, {
|
||||
fetch(`{{ url_for('admin.restore_backup', backup_id='') }}${backupId}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
@@ -260,7 +260,7 @@ function confirmRestore() {
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ ' + data.message + '\n\nDie Seite wird neu geladen...');
|
||||
window.location.href = '/';
|
||||
window.location.href = '{{ url_for('admin.dashboard') }}';
|
||||
} else {
|
||||
alert('❌ ' + data.message);
|
||||
}
|
||||
@@ -278,7 +278,7 @@ function deleteBackup(backupId, filename) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/backup/delete/${backupId}`, {
|
||||
fetch(`{{ url_for('admin.delete_backup', backup_id='') }}${backupId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
<body class="bg-light">
|
||||
<nav class="navbar navbar-dark bg-dark navbar-expand-lg">
|
||||
<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">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@@ -353,8 +353,8 @@
|
||||
⏱️ <span id="timer-display">5:00</span>
|
||||
</div>
|
||||
<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="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
||||
<a href="{{ url_for('auth.profile') }}" class="btn btn-outline-light btn-sm me-2">👤 Profil</a>
|
||||
<a href="{{ url_for('auth.logout') }}" class="btn btn-outline-light btn-sm">Abmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,40 +363,40 @@
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<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 %}">
|
||||
<a class="nav-link has-submenu {% if request.endpoint == 'customers_licenses' %}active{% endif %}" href="/customers-licenses">
|
||||
<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.customers_licenses' %}active{% endif %}" href="{{ url_for('customers.customers_licenses') }}">
|
||||
<i class="bi bi-people"></i>
|
||||
<span>Kunden & Lizenzen</span>
|
||||
</a>
|
||||
<ul class="sidebar-submenu">
|
||||
<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>
|
||||
<span>Neuer Kunde</span>
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
<span>Neue Lizenz</span>
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
<span>Batch-Erstellung</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item {% if request.endpoint in ['resources', 'add_resources'] %}has-active-child{% endif %}">
|
||||
<a class="nav-link has-submenu {% if request.endpoint == 'resources' %}active{% endif %}" href="/resources">
|
||||
<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.resources' %}active{% endif %}" href="{{ url_for('resources.resources') }}">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<span>Resource Pool</span>
|
||||
</a>
|
||||
<ul class="sidebar-submenu">
|
||||
<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>
|
||||
<span>Ressourcen hinzufügen</span>
|
||||
</a>
|
||||
@@ -404,25 +404,25 @@
|
||||
</ul>
|
||||
</li>
|
||||
<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>
|
||||
<span>Audit-Log</span>
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
<span>Sitzungen</span>
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
<span>Backups</span>
|
||||
</a>
|
||||
</li>
|
||||
<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>
|
||||
<span>Sicherheit</span>
|
||||
</a>
|
||||
@@ -509,7 +509,7 @@
|
||||
|
||||
// Session verlängern
|
||||
function extendSession() {
|
||||
fetch('/heartbeat', {
|
||||
fetch('{{ url_for('auth.heartbeat') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -531,7 +531,7 @@
|
||||
|
||||
if (timeRemaining <= 0) {
|
||||
clearInterval(timerInterval);
|
||||
window.location.href = '/logout';
|
||||
window.location.href = '{{ url_for('auth.logout') }}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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 class="alert alert-info">
|
||||
@@ -26,7 +26,7 @@
|
||||
{% endif %}
|
||||
{% 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="col-md-12">
|
||||
<label for="customerSelect" class="form-label">Kunde auswählen</label>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>✅ Batch-Lizenzen erfolgreich generiert</h2>
|
||||
<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>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="card-body">
|
||||
<p>Exportieren Sie die generierten Lizenzen für den Kunden:</p>
|
||||
<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
|
||||
</a>
|
||||
<button class="btn btn-outline-primary" onclick="copyAllKeys()">
|
||||
@@ -102,7 +102,7 @@
|
||||
<!-- Hinweis -->
|
||||
<div class="alert alert-info mt-4">
|
||||
<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>
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
{% 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 }}">
|
||||
<button type="submit" class="btn btn-success"
|
||||
onclick="return confirm('IP {{ ip.ip_address }} wirklich entsperren?')">
|
||||
@@ -59,7 +59,7 @@
|
||||
</button>
|
||||
</form>
|
||||
{% 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 }}">
|
||||
<button type="submit" class="btn btn-warning"
|
||||
onclick="return confirm('Alle Versuche für IP {{ ip.ip_address }} zurücksetzen?')">
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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 class="card">
|
||||
<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="col-md-6">
|
||||
<label for="name" class="form-label">Kundenname <span class="text-danger">*</span></label>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{% macro sortable_header(label, field, current_sort, current_order) %}
|
||||
<th>
|
||||
{% 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">
|
||||
{% 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">
|
||||
{% endif %}
|
||||
{{ label }}
|
||||
@@ -32,7 +32,7 @@
|
||||
<!-- Suchformular -->
|
||||
<div class="card mb-3">
|
||||
<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">
|
||||
<label for="search" class="form-label">🔍 Suchen</label>
|
||||
<input type="text" class="form-control" id="search" name="search"
|
||||
@@ -49,13 +49,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</form>
|
||||
{% if search %}
|
||||
<div class="mt-2">
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -92,9 +92,9 @@
|
||||
</td>
|
||||
<td>
|
||||
<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 %}
|
||||
<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>
|
||||
</form>
|
||||
{% else %}
|
||||
@@ -111,10 +111,10 @@
|
||||
<div class="text-center py-5">
|
||||
{% if search %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -126,31 +126,31 @@
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Erste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Vorherige Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Seitenzahlen -->
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p >= page - 2 and p <= page + 2 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Nächste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Letzte Seite -->
|
||||
<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>
|
||||
</ul>
|
||||
<p class="text-center text-muted">
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
<i class="bi bi-download"></i> Export
|
||||
</button>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; opacity: 0.3;"></i>
|
||||
<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>
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@
|
||||
<small class="text-muted">{{ selected_customer[2] }}</small>
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
@@ -179,7 +179,7 @@
|
||||
<button class="btn btn-outline-info" onclick="showDeviceManagement({{ license[0] }})" title="Geräte verwalten">
|
||||
<i class="bi bi-laptop"></i>
|
||||
</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>
|
||||
</a>
|
||||
</div>
|
||||
@@ -544,7 +544,7 @@ function updateLicenseView(customerId, licenses) {
|
||||
function toggleLicenseStatus(licenseId, currentStatus) {
|
||||
const newStatus = !currentStatus;
|
||||
|
||||
fetch(`/api/license/${licenseId}/toggle`, {
|
||||
fetch(`{{ url_for('api.toggle_license', license_id='') }}${licenseId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -565,7 +565,7 @@ function toggleLicenseStatus(licenseId, currentStatus) {
|
||||
|
||||
// Direkt zur Lizenzerstellung navigieren
|
||||
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
|
||||
@@ -686,7 +686,7 @@ function showResourceDetails(licenseId, resourceType) {
|
||||
</td>
|
||||
<td>${resource.assigned_at}</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">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Details
|
||||
</a>
|
||||
@@ -1052,7 +1052,7 @@ function saveResourceChanges() {
|
||||
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span>Wird gespeichert...';
|
||||
|
||||
// API-Call zum Zuweisen der Ressourcen
|
||||
fetch('/api/resources/allocate', {
|
||||
fetch('{{ url_for('api.allocate_resources') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<!-- Statistik-Karten -->
|
||||
<div class="row g-3 mb-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-body text-center">
|
||||
<div class="card-icon text-primary">👥</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<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-body text-center">
|
||||
<div class="card-icon text-info">📋</div>
|
||||
@@ -94,7 +94,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<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-body text-center">
|
||||
<div class="card-icon text-success{% if stats.active_sessions > 0 %} pulse-effect{% endif %}">🟢</div>
|
||||
@@ -161,7 +161,7 @@
|
||||
<!-- Backup-Status und Sicherheit nebeneinander -->
|
||||
<div class="row g-3 mb-4">
|
||||
<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-body">
|
||||
<h5 class="card-title">💾 Backup-Status</h5>
|
||||
@@ -210,7 +210,7 @@
|
||||
<small class="text-muted">Fehlversuche heute</small>
|
||||
</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>
|
||||
@@ -277,14 +277,14 @@
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex-grow-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>
|
||||
</h6>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@@ -303,14 +303,14 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<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">
|
||||
{{ data.allocated }} zugeteilt
|
||||
</a>
|
||||
</small>
|
||||
{% if data.quarantine > 0 %}
|
||||
<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">
|
||||
<i class="bi bi-exclamation-triangle"></i> {{ data.quarantine }} in Quarantäne
|
||||
</a>
|
||||
@@ -325,7 +325,7 @@
|
||||
<div class="col-12 text-center text-muted">
|
||||
<i class="fas fa-inbox fa-3x mb-3"></i>
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
@@ -337,7 +337,7 @@
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Kritisch:</strong> {{ resource_warning }}
|
||||
</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">
|
||||
<i class="bi bi-plus"></i> Ressourcen auffüllen
|
||||
</a>
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Kunde bearbeiten</h2>
|
||||
<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 class="card mb-4">
|
||||
<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' %}
|
||||
<input type="hidden" name="show_test" value="true">
|
||||
{% endif %}
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
{% endif %}
|
||||
</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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Lizenz bearbeiten</h2>
|
||||
<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 class="card">
|
||||
<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' %}
|
||||
<input type="hidden" name="show_test" value="true">
|
||||
{% endif %}
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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>
|
||||
|
||||
<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="col-md-12">
|
||||
<label for="customerSelect" class="form-label">Kunde auswählen</label>
|
||||
@@ -197,7 +197,7 @@ function generateLicenseKey() {
|
||||
button.innerHTML = '⏳ Generiere...';
|
||||
|
||||
// API-Call
|
||||
fetch('/api/generate-license-key', {
|
||||
fetch('{{ url_for('api.api_generate_key') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -298,7 +298,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...',
|
||||
allowClear: true,
|
||||
ajax: {
|
||||
url: '/api/customers',
|
||||
url: '{{ url_for('api.api_customers') }}',
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
@@ -339,7 +339,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Vorausgewählten Kunden setzen (falls von kombinierter Ansicht kommend)
|
||||
{% if preselected_customer_id %}
|
||||
// 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(data => {
|
||||
if (data.results && data.results.length > 0) {
|
||||
@@ -403,7 +403,7 @@ function checkResourceAvailability() {
|
||||
const phoneCount = parseInt(document.getElementById('phoneCount').value) || 0;
|
||||
|
||||
// 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(data => {
|
||||
// Update der Verfügbarkeitsanzeigen
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{% macro sortable_header(label, field, current_sort, current_order) %}
|
||||
<th>
|
||||
{% 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">
|
||||
{% 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">
|
||||
{% endif %}
|
||||
{{ label }}
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- Such- und Filterformular -->
|
||||
<div class="card mb-3">
|
||||
<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="col-md-4">
|
||||
<label for="search" class="form-label">🔍 Suchen</label>
|
||||
@@ -64,7 +64,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
</form>
|
||||
@@ -154,8 +154,8 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="/license/edit/{{ 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?');">
|
||||
<a href="{{ url_for('licenses.edit_license', license_id=license.id) }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
@@ -169,10 +169,10 @@
|
||||
<div class="text-center py-5">
|
||||
{% if search %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -184,31 +184,31 @@
|
||||
<ul class="pagination justify-content-center">
|
||||
<!-- Erste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Vorherige Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Seitenzahlen -->
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p >= page - 2 and p <= page + 2 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Nächste Seite -->
|
||||
<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>
|
||||
|
||||
<!-- Letzte Seite -->
|
||||
<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>
|
||||
</ul>
|
||||
<p class="text-center text-muted">
|
||||
@@ -277,7 +277,7 @@ function copyToClipboard(text, button) {
|
||||
|
||||
// Toggle License Status
|
||||
function toggleLicenseStatus(licenseId, isActive) {
|
||||
fetch(`/api/license/${licenseId}/toggle`, {
|
||||
fetch(`{{ url_for('api.toggle_license', license_id='') }}${licenseId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -336,21 +336,21 @@ function getSelectedIds() {
|
||||
function bulkActivate() {
|
||||
const ids = getSelectedIds();
|
||||
if (confirm(`${ids.length} Lizenzen aktivieren?`)) {
|
||||
performBulkAction('/api/licenses/bulk-activate', ids);
|
||||
performBulkAction('{{ url_for('api.bulk_activate_licenses') }}', ids);
|
||||
}
|
||||
}
|
||||
|
||||
function bulkDeactivate() {
|
||||
const ids = getSelectedIds();
|
||||
if (confirm(`${ids.length} Lizenzen deaktivieren?`)) {
|
||||
performBulkAction('/api/licenses/bulk-deactivate', ids);
|
||||
performBulkAction('{{ url_for('api.bulk_deactivate_licenses') }}', ids);
|
||||
}
|
||||
}
|
||||
|
||||
function bulkDelete() {
|
||||
const ids = getSelectedIds();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
Passwort ändern
|
||||
</h5>
|
||||
<hr>
|
||||
<form method="POST" action="{{ url_for('change_password') }}">
|
||||
<form method="POST" action="{{ url_for('auth.change_password') }}">
|
||||
<div class="mb-3">
|
||||
<label for="current_password" class="form-label">Aktuelles Passwort</label>
|
||||
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||||
@@ -131,7 +131,7 @@
|
||||
</div>
|
||||
<div class="text-success" style="font-size: 3rem;">✅</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">
|
||||
<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">
|
||||
@@ -150,7 +150,7 @@
|
||||
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.
|
||||
</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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<h1 class="mb-0">Resource Historie</h1>
|
||||
<p class="text-muted mb-0">Detaillierte Aktivitätshistorie</p>
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
@@ -223,7 +223,7 @@
|
||||
<div class="info-item">
|
||||
<div class="info-label">Zugewiesen an Lizenz</div>
|
||||
<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">
|
||||
{{ license_info.license_key if license_info else 'ID: ' + resource.allocated_to_license|string }}
|
||||
</a>
|
||||
@@ -327,7 +327,7 @@
|
||||
{% if event.license_id %}
|
||||
•
|
||||
<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 }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -131,10 +131,10 @@
|
||||
<p class="text-muted mb-0">Resource Pool Metriken und Analysen</p>
|
||||
</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
|
||||
</a>
|
||||
<a href="{{ url_for('resources') }}" class="btn btn-secondary">
|
||||
<a href="{{ url_for('resources.resources') }}" class="btn btn-secondary">
|
||||
← Zurück
|
||||
</a>
|
||||
</div>
|
||||
@@ -262,7 +262,7 @@
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<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">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
@@ -325,7 +325,7 @@
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<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">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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
|
||||
</a>
|
||||
</div>
|
||||
@@ -18,7 +18,7 @@
|
||||
<h5 class="mb-0">Report-Einstellungen</h5>
|
||||
</div>
|
||||
<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="col-md-6">
|
||||
<label for="report_type" class="form-label">Report-Typ</label>
|
||||
|
||||
@@ -267,10 +267,10 @@
|
||||
{% if data.allocated > 0 %}{{ data.allocated }}{% endif %}
|
||||
</div>
|
||||
<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"
|
||||
title="{{ data.quarantine }} in Quarantäne">
|
||||
{% if data.quarantine > 0 %}{{ data.quarantine }}{% endif %}
|
||||
title="{{ data.quarantined }} in Quarantäne">
|
||||
{% if data.quarantined > 0 %}{{ data.quarantined }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
<!-- Filter -->
|
||||
<div class="card filter-card mb-4">
|
||||
<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' }}">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
@@ -344,9 +344,9 @@
|
||||
<i class="bi bi-download"></i> Export
|
||||
</button>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -504,7 +504,7 @@
|
||||
<td class="text-center">
|
||||
{% if resource.status == 'quarantine' %}
|
||||
<!-- 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;">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
|
||||
<input type="hidden" name="show_test" value="{{ show_test }}">
|
||||
@@ -528,7 +528,7 @@
|
||||
<!-- Historie immer verfügbar -->
|
||||
<li>
|
||||
<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
|
||||
</a>
|
||||
</li>
|
||||
@@ -548,7 +548,7 @@
|
||||
{% if resource.allocated_to_license %}
|
||||
<li>
|
||||
<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
|
||||
</a>
|
||||
</li>
|
||||
@@ -556,7 +556,7 @@
|
||||
{% if resource.id %}
|
||||
<li>
|
||||
<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
|
||||
</a>
|
||||
</li>
|
||||
@@ -564,7 +564,7 @@
|
||||
{% elif resource.status == 'quarantine' %}
|
||||
<!-- Aktionen für Quarantäne-Ressourcen -->
|
||||
<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;">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
|
||||
<input type="hidden" name="show_test" value="{{ show_test }}">
|
||||
@@ -845,7 +845,7 @@ function showQuarantineModal(resourceId) {
|
||||
// URL mit aktuellen Filtern
|
||||
const currentUrl = new URL(window.location);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
{% macro active_sortable_header(label, field, current_sort, current_order) %}
|
||||
<th>
|
||||
{% 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">
|
||||
{% 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">
|
||||
{% endif %}
|
||||
{{ label }}
|
||||
@@ -26,10 +26,10 @@
|
||||
{% macro ended_sortable_header(label, field, current_sort, current_order) %}
|
||||
<th>
|
||||
{% 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">
|
||||
{% 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">
|
||||
{% endif %}
|
||||
{{ label }}
|
||||
@@ -62,15 +62,15 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<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>
|
||||
<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>
|
||||
<li><hr class="dropdown-divider"></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>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -114,7 +114,7 @@
|
||||
{% endif %}
|
||||
</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"
|
||||
onclick="return confirm('Session wirklich beenden?');">
|
||||
⏹️ Beenden
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<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>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
@@ -167,7 +167,7 @@
|
||||
</h5>
|
||||
<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="col-md-6 mb-3">
|
||||
<input type="text"
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<hr class="my-4">
|
||||
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren