Lizenzübersicht besser

Dieser Commit ist enthalten in:
2025-06-22 15:18:21 +02:00
Ursprung 1146406393
Commit ce03b90a96
2 geänderte Dateien mit 82 neuen und 223 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

@@ -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">&nbsp;</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">&times;</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">&times;</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">&times;</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