Kunden & Lizenzen + Ressourcen - Nicht so zufrieden

Dieser Commit ist enthalten in:
2025-06-09 23:45:42 +02:00
Ursprung 06bfdfaf2a
Commit c7decff64e
6 geänderte Dateien mit 573 neuen und 40 gelöschten Zeilen

Datei anzeigen

@@ -155,9 +155,23 @@
</span>
</td>
<td>
{% if license[7] > 0 %}🌐 {{ license[7] }}{% endif %}
{% if license[8] > 0 %}📡 {{ license[8] }}{% endif %}
{% if license[9] > 0 %}📱 {{ license[9] }}{% endif %}
<div class="resource-info">
{% if license[7] > 0 %}
<div class="d-inline-block me-2" data-bs-toggle="tooltip" title="Domains">
🌐 {{ license[7] }}
</div>
{% endif %}
{% if license[8] > 0 %}
<div class="d-inline-block me-2" data-bs-toggle="tooltip" title="IPv4-Adressen">
📡 {{ license[8] }}
</div>
{% endif %}
{% if license[9] > 0 %}
<div class="d-inline-block" data-bs-toggle="tooltip" title="Telefonnummern">
📱 {{ license[9] }}
</div>
{% endif %}
</div>
</td>
<td>
<div class="btn-group btn-group-sm">
@@ -196,7 +210,47 @@
</div>
</div>
<!-- Modal für neue Lizenz -->
<!-- Modal für Ressourcen-Details -->
<div class="modal fade" id="resourceDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ressourcen-Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="resourceDetailsContent">
<!-- Wird dynamisch gefüllt -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<a href="#" id="goToResourcePool" class="btn btn-primary">
<i class="bi bi-box-arrow-up-right"></i> Zum Resource Pool
</a>
</div>
</div>
</div>
</div>
<!-- Modal für Ressourcen-Verwaltung -->
<div class="modal fade" id="resourceManagementModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ressourcen verwalten</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="resourceManagementContent">
<!-- Wird dynamisch gefüllt -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<button type="button" class="btn btn-primary" onclick="saveResourceChanges()">
<i class="bi bi-save"></i> Änderungen speichern
</button>
</div>
</div>
</div>
</div>
<style>
.customer-item {
@@ -217,6 +271,22 @@
.table-hover tbody tr:hover {
background-color: #f8f9fa;
}
.resource-group {
display: inline-block;
padding: 2px 8px;
background-color: #f8f9fa;
border-radius: 4px;
margin-right: 4px;
}
.resource-icon {
font-size: 0.9rem;
}
.resources-cell {
min-width: 150px;
}
.btn-link {
padding: 0 4px;
}
</style>
{% endblock %}
@@ -333,8 +403,37 @@ function updateLicenseView(customerId, licenses) {
const typeClass = license.license_type === 'full' ? 'bg-primary' : 'bg-secondary';
// Erstelle Ressourcen-HTML mit Details
let resourcesHtml = '';
if (license.resources) {
if (license.domain_count > 0 && license.resources.domains) {
resourcesHtml += `<div class="resource-group mb-1">
<span class="resource-icon" data-bs-toggle="tooltip" title="Domains">🌐 ${license.domain_count}</span>
<button class="btn btn-sm btn-link p-0 ms-1" onclick="showResourceDetails(${license.id}, 'domain')">
<i class="bi bi-info-circle"></i>
</button>
</div>`;
}
if (license.ipv4_count > 0 && license.resources.ipv4s) {
resourcesHtml += `<div class="resource-group mb-1">
<span class="resource-icon" data-bs-toggle="tooltip" title="IPv4-Adressen">📡 ${license.ipv4_count}</span>
<button class="btn btn-sm btn-link p-0 ms-1" onclick="showResourceDetails(${license.id}, 'ipv4')">
<i class="bi bi-info-circle"></i>
</button>
</div>`;
}
if (license.phone_count > 0 && license.resources.phones) {
resourcesHtml += `<div class="resource-group mb-1">
<span class="resource-icon" data-bs-toggle="tooltip" title="Telefonnummern">📱 ${license.phone_count}</span>
<button class="btn btn-sm btn-link p-0 ms-1" onclick="showResourceDetails(${license.id}, 'phone')">
<i class="bi bi-info-circle"></i>
</button>
</div>`;
}
}
html += `
<tr>
<tr data-license-id="${license.id}">
<td>
<code>${license.license_key}</code>
<button class="btn btn-sm btn-link" onclick="copyToClipboard('${license.license_key}')">
@@ -345,17 +444,18 @@ function updateLicenseView(customerId, licenses) {
<td>${license.valid_from || '-'}</td>
<td>${license.valid_until || '-'}</td>
<td><span class="badge ${statusClass}">${license.status}</span></td>
<td>
${license.domain_count > 0 ? '🌐 ' + license.domain_count : ''}
${license.ipv4_count > 0 ? '📡 ' + license.ipv4_count : ''}
${license.phone_count > 0 ? '📱 ' + license.phone_count : ''}
<td class="resources-cell">
${resourcesHtml || '<span class="text-muted">-</span>'}
</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus(${license.id}, ${license.is_active})">
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus(${license.id}, ${license.is_active})" title="Status ändern">
<i class="bi bi-power"></i>
</button>
<a href="/license/edit/${license.id}${window.location.search ? '?ref=customers-licenses&' + window.location.search.substring(1) : ''}" class="btn btn-outline-secondary">
<button class="btn btn-outline-info" onclick="showResourceManagement(${license.id})" title="Ressourcen verwalten">
<i class="bi bi-gear"></i>
</button>
<a href="/license/edit/${license.id}${window.location.search ? '?ref=customers-licenses&' + window.location.search.substring(1) : ''}" class="btn btn-outline-secondary" title="Bearbeiten">
<i class="bi bi-pencil"></i>
</a>
</div>
@@ -439,5 +539,239 @@ document.addEventListener('keydown', function(e) {
loadCustomerLicenses(customerId);
}
});
// Globale Variable für aktuelle Lizenz-Daten
let currentLicenses = {};
// Erweitere updateLicenseView um Daten zu speichern
const originalUpdateLicenseView = updateLicenseView;
updateLicenseView = function(customerId, licenses) {
// Speichere Lizenzdaten für späteren Zugriff
licenses.forEach(license => {
currentLicenses[license.id] = license;
});
originalUpdateLicenseView(customerId, licenses);
// Bootstrap Tooltips initialisieren
setTimeout(() => {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
}, 100);
};
// Zeige Ressourcen-Details
function showResourceDetails(licenseId, resourceType) {
const license = currentLicenses[licenseId];
if (!license || !license.resources) return;
let resources = [];
let title = '';
let icon = '';
switch(resourceType) {
case 'domain':
resources = license.resources.domains;
title = 'Domains';
icon = '🌐';
break;
case 'ipv4':
resources = license.resources.ipv4s;
title = 'IPv4-Adressen';
icon = '📡';
break;
case 'phone':
resources = license.resources.phones;
title = 'Telefonnummern';
icon = '📱';
break;
}
// Modal-Inhalt generieren
let content = `
<h6>${icon} ${title} (${resources.length})</h6>
<div class="table-responsive mt-3">
<table class="table table-sm">
<thead>
<tr>
<th>Ressource</th>
<th>Zugewiesen am</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>`;
resources.forEach(resource => {
content += `
<tr>
<td>
<code>${resource.value}</code>
<button class="btn btn-sm btn-link" onclick="copyToClipboard('${resource.value}')">
<i class="bi bi-clipboard"></i>
</button>
</td>
<td>${resource.assigned_at}</td>
<td>
<a href="/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>
</td>
</tr>`;
});
content += `
</tbody>
</table>
</div>`;
// Link zum Resource Pool vorbereiten
document.getElementById('goToResourcePool').href = `/resources?type=${resourceType}&status=allocated`;
// Modal zeigen
document.getElementById('resourceDetailsContent').innerHTML = content;
const modal = new bootstrap.Modal(document.getElementById('resourceDetailsModal'));
modal.show();
}
// Ressourcen-Verwaltung Modal
function showResourceManagement(licenseId) {
const license = currentLicenses[licenseId];
if (!license) return;
// Lade aktuelle Ressourcen-Informationen
fetch(`/api/license/${licenseId}/resources`)
.then(response => response.json())
.then(data => {
if (data.success) {
let content = `
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Hier können Sie die Ressourcen dieser Lizenz verwalten.
</div>
<h6>Aktuelle Ressourcen</h6>
<div class="row g-3 mb-4">`;
// Domains
if (license.domain_count > 0) {
content += `
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">🌐 Domains (${license.domain_count})</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">`;
data.resources.domains.forEach(domain => {
content += `
<li class="mb-2">
<code>${domain.value}</code>
<button class="btn btn-sm btn-link text-danger float-end"
onclick="quarantineResource(${domain.id}, '${domain.value}')">
<i class="bi bi-exclamation-triangle"></i>
</button>
</li>`;
});
content += `
</ul>
</div>
</div>
</div>`;
}
// IPv4s
if (license.ipv4_count > 0) {
content += `
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">📡 IPv4-Adressen (${license.ipv4_count})</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">`;
data.resources.ipv4s.forEach(ipv4 => {
content += `
<li class="mb-2">
<code>${ipv4.value}</code>
<button class="btn btn-sm btn-link text-danger float-end"
onclick="quarantineResource(${ipv4.id}, '${ipv4.value}')">
<i class="bi bi-exclamation-triangle"></i>
</button>
</li>`;
});
content += `
</ul>
</div>
</div>
</div>`;
}
// Phones
if (license.phone_count > 0) {
content += `
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">📱 Telefonnummern (${license.phone_count})</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">`;
data.resources.phones.forEach(phone => {
content += `
<li class="mb-2">
<code>${phone.value}</code>
<button class="btn btn-sm btn-link text-danger float-end"
onclick="quarantineResource(${phone.id}, '${phone.value}')">
<i class="bi bi-exclamation-triangle"></i>
</button>
</li>`;
});
content += `
</ul>
</div>
</div>
</div>`;
}
content += `
</div>
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
Das Quarantäne-setzen einer Ressource entfernt sie von der Lizenz und markiert sie als problematisch.
</div>`;
document.getElementById('resourceManagementContent').innerHTML = content;
const modal = new bootstrap.Modal(document.getElementById('resourceManagementModal'));
modal.show();
}
})
.catch(error => {
console.error('Error loading resources:', error);
alert('Fehler beim Laden der Ressourcen-Details');
});
}
// Ressource in Quarantäne setzen
function quarantineResource(resourceId, resourceValue) {
if (!confirm(`Möchten Sie die Ressource "${resourceValue}" wirklich in Quarantäne setzen?`)) {
return;
}
// Hier würde die API-Call für Quarantäne implementiert
alert('Diese Funktion wird noch implementiert');
}
// Dummy-Funktion für Speichern (wird später implementiert)
function saveResourceChanges() {
alert('Diese Funktion wird noch implementiert');
}
</script>
{% endblock %}