Lizenzübersicht besser
Dieser Commit ist enthalten in:
@@ -24,47 +24,48 @@ def licenses():
|
||||
|
||||
# Get filter parameters
|
||||
search = request.args.get('search', '').strip()
|
||||
filter_types = request.args.getlist('types[]') # Multi-select for types
|
||||
filter_statuses = request.args.getlist('statuses[]') # Multi-select for statuses
|
||||
data_source = request.args.get('data_source', 'real') # real, fake, all
|
||||
license_type = request.args.get('license_type', '') # '', full, test
|
||||
license_status = request.args.get('license_status', '') # '', active, expiring, expired, inactive
|
||||
sort = request.args.get('sort', 'created_at')
|
||||
order = request.args.get('order', 'desc')
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 50
|
||||
|
||||
# Get all licenses (both fake and real data)
|
||||
licenses_list = get_licenses(show_fake=True)
|
||||
# Get licenses based on data source
|
||||
if data_source == 'fake':
|
||||
licenses_list = get_licenses(show_fake=True)
|
||||
licenses_list = [l for l in licenses_list if l.get('is_fake')]
|
||||
elif data_source == 'all':
|
||||
licenses_list = get_licenses(show_fake=True)
|
||||
else: # real
|
||||
licenses_list = get_licenses(show_fake=False)
|
||||
|
||||
# Type filtering with OR logic
|
||||
if filter_types:
|
||||
filtered_by_type = []
|
||||
for license in licenses_list:
|
||||
# Check if license matches any selected type
|
||||
if 'full' in filter_types and license.get('license_type') == 'full' and not license.get('is_fake'):
|
||||
filtered_by_type.append(license)
|
||||
elif 'test' in filter_types and license.get('license_type') == 'test' and not license.get('is_fake'):
|
||||
filtered_by_type.append(license)
|
||||
licenses_list = filtered_by_type
|
||||
else:
|
||||
# If no types selected, show only real data by default
|
||||
licenses_list = [l for l in licenses_list if not l.get('is_fake')]
|
||||
# Type filtering
|
||||
if license_type:
|
||||
if license_type == 'full':
|
||||
licenses_list = [l for l in licenses_list if l.get('license_type') == 'full']
|
||||
elif license_type == 'test':
|
||||
licenses_list = [l for l in licenses_list if l.get('license_type') == 'test']
|
||||
|
||||
# Status filtering with OR logic
|
||||
if filter_statuses:
|
||||
now = datetime.now()
|
||||
filtered_by_status = []
|
||||
# Status filtering
|
||||
if license_status:
|
||||
now = datetime.now().date()
|
||||
filtered_licenses = []
|
||||
|
||||
for license in licenses_list:
|
||||
# Check if license matches any selected status
|
||||
if 'active' in filter_statuses and license.get('is_active') and license.get('valid_until') and license.get('valid_until') > now:
|
||||
filtered_by_status.append(license)
|
||||
elif 'expiring' in filter_statuses:
|
||||
if license_status == 'active' and license.get('is_active') and license.get('valid_until') and license.get('valid_until') > now:
|
||||
filtered_licenses.append(license)
|
||||
elif license_status == 'expiring':
|
||||
expiry_threshold = now + timedelta(days=30)
|
||||
if license.get('valid_until') and now < license.get('valid_until') <= expiry_threshold:
|
||||
filtered_by_status.append(license)
|
||||
elif 'expired' in filter_statuses and license.get('valid_until') and license.get('valid_until') <= now:
|
||||
filtered_by_status.append(license)
|
||||
elif 'inactive' in filter_statuses and not license.get('is_active'):
|
||||
filtered_by_status.append(license)
|
||||
licenses_list = filtered_by_status
|
||||
filtered_licenses.append(license)
|
||||
elif license_status == 'expired' and license.get('valid_until') and license.get('valid_until') <= now:
|
||||
filtered_licenses.append(license)
|
||||
elif license_status == 'inactive' and not license.get('is_active'):
|
||||
filtered_licenses.append(license)
|
||||
|
||||
licenses_list = filtered_licenses
|
||||
|
||||
# Search filtering
|
||||
if search:
|
||||
@@ -84,8 +85,9 @@ def licenses():
|
||||
return render_template("licenses.html",
|
||||
licenses=licenses_list,
|
||||
search=search,
|
||||
filter_types=filter_types,
|
||||
filter_statuses=filter_statuses,
|
||||
data_source=data_source,
|
||||
license_type=license_type,
|
||||
license_status=license_status,
|
||||
sort=sort,
|
||||
order=order,
|
||||
page=page,
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
{% set base_url = url_for('licenses.licenses') %}
|
||||
{% set params = [] %}
|
||||
{% if search %}{% set _ = params.append('search=' + search|urlencode) %}{% endif %}
|
||||
{% for type in filter_types %}{% set _ = params.append('types[]=' + type|urlencode) %}{% endfor %}
|
||||
{% for status in filter_statuses %}{% set _ = params.append('statuses[]=' + status|urlencode) %}{% endfor %}
|
||||
{% if request.args.get('data_source') %}{% set _ = params.append('data_source=' + request.args.get('data_source')|urlencode) %}{% endif %}
|
||||
{% if request.args.get('license_type') %}{% set _ = params.append('license_type=' + request.args.get('license_type')|urlencode) %}{% endif %}
|
||||
{% if request.args.get('license_status') %}{% set _ = params.append('license_status=' + request.args.get('license_status')|urlencode) %}{% endif %}
|
||||
{% set _ = params.append('sort=' + field) %}
|
||||
{% if current_sort == field %}
|
||||
{% set _ = params.append('order=' + ('desc' if current_order == 'asc' else 'asc')) %}
|
||||
@@ -83,131 +84,62 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="{{ url_for('licenses.licenses') }}" id="filterForm">
|
||||
<!-- Suchfeld -->
|
||||
<!-- Filter Controls -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-3">
|
||||
<label for="dataSource" class="form-label">Datenquelle</label>
|
||||
<select class="form-select" name="data_source" id="dataSource" onchange="this.form.submit()">
|
||||
<option value="real" {% if request.args.get('data_source', 'real') == 'real' %}selected{% endif %}>Echte Lizenzen</option>
|
||||
<option value="fake" {% if request.args.get('data_source') == 'fake' %}selected{% endif %}>🧪 Fake-Daten</option>
|
||||
<option value="all" {% if request.args.get('data_source') == 'all' %}selected{% endif %}>Alle Daten</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="licenseType" class="form-label">Lizenztyp</label>
|
||||
<select class="form-select" name="license_type" id="licenseType" onchange="this.form.submit()">
|
||||
<option value="" {% if not request.args.get('license_type') %}selected{% endif %}>Alle Typen</option>
|
||||
<option value="full" {% if request.args.get('license_type') == 'full' %}selected{% endif %}>Vollversion</option>
|
||||
<option value="test" {% if request.args.get('license_type') == 'test' %}selected{% endif %}>Testversion</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="licenseStatus" class="form-label">Status</label>
|
||||
<select class="form-select" name="license_status" id="licenseStatus" onchange="this.form.submit()">
|
||||
<option value="" {% if not request.args.get('license_status') %}selected{% endif %}>Alle Status</option>
|
||||
<option value="active" {% if request.args.get('license_status') == 'active' %}selected{% endif %}>✅ Aktiv</option>
|
||||
<option value="expiring" {% if request.args.get('license_status') == 'expiring' %}selected{% endif %}>⏰ Läuft bald ab</option>
|
||||
<option value="expired" {% if request.args.get('license_status') == 'expired' %}selected{% endif %}>⚠️ Abgelaufen</option>
|
||||
<option value="inactive" {% if request.args.get('license_status') == 'inactive' %}selected{% endif %}>❌ Deaktiviert</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<a href="{{ url_for('licenses.licenses') }}" class="btn btn-secondary w-100">
|
||||
<i class="bi bi-arrow-clockwise"></i> Filter zurücksetzen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Field -->
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-9">
|
||||
<label for="search" class="form-label">🔍 Suchen</label>
|
||||
<input type="text" class="form-control" id="search" name="search"
|
||||
placeholder="Lizenzschlüssel, Kunde, E-Mail..."
|
||||
value="{{ search }}">
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<label class="form-label d-block"> </label>
|
||||
<button type="button" class="btn btn-outline-primary me-2" onclick="toggleFilters()">
|
||||
<i class="bi bi-funnel"></i> Filter
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-search"></i> Suchen
|
||||
</button>
|
||||
<a href="{{ url_for('licenses.licenses') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-clockwise"></i> Zurücksetzen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Erweiterter Filterbereich -->
|
||||
<div id="advancedFilters" class="collapse {{ 'show' if filter_types or filter_status else '' }}">
|
||||
<hr class="my-3">
|
||||
|
||||
<!-- Lizenztyp Filter (OR Logic) -->
|
||||
<div class="filter-group mb-3">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<h6 class="mb-0 me-2">Lizenztyp</h6>
|
||||
<span class="badge bg-info">ODER</span>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="types[]" value="full" id="typeFullversion"
|
||||
{% if 'full' in (filter_types or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="typeFullversion">
|
||||
<span class="badge bg-success">Vollversion</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="types[]" value="test" id="typeTestversion"
|
||||
{% if 'test' in (filter_types or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="typeTestversion">
|
||||
<span class="badge bg-warning">Testversion</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Filter (OR Logic) -->
|
||||
<div class="filter-group mb-3">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<h6 class="mb-0 me-2">Status</h6>
|
||||
<span class="badge bg-info">ODER</span>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="statuses[]" value="active" id="statusActive"
|
||||
{% if 'active' in (filter_statuses or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="statusActive">
|
||||
✅ Aktiv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="statuses[]" value="expiring" id="statusExpiring"
|
||||
{% if 'expiring' in (filter_statuses or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="statusExpiring">
|
||||
⏰ Läuft bald ab
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="statuses[]" value="expired" id="statusExpired"
|
||||
{% if 'expired' in (filter_statuses or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="statusExpired">
|
||||
⚠️ Abgelaufen
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="statuses[]" value="inactive" id="statusInactive"
|
||||
{% if 'inactive' in (filter_statuses or []) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="statusInactive">
|
||||
❌ Deaktiviert
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Filters / Presets -->
|
||||
<div class="filter-group">
|
||||
<h6 class="mb-2">Schnellfilter</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('expiring_soon')">
|
||||
<i class="bi bi-clock-history"></i> Läuft in 7 Tagen ab
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('all_test')">
|
||||
<i class="bi bi-bug"></i> Alle Testversionen
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('active_full')">
|
||||
<i class="bi bi-shield-check"></i> Aktive Vollversionen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields for sorting -->
|
||||
<!-- Hidden fields for sorting and pagination -->
|
||||
<input type="hidden" name="sort" value="{{ sort }}">
|
||||
<input type="hidden" name="order" value="{{ order }}">
|
||||
<!-- Note: Other filters are preserved by the form elements themselves -->
|
||||
</form>
|
||||
{% if search or filter_types or filter_statuses %}
|
||||
{% if search or request.args.get('data_source') != 'real' or request.args.get('license_type') or request.args.get('license_status') %}
|
||||
<div class="mt-2">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<small class="text-muted">
|
||||
@@ -220,23 +152,12 @@
|
||||
{% set clear_search_params = [] %}
|
||||
{% for type in filter_types %}{% set _ = clear_search_params.append('types[]=' + type|urlencode) %}{% endfor %}
|
||||
{% for status in filter_statuses %}{% set _ = clear_search_params.append('statuses[]=' + status|urlencode) %}{% endfor %}
|
||||
{% if show_fake %}{% set _ = clear_search_params.append('show_fake=1') %}{% endif %}
|
||||
{% set _ = clear_search_params.append('sort=' + sort) %}
|
||||
{% set _ = clear_search_params.append('order=' + order) %}
|
||||
<a href="{{ url_for('licenses.licenses') }}?{{ clear_search_params|join('&') }}" class="text-white ms-1">×</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for type in (filter_types or []) %}
|
||||
<span class="badge bg-primary me-1">
|
||||
{{ 'Vollversion' if type == 'full' else 'Testversion' }}
|
||||
<a href="#" onclick="removeFilter('type', '{{ type }}')" class="text-white ms-1">×</a>
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% for status in (filter_statuses or []) %}
|
||||
<span class="badge bg-info me-1">
|
||||
{% if status == 'active' %}✅ Aktiv{% elif status == 'expiring' %}⏰ Läuft bald ab{% elif status == 'expired' %}⚠️ Abgelaufen{% else %}❌ Deaktiviert{% endif %}
|
||||
<a href="#" onclick="removeFilter('status', '{{ status }}')" class="text-white ms-1">×</a>
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,76 +327,12 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Toggle Filter Panel
|
||||
function toggleFilters() {
|
||||
const filtersDiv = document.getElementById('advancedFilters');
|
||||
const bsCollapse = new bootstrap.Collapse(filtersDiv, {
|
||||
toggle: true
|
||||
});
|
||||
}
|
||||
|
||||
// Apply Preset Filters
|
||||
function applyPreset(preset) {
|
||||
const form = document.getElementById('filterForm');
|
||||
|
||||
// Clear existing filters
|
||||
document.querySelectorAll('input[name="types[]"]').forEach(cb => cb.checked = false);
|
||||
document.querySelectorAll('input[name="statuses[]"]').forEach(cb => cb.checked = false);
|
||||
|
||||
switch(preset) {
|
||||
case 'expiring_soon':
|
||||
document.getElementById('statusExpiring').checked = true;
|
||||
break;
|
||||
case 'all_test':
|
||||
document.getElementById('typeTestversion').checked = true;
|
||||
break;
|
||||
case 'active_full':
|
||||
document.getElementById('typeFullversion').checked = true;
|
||||
document.getElementById('statusActive').checked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// Remove Individual Filter
|
||||
function removeFilter(filterType, value) {
|
||||
const form = document.getElementById('filterForm');
|
||||
|
||||
if (filterType === 'type') {
|
||||
const checkbox = document.querySelector(`input[name="types[]"][value="${value}"]`);
|
||||
if (checkbox) checkbox.checked = false;
|
||||
} else if (filterType === 'status') {
|
||||
const checkbox = document.querySelector(`input[name="statuses[]"][value="${value}"]`);
|
||||
if (checkbox) checkbox.checked = false;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Live Filtering
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const filterForm = document.getElementById('filterForm');
|
||||
const searchInput = document.getElementById('search');
|
||||
|
||||
// Debounce timer für Suchfeld
|
||||
let searchTimeout;
|
||||
|
||||
// Live-Filter für Suchfeld (mit 300ms Verzögerung)
|
||||
searchInput.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
filterForm.submit();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Live-Filter für Checkboxen
|
||||
document.querySelectorAll('input[name="types[]"], input[name="statuses[]"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
filterForm.submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Copy to Clipboard
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren