Zuweisung über Kunden & Lizenzen geht

Dieser Commit ist enthalten in:
2025-06-15 23:13:09 +02:00
Ursprung 13e13869ef
Commit ff935204d5
2 geänderte Dateien mit 347 neuen und 22 gelöschten Zeilen

Datei anzeigen

@@ -234,7 +234,7 @@
<!-- Modal für Ressourcen-Verwaltung -->
<div class="modal fade" id="resourceManagementModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ressourcen verwalten</h5>
@@ -244,8 +244,11 @@
<!-- Wird dynamisch gefüllt -->
</div>
<div class="modal-footer">
<div class="me-auto">
<span class="text-muted" id="selectionCounter">0 Ressourcen ausgewählt</span>
</div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<button type="button" class="btn btn-primary" onclick="saveResourceChanges()">
<button type="button" class="btn btn-primary" onclick="saveResourceChanges()" id="saveResourcesBtn">
<i class="bi bi-save"></i> Änderungen speichern
</button>
</div>
@@ -310,6 +313,44 @@
.btn-link {
padding: 0 4px;
}
/* Multi-select improvements */
select[multiple] {
scrollbar-width: thin;
scrollbar-color: #6c757d #f8f9fa;
}
select[multiple]::-webkit-scrollbar {
width: 8px;
}
select[multiple]::-webkit-scrollbar-track {
background: #f8f9fa;
}
select[multiple]::-webkit-scrollbar-thumb {
background-color: #6c757d;
border-radius: 4px;
}
select[multiple] option {
padding: 4px 8px;
}
select[multiple] option:checked {
background-color: #0d6efd;
color: white;
}
select[multiple] option:hover {
background-color: #e9ecef;
color: #212529;
}
/* Modal improvements */
.modal-xl {
max-width: 1200px;
}
</style>
{% endblock %}
@@ -672,6 +713,9 @@ function showResourceManagement(licenseId) {
const license = currentLicenses[licenseId];
if (!license) return;
// Speichere License-ID im Modal für späteren Zugriff
document.getElementById('resourceManagementModal').dataset.licenseId = licenseId;
// Lade aktuelle Ressourcen-Informationen
fetch(`/api/license/${licenseId}/resources`)
.then(response => response.json())
@@ -775,7 +819,62 @@ function showResourceManagement(licenseId) {
content += `
</div>
<div class="alert alert-warning">
<hr class="my-4">
<h6>Neue Ressourcen zuweisen</h6>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Wählen Sie verfügbare Ressourcen aus, um sie dieser Lizenz zuzuweisen.
</div>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">🌐 Domains hinzufügen</label>
<div class="text-muted small mb-2">Mehrfachauswahl mit Strg/Cmd + Klick</div>
<select id="availableDomains" class="form-select" size="10" multiple>
<option value="" disabled>Lade verfügbare Domains...</option>
</select>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllInCategory('availableDomains')">
Alle auswählen
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deselectAllInCategory('availableDomains')">
Auswahl aufheben
</button>
</div>
</div>
<div class="col-md-4">
<label class="form-label">📡 IPv4-Adressen hinzufügen</label>
<div class="text-muted small mb-2">Mehrfachauswahl mit Strg/Cmd + Klick</div>
<select id="availableIpv4s" class="form-select" size="10" multiple>
<option value="" disabled>Lade verfügbare IPs...</option>
</select>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllInCategory('availableIpv4s')">
Alle auswählen
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deselectAllInCategory('availableIpv4s')">
Auswahl aufheben
</button>
</div>
</div>
<div class="col-md-4">
<label class="form-label">📱 Telefonnummern hinzufügen</label>
<div class="text-muted small mb-2">Mehrfachauswahl mit Strg/Cmd + Klick</div>
<select id="availablePhones" class="form-select" size="10" multiple>
<option value="" disabled>Lade verfügbare Nummern...</option>
</select>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectAllInCategory('availablePhones')">
Alle auswählen
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="deselectAllInCategory('availablePhones')">
Auswahl aufheben
</button>
</div>
</div>
</div>
<div class="alert alert-warning mt-3">
<i class="bi bi-exclamation-triangle"></i>
Das Quarantäne-setzen einer Ressource entfernt sie von der Lizenz und markiert sie als problematisch.
</div>`;
@@ -783,6 +882,20 @@ function showResourceManagement(licenseId) {
document.getElementById('resourceManagementContent').innerHTML = content;
const modal = new bootstrap.Modal(document.getElementById('resourceManagementModal'));
modal.show();
// Lade verfügbare Ressourcen
loadAvailableResources(licenseId);
// Add event listeners for selection changes after resources are loaded
setTimeout(() => {
['availableDomains', 'availableIpv4s', 'availablePhones'].forEach(selectId => {
const select = document.getElementById(selectId);
if (select) {
select.addEventListener('change', updateSelectionCounter);
}
});
updateSelectionCounter();
}, 500);
}
})
.catch(error => {
@@ -801,9 +914,187 @@ function quarantineResource(resourceId, resourceValue) {
alert('Diese Funktion wird noch implementiert');
}
// Dummy-Funktion für Speichern (wird später implementiert)
// Lade verfügbare Ressourcen
function loadAvailableResources(licenseId) {
// Hole show_test Parameter aus der URL
const urlParams = new URLSearchParams(window.location.search);
const showTest = urlParams.get('show_test') === 'true';
// Lade verfügbare Domains
fetch(`/api/resources/check-availability?type=domain&count=200&show_test=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availableDomains');
select.innerHTML = '';
if (data.available && data.available.length > 0) {
data.available.forEach(resource => {
const option = document.createElement('option');
option.value = resource.id;
option.textContent = resource.value;
select.appendChild(option);
});
// Zeige Anzahl verfügbarer Ressourcen
const label = select.parentElement.querySelector('.form-label');
label.innerHTML = `🌐 Domains hinzufügen (${data.available.length} verfügbar)`;
} else {
select.innerHTML = '<option value="" disabled>Keine verfügbaren Domains</option>';
}
});
// Lade verfügbare IPv4s
fetch(`/api/resources/check-availability?type=ipv4&count=200&show_test=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availableIpv4s');
select.innerHTML = '';
if (data.available && data.available.length > 0) {
data.available.forEach(resource => {
const option = document.createElement('option');
option.value = resource.id;
option.textContent = resource.value;
select.appendChild(option);
});
// Zeige Anzahl verfügbarer Ressourcen
const label = select.parentElement.querySelector('.form-label');
label.innerHTML = `📡 IPv4-Adressen hinzufügen (${data.available.length} verfügbar)`;
} else {
select.innerHTML = '<option value="" disabled>Keine verfügbaren IPv4-Adressen</option>';
}
});
// Lade verfügbare Telefonnummern
fetch(`/api/resources/check-availability?type=phone&count=200&show_test=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availablePhones');
select.innerHTML = '';
if (data.available && data.available.length > 0) {
data.available.forEach(resource => {
const option = document.createElement('option');
option.value = resource.id;
option.textContent = resource.value;
select.appendChild(option);
});
// Zeige Anzahl verfügbarer Ressourcen
const label = select.parentElement.querySelector('.form-label');
label.innerHTML = `📱 Telefonnummern hinzufügen (${data.available.length} verfügbar)`;
} else {
select.innerHTML = '<option value="" disabled>Keine verfügbaren Telefonnummern</option>';
}
});
}
// Alle Optionen in einer Kategorie auswählen
function selectAllInCategory(selectId) {
const select = document.getElementById(selectId);
for (let i = 0; i < select.options.length; i++) {
if (!select.options[i].disabled) {
select.options[i].selected = true;
}
}
updateSelectionCounter();
}
// Auswahl in einer Kategorie aufheben
function deselectAllInCategory(selectId) {
const select = document.getElementById(selectId);
for (let i = 0; i < select.options.length; i++) {
select.options[i].selected = false;
}
updateSelectionCounter();
}
// Update selection counter
function updateSelectionCounter() {
const domainsCount = document.getElementById('availableDomains').selectedOptions.length;
const ipv4sCount = document.getElementById('availableIpv4s').selectedOptions.length;
const phonesCount = document.getElementById('availablePhones').selectedOptions.length;
const totalCount = domainsCount + ipv4sCount + phonesCount;
const counter = document.getElementById('selectionCounter');
if (counter) {
counter.textContent = `${totalCount} Ressourcen ausgewählt`;
// Update save button state
const saveBtn = document.getElementById('saveResourcesBtn');
if (saveBtn) {
saveBtn.disabled = totalCount === 0;
if (totalCount === 0) {
saveBtn.classList.add('disabled');
} else {
saveBtn.classList.remove('disabled');
}
}
}
}
// Speichere Ressourcen-Änderungen
function saveResourceChanges() {
alert('Diese Funktion wird noch implementiert');
const modal = document.getElementById('resourceManagementModal');
const licenseId = modal.dataset.licenseId || currentLicenses[Object.keys(currentLicenses)[0]].id;
// Sammle ausgewählte Ressourcen
const selectedDomains = Array.from(document.getElementById('availableDomains').selectedOptions).map(opt => opt.value);
const selectedIpv4s = Array.from(document.getElementById('availableIpv4s').selectedOptions).map(opt => opt.value);
const selectedPhones = Array.from(document.getElementById('availablePhones').selectedOptions).map(opt => opt.value);
const allResources = [...selectedDomains, ...selectedIpv4s, ...selectedPhones];
if (allResources.length === 0) {
alert('Bitte wählen Sie mindestens eine Ressource aus.');
return;
}
// Disable save button and show loading state
const saveBtn = document.getElementById('saveResourcesBtn');
const originalBtnText = saveBtn.innerHTML;
saveBtn.disabled = true;
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', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
license_id: licenseId,
resource_ids: allResources
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Show success message with details
const domainCount = selectedDomains.length;
const ipv4Count = selectedIpv4s.length;
const phoneCount = selectedPhones.length;
let message = `${data.allocated} Ressourcen erfolgreich zugewiesen!\n\n`;
if (domainCount > 0) message += `🌐 ${domainCount} Domains\n`;
if (ipv4Count > 0) message += `📡 ${ipv4Count} IPv4-Adressen\n`;
if (phoneCount > 0) message += `📱 ${phoneCount} Telefonnummern`;
alert(message);
// Modal schließen und Ansicht aktualisieren
bootstrap.Modal.getInstance(modal).hide();
if (currentCustomerId) {
loadCustomerLicenses(currentCustomerId);
}
} else {
alert('❌ ' + (data.message || 'Fehler beim Zuweisen der Ressourcen'));
}
})
.catch(error => {
console.error('Error allocating resources:', error);
alert('❌ Fehler beim Zuweisen der Ressourcen');
})
.finally(() => {
// Restore button state
saveBtn.disabled = false;
saveBtn.innerHTML = originalBtnText;
});
}
// Geräte-Verwaltung anzeigen