777 Zeilen
34 KiB
HTML
777 Zeilen
34 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Kunden & Lizenzen - AccountForger Admin{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container py-5">
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2>👥 Kunden & Lizenzen</h2>
|
||
<div>
|
||
<a href="/customer/create" class="btn btn-primary">👤 Neuer Kunde</a>
|
||
<a href="/create" class="btn btn-success">➕ Neue Lizenz</a>
|
||
<a href="/batch" class="btn btn-primary">🔑 Batch-Lizenzen</a>
|
||
<div class="btn-group">
|
||
<button type="button" class="btn btn-info dropdown-toggle" data-bs-toggle="dropdown">
|
||
📥 Export
|
||
</button>
|
||
<ul class="dropdown-menu">
|
||
<li><a class="dropdown-item" href="/export/licenses?format=excel">📊 Excel (.xlsx)</a></li>
|
||
<li><a class="dropdown-item" href="/export/licenses?format=csv">📄 CSV (.csv)</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Kundenliste (Links) -->
|
||
<div class="col-md-4 col-lg-3">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">
|
||
<i class="bi bi-people"></i> Kunden
|
||
<span class="badge bg-secondary float-end">{{ customers|length if customers else 0 }}</span>
|
||
</h5>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<!-- Suchfeld -->
|
||
<div class="p-3 border-bottom">
|
||
<input type="text" class="form-control mb-2" id="customerSearch"
|
||
placeholder="Kunde suchen..." autocomplete="off">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="showTestCustomers"
|
||
{% if request.args.get('show_test', 'false').lower() == 'true' %}checked{% endif %}
|
||
onchange="toggleTestCustomers()">
|
||
<label class="form-check-label" for="showTestCustomers">
|
||
<small class="text-muted">Testkunden anzeigen</small>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Kundenliste -->
|
||
<div class="customer-list" style="max-height: 600px; overflow-y: auto;">
|
||
{% if customers %}
|
||
{% for customer in customers %}
|
||
<div class="customer-item p-3 border-bottom {% if customer[0] == selected_customer_id %}active{% endif %}"
|
||
data-customer-id="{{ customer[0] }}"
|
||
data-customer-name="{{ customer[1]|lower }}"
|
||
data-customer-email="{{ customer[2]|lower }}"
|
||
onclick="loadCustomerLicenses({{ customer[0] }})"
|
||
style="cursor: pointer;">
|
||
<div class="d-flex justify-content-between align-items-start">
|
||
<div class="flex-grow-1">
|
||
<h6 class="mb-1">{{ customer[1] }}</h6>
|
||
<small class="text-muted">{{ customer[2] }}</small>
|
||
</div>
|
||
<div class="text-end">
|
||
<span class="badge bg-primary">{{ customer[4] }}</span>
|
||
{% if customer[5] > 0 %}
|
||
<span class="badge bg-success">{{ customer[5] }}</span>
|
||
{% endif %}
|
||
{% if customer[6] > 0 %}
|
||
<span class="badge bg-danger">{{ customer[6] }}</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="p-4 text-center text-muted">
|
||
<i class="bi bi-inbox" style="font-size: 3rem; opacity: 0.3;"></i>
|
||
<p class="mt-3 mb-2">Keine Kunden vorhanden</p>
|
||
<small class="d-block mb-3">Erstellen Sie eine neue Lizenz, um automatisch einen Kunden anzulegen.</small>
|
||
<a href="/create" class="btn btn-sm btn-primary">
|
||
<i class="bi bi-plus-circle"></i> Neue Lizenz erstellen
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Lizenzdetails (Rechts) -->
|
||
<div class="col-md-8 col-lg-9">
|
||
<div class="card">
|
||
<div class="card-header bg-light">
|
||
{% if selected_customer %}
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<h5 class="mb-0">{{ selected_customer[1] }}</h5>
|
||
<small class="text-muted">{{ selected_customer[2] }}</small>
|
||
</div>
|
||
<div>
|
||
<a href="/customer/edit/{{ selected_customer[0] }}?ref=customers-licenses{% if request.args.get('show_test') %}&show_test=true{% endif %}" class="btn btn-sm btn-outline-primary">
|
||
<i class="bi bi-pencil"></i> Bearbeiten
|
||
</a>
|
||
<button class="btn btn-sm btn-success" onclick="showNewLicenseModal({{ selected_customer[0] }})">
|
||
<i class="bi bi-plus"></i> Neue Lizenz
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<h5 class="mb-0">Wählen Sie einen Kunden aus</h5>
|
||
{% endif %}
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="licenseContainer">
|
||
{% if selected_customer %}
|
||
{% if licenses %}
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Lizenzschlüssel</th>
|
||
<th>Typ</th>
|
||
<th>Gültig von</th>
|
||
<th>Gültig bis</th>
|
||
<th>Status</th>
|
||
<th>Ressourcen</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for license in licenses %}
|
||
<tr>
|
||
<td>
|
||
<code>{{ license[1] }}</code>
|
||
<button class="btn btn-sm btn-link" onclick="copyToClipboard('{{ license[1] }}')">
|
||
<i class="bi bi-clipboard"></i>
|
||
</button>
|
||
</td>
|
||
<td>
|
||
<span class="badge {% if license[2] == 'full' %}bg-primary{% else %}bg-secondary{% endif %}">
|
||
{{ license[2]|upper }}
|
||
</span>
|
||
</td>
|
||
<td>{{ license[3].strftime('%d.%m.%Y') if license[3] else '-' }}</td>
|
||
<td>{{ license[4].strftime('%d.%m.%Y') if license[4] else '-' }}</td>
|
||
<td>
|
||
<span class="badge
|
||
{% if license[6] == 'aktiv' %}bg-success
|
||
{% elif license[6] == 'läuft bald ab' %}bg-warning
|
||
{% elif license[6] == 'abgelaufen' %}bg-danger
|
||
{% else %}bg-secondary{% endif %}">
|
||
{{ license[6] }}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<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">
|
||
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus({{ license[0] }}, {{ license[5] }})">
|
||
<i class="bi bi-power"></i>
|
||
</button>
|
||
<a href="/license/edit/{{ license[0] }}{% if request.args.get('show_test') %}?ref=customers-licenses&show_test=true{% endif %}" class="btn btn-outline-secondary">
|
||
<i class="bi bi-pencil"></i>
|
||
</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="text-center py-5">
|
||
<i class="bi bi-inbox text-muted" style="font-size: 3rem;"></i>
|
||
<p class="text-muted mt-3">Keine Lizenzen für diesen Kunden vorhanden</p>
|
||
<button class="btn btn-success" onclick="showNewLicenseModal({{ selected_customer[0] }})">
|
||
<i class="bi bi-plus"></i> Erste Lizenz erstellen
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
{% else %}
|
||
<div class="text-center py-5">
|
||
<i class="bi bi-arrow-left text-muted" style="font-size: 3rem;"></i>
|
||
<p class="text-muted mt-3">Wählen Sie einen Kunden aus der Liste aus</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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 {
|
||
transition: all 0.2s ease;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
.customer-item:hover {
|
||
background-color: #f8f9fa;
|
||
border-left-color: #dee2e6;
|
||
}
|
||
.customer-item.active {
|
||
background-color: #e7f3ff;
|
||
border-left-color: #0d6efd;
|
||
}
|
||
.card {
|
||
box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075);
|
||
}
|
||
.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 %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
// Globale Variablen
|
||
let currentCustomerId = {{ selected_customer_id or 'null' }};
|
||
|
||
// Kundensuche
|
||
document.getElementById('customerSearch').addEventListener('input', function(e) {
|
||
const searchTerm = e.target.value.toLowerCase();
|
||
const customerItems = document.querySelectorAll('.customer-item');
|
||
|
||
customerItems.forEach(item => {
|
||
const name = item.dataset.customerName;
|
||
const email = item.dataset.customerEmail;
|
||
if (name.includes(searchTerm) || email.includes(searchTerm)) {
|
||
item.style.display = 'block';
|
||
} else {
|
||
item.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Lade Lizenzen eines Kunden
|
||
function loadCustomerLicenses(customerId) {
|
||
// Aktiven Status aktualisieren
|
||
document.querySelectorAll('.customer-item').forEach(item => {
|
||
item.classList.remove('active');
|
||
});
|
||
document.querySelector(`[data-customer-id="${customerId}"]`).classList.add('active');
|
||
|
||
// URL aktualisieren ohne Reload (behalte show_test Parameter)
|
||
const currentUrl = new URL(window.location);
|
||
currentUrl.searchParams.set('customer_id', customerId);
|
||
window.history.pushState({}, '', currentUrl.toString());
|
||
|
||
// Lade Lizenzen via AJAX
|
||
const container = document.getElementById('licenseContainer');
|
||
const cardHeader = document.querySelector('.card-header.bg-light');
|
||
container.innerHTML = '<div class="text-center py-5"><div class="spinner-border text-primary" role="status"></div></div>';
|
||
|
||
fetch(`/api/customer/${customerId}/licenses`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Update header with customer info
|
||
const customerItem = document.querySelector(`[data-customer-id="${customerId}"]`);
|
||
const customerName = customerItem.querySelector('h6').textContent;
|
||
const customerEmail = customerItem.querySelector('small').textContent;
|
||
|
||
cardHeader.innerHTML = `
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<h5 class="mb-0">${customerName}</h5>
|
||
<small class="text-muted">${customerEmail}</small>
|
||
</div>
|
||
<div>
|
||
<a href="/customer/edit/${customerId}?ref=customers-licenses${window.location.search ? '&' + window.location.search.substring(1) : ''}" class="btn btn-sm btn-outline-primary">
|
||
<i class="bi bi-pencil"></i> Bearbeiten
|
||
</a>
|
||
<button class="btn btn-sm btn-success" onclick="showNewLicenseModal(${customerId})">
|
||
<i class="bi bi-plus"></i> Neue Lizenz
|
||
</button>
|
||
</div>
|
||
</div>`;
|
||
|
||
updateLicenseView(customerId, data.licenses);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
container.innerHTML = '<div class="alert alert-danger">Fehler beim Laden der Lizenzen</div>';
|
||
});
|
||
}
|
||
|
||
// Aktualisiere Lizenzansicht
|
||
function updateLicenseView(customerId, licenses) {
|
||
currentCustomerId = customerId;
|
||
const container = document.getElementById('licenseContainer');
|
||
|
||
if (licenses.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="text-center py-5">
|
||
<i class="bi bi-inbox text-muted" style="font-size: 3rem;"></i>
|
||
<p class="text-muted mt-3">Keine Lizenzen für diesen Kunden vorhanden</p>
|
||
<button class="btn btn-success" onclick="showNewLicenseModal(${customerId})">
|
||
<i class="bi bi-plus"></i> Erste Lizenz erstellen
|
||
</button>
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
let html = `
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Lizenzschlüssel</th>
|
||
<th>Typ</th>
|
||
<th>Gültig von</th>
|
||
<th>Gültig bis</th>
|
||
<th>Status</th>
|
||
<th>Ressourcen</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
licenses.forEach(license => {
|
||
const statusClass = license.status === 'aktiv' ? 'bg-success' :
|
||
license.status === 'läuft bald ab' ? 'bg-warning' :
|
||
license.status === 'abgelaufen' ? 'bg-danger' : 'bg-secondary';
|
||
|
||
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 data-license-id="${license.id}">
|
||
<td>
|
||
<code>${license.license_key}</code>
|
||
<button class="btn btn-sm btn-link" onclick="copyToClipboard('${license.license_key}')">
|
||
<i class="bi bi-clipboard"></i>
|
||
</button>
|
||
</td>
|
||
<td><span class="badge ${typeClass}">${license.license_type.toUpperCase()}</span></td>
|
||
<td>${license.valid_from || '-'}</td>
|
||
<td>${license.valid_until || '-'}</td>
|
||
<td><span class="badge ${statusClass}">${license.status}</span></td>
|
||
<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})" title="Status ändern">
|
||
<i class="bi bi-power"></i>
|
||
</button>
|
||
<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>
|
||
</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += '</tbody></table></div>';
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
// Toggle Lizenzstatus
|
||
function toggleLicenseStatus(licenseId, currentStatus) {
|
||
const newStatus = !currentStatus;
|
||
|
||
fetch(`/api/license/${licenseId}/toggle`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ is_active: newStatus })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Reload current customer licenses
|
||
if (currentCustomerId) {
|
||
loadCustomerLicenses(currentCustomerId);
|
||
}
|
||
}
|
||
})
|
||
.catch(error => console.error('Error:', error));
|
||
}
|
||
|
||
// Direkt zur Lizenzerstellung navigieren
|
||
function showNewLicenseModal(customerId) {
|
||
window.location.href = `/create?customer_id=${customerId}${window.location.search ? '&' + window.location.search.substring(1) : ''}`;
|
||
}
|
||
|
||
// Copy to clipboard
|
||
function copyToClipboard(text) {
|
||
const button = event.currentTarget;
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
// Zeige kurz Feedback
|
||
button.innerHTML = '<i class="bi bi-check"></i>';
|
||
setTimeout(() => {
|
||
button.innerHTML = '<i class="bi bi-clipboard"></i>';
|
||
}, 1000);
|
||
}).catch(err => {
|
||
console.error('Fehler beim Kopieren:', err);
|
||
alert('Konnte nicht in die Zwischenablage kopieren');
|
||
});
|
||
}
|
||
|
||
// Toggle Testkunden
|
||
function toggleTestCustomers() {
|
||
const showTest = document.getElementById('showTestCustomers').checked;
|
||
const currentUrl = new URL(window.location);
|
||
currentUrl.searchParams.set('show_test', showTest);
|
||
window.location.href = currentUrl.toString();
|
||
}
|
||
|
||
// Keyboard navigation
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.target.id === 'customerSearch') return; // Nicht bei Suche
|
||
|
||
const activeItem = document.querySelector('.customer-item.active');
|
||
if (!activeItem) return;
|
||
|
||
let targetItem = null;
|
||
|
||
if (e.key === 'ArrowUp') {
|
||
targetItem = activeItem.previousElementSibling;
|
||
} else if (e.key === 'ArrowDown') {
|
||
targetItem = activeItem.nextElementSibling;
|
||
}
|
||
|
||
if (targetItem && targetItem.classList.contains('customer-item')) {
|
||
e.preventDefault();
|
||
const customerId = parseInt(targetItem.dataset.customerId);
|
||
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 %} |