601 Zeilen
27 KiB
HTML
601 Zeilen
27 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Lizenzübersicht{% endblock %}
|
|
|
|
{% macro sortable_header(label, field, current_sort, current_order) %}
|
|
<th>
|
|
{% 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 %}
|
|
{% set _ = params.append('sort=' + field) %}
|
|
{% if current_sort == field %}
|
|
{% set _ = params.append('order=' + ('desc' if current_order == 'asc' else 'asc')) %}
|
|
{% else %}
|
|
{% set _ = params.append('order=asc') %}
|
|
{% endif %}
|
|
{% set _ = params.append('page=1') %}
|
|
|
|
<a href="{{ base_url }}?{{ params|join('&') }}" class="server-sortable">
|
|
{{ label }}
|
|
<span class="sort-indicator{% if current_sort == field %} active{% endif %}">
|
|
{% if current_sort == field %}
|
|
{% if current_order == 'asc' %}↑{% else %}↓{% endif %}
|
|
{% else %}
|
|
↕
|
|
{% endif %}
|
|
</span>
|
|
</a>
|
|
</th>
|
|
{% endmacro %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.filter-group {
|
|
background-color: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.filter-group h6 {
|
|
color: #495057;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge.bg-info {
|
|
background-color: #0dcaf0 !important;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.active-filters .badge {
|
|
font-size: 0.875rem;
|
|
padding: 0.35em 0.65em;
|
|
}
|
|
|
|
.active-filters a {
|
|
text-decoration: none;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.active-filters a:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
#advancedFilters {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 0.375rem 0.75rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container py-5">
|
|
<div class="mb-4">
|
|
<h2>Lizenzübersicht</h2>
|
|
</div>
|
|
|
|
<!-- Such- und Filterformular -->
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<form method="get" action="{{ url_for('licenses.licenses') }}" id="filterForm">
|
|
<!-- Suchfeld -->
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-8">
|
|
<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
|
|
</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 -->
|
|
<input type="hidden" name="sort" value="{{ sort }}">
|
|
<input type="hidden" name="order" value="{{ order }}">
|
|
</form>
|
|
{% if search or filter_types or filter_statuses %}
|
|
<div class="mt-2">
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<small class="text-muted">
|
|
Gefiltert: {{ total }} Ergebnisse
|
|
</small>
|
|
<div class="active-filters">
|
|
{% if search %}
|
|
<span class="badge bg-secondary me-1">
|
|
<i class="bi bi-search"></i> {{ search }}
|
|
{% 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 %}
|
|
{% 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>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body p-0">
|
|
<div class="table-container">
|
|
<table class="table table-hover table-sticky mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th class="checkbox-cell">
|
|
<input type="checkbox" class="form-check-input form-check-input-custom" id="selectAll">
|
|
</th>
|
|
{{ sortable_header('ID', 'id', sort, order) }}
|
|
{{ sortable_header('Lizenzschlüssel', 'license_key', sort, order) }}
|
|
{{ sortable_header('Kunde', 'customer', sort, order) }}
|
|
{{ sortable_header('E-Mail', 'email', sort, order) }}
|
|
{{ sortable_header('Typ', 'type', sort, order) }}
|
|
{{ sortable_header('Gültig von', 'valid_from', sort, order) }}
|
|
{{ sortable_header('Gültig bis', 'valid_until', sort, order) }}
|
|
{{ sortable_header('Status', 'status', sort, order) }}
|
|
{{ sortable_header('Aktiv', 'active', sort, order) }}
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for license in licenses %}
|
|
<tr>
|
|
<td class="checkbox-cell">
|
|
<input type="checkbox" class="form-check-input form-check-input-custom license-checkbox" value="{{ license.id }}">
|
|
</td>
|
|
<td>{{ license.id }}</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<code class="me-2">{{ license.license_key }}</code>
|
|
<button class="btn btn-sm btn-outline-secondary btn-copy" onclick="copyToClipboard('{{ license.license_key }}', this)" title="Kopieren">
|
|
📋
|
|
</button>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{{ license.customer_name }}
|
|
{% if license.is_fake %}
|
|
<span class="badge bg-secondary ms-1" title="Fake-Daten">🧪</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>-</td>
|
|
<td>
|
|
{% if license.license_type == 'full' %}
|
|
<span class="badge bg-success">Vollversion</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Testversion</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ license.valid_from.strftime('%d.%m.%Y') }}</td>
|
|
<td>{{ license.valid_until.strftime('%d.%m.%Y') }}</td>
|
|
<td>
|
|
{% if not license.is_active %}
|
|
<span class="status-deaktiviert">❌ Deaktiviert</span>
|
|
{% elif license.valid_until < now().date() %}
|
|
<span class="status-abgelaufen">⚠️ Abgelaufen</span>
|
|
{% elif license.valid_until < (now() + timedelta(days=30)).date() %}
|
|
<span class="status-ablaufend">⏰ Läuft bald ab</span>
|
|
{% else %}
|
|
<span class="status-aktiv">✅ Aktiv</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="form-check form-switch form-switch-custom">
|
|
<input class="form-check-input" type="checkbox"
|
|
id="active_{{ license.id }}"
|
|
{{ 'checked' if license.is_active else '' }}
|
|
onchange="toggleLicenseStatus({{ license.id }}, this.checked)">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<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>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if not licenses %}
|
|
<div class="text-center py-5">
|
|
{% if search %}
|
|
<p class="text-muted">Keine Lizenzen gefunden für: <strong>{{ search }}</strong></p>
|
|
<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="{{ url_for('licenses.create_license') }}" class="btn btn-primary">Erste Lizenz erstellen</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if total_pages > 1 %}
|
|
<nav aria-label="Seitennavigation" class="mt-3">
|
|
<ul class="pagination justify-content-center">
|
|
{% set base_url = url_for('licenses.licenses') %}
|
|
{% set base_params = [] %}
|
|
{% if search %}{% set _ = base_params.append('search=' + search|urlencode) %}{% endif %}
|
|
{% for type in filter_types %}{% set _ = base_params.append('types[]=' + type|urlencode) %}{% endfor %}
|
|
{% for status in filter_statuses %}{% set _ = base_params.append('statuses[]=' + status|urlencode) %}{% endfor %}
|
|
{% set _ = base_params.append('sort=' + sort) %}
|
|
{% set _ = base_params.append('order=' + order) %}
|
|
|
|
<!-- Erste Seite -->
|
|
<li class="page-item {% if page == 1 %}disabled{% endif %}">
|
|
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page=1">Erste</a>
|
|
</li>
|
|
|
|
<!-- Vorherige Seite -->
|
|
<li class="page-item {% if page == 1 %}disabled{% endif %}">
|
|
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ page-1 }}">←</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="{{ base_url }}?{{ base_params|join('&') }}&page={{ p }}">{{ p }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
<!-- Nächste Seite -->
|
|
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
|
|
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ page+1 }}">→</a>
|
|
</li>
|
|
|
|
<!-- Letzte Seite -->
|
|
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
|
|
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ total_pages }}">Letzte</a>
|
|
</li>
|
|
</ul>
|
|
<p class="text-center text-muted">
|
|
Seite {{ page }} von {{ total_pages }} | Gesamt: {{ total }} Lizenzen
|
|
</p>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Actions Bar -->
|
|
<div class="bulk-actions" id="bulkActionsBar">
|
|
<div>
|
|
<span id="selectedCount">0</span> Lizenzen ausgewählt
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-success btn-sm me-2" onclick="bulkActivate()">✅ Aktivieren</button>
|
|
<button class="btn btn-warning btn-sm me-2" onclick="bulkDeactivate()">⏸️ Deaktivieren</button>
|
|
<button class="btn btn-danger btn-sm" onclick="bulkDelete()">🗑️ Löschen</button>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% 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
|
|
function copyToClipboard(text, button) {
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
button.classList.add('copied');
|
|
button.innerHTML = '✅';
|
|
setTimeout(function() {
|
|
button.classList.remove('copied');
|
|
button.innerHTML = '📋';
|
|
}, 2000);
|
|
});
|
|
}
|
|
|
|
// Toggle License Status
|
|
function toggleLicenseStatus(licenseId, isActive) {
|
|
// Build URL manually to avoid template rendering issues
|
|
const baseUrl = '{{ url_for("api.toggle_license", license_id=999999) }}'.replace('999999', licenseId);
|
|
fetch(baseUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ is_active: isActive })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Optional: Show success message
|
|
} else {
|
|
// Revert toggle on error
|
|
document.getElementById(`active_${licenseId}`).checked = !isActive;
|
|
alert('Fehler beim Ändern des Status');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Bulk Selection
|
|
const selectAll = document.getElementById('selectAll');
|
|
const checkboxes = document.querySelectorAll('.license-checkbox');
|
|
const bulkActionsBar = document.getElementById('bulkActionsBar');
|
|
const selectedCount = document.getElementById('selectedCount');
|
|
|
|
selectAll.addEventListener('change', function() {
|
|
checkboxes.forEach(cb => cb.checked = this.checked);
|
|
updateBulkActions();
|
|
});
|
|
|
|
checkboxes.forEach(cb => {
|
|
cb.addEventListener('change', updateBulkActions);
|
|
});
|
|
|
|
function updateBulkActions() {
|
|
const checkedBoxes = document.querySelectorAll('.license-checkbox:checked');
|
|
const count = checkedBoxes.length;
|
|
|
|
if (count > 0) {
|
|
bulkActionsBar.classList.add('show');
|
|
selectedCount.textContent = count;
|
|
} else {
|
|
bulkActionsBar.classList.remove('show');
|
|
}
|
|
|
|
// Update select all checkbox
|
|
selectAll.checked = count === checkboxes.length && count > 0;
|
|
selectAll.indeterminate = count > 0 && count < checkboxes.length;
|
|
}
|
|
|
|
// Bulk Actions
|
|
function getSelectedIds() {
|
|
return Array.from(document.querySelectorAll('.license-checkbox:checked'))
|
|
.map(cb => cb.value);
|
|
}
|
|
|
|
function bulkActivate() {
|
|
const ids = getSelectedIds();
|
|
if (confirm(`${ids.length} Lizenzen aktivieren?`)) {
|
|
performBulkAction('{{ url_for('api.bulk_activate_licenses') }}', ids);
|
|
}
|
|
}
|
|
|
|
function bulkDeactivate() {
|
|
const ids = getSelectedIds();
|
|
if (confirm(`${ids.length} Lizenzen deaktivieren?`)) {
|
|
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('{{ url_for('api.bulk_delete_licenses') }}', ids);
|
|
}
|
|
}
|
|
|
|
function performBulkAction(url, ids) {
|
|
fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ ids: ids })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Show warnings if any licenses were skipped
|
|
if (data.warnings && data.warnings.length > 0) {
|
|
let warningMessage = data.message + '\n\n';
|
|
warningMessage += 'Warnungen:\n';
|
|
data.warnings.forEach(warning => {
|
|
warningMessage += '⚠️ ' + warning + '\n';
|
|
});
|
|
alert(warningMessage);
|
|
}
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler bei der Bulk-Aktion: ' + (data.error || data.message));
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |