Files
v2-Docker/v2_adminpanel/templates/batch_form.html
Claude Project Manager 0d7d888502 Initial commit
2025-07-05 17:51:16 +02:00

514 Zeilen
22 KiB
HTML
Originalformat Blame Verlauf

Diese Datei enthält unsichtbare Unicode-Zeichen

Diese Datei enthält unsichtbare Unicode-Zeichen, die für Menschen nicht unterscheidbar sind, aber von einem Computer unterschiedlich verarbeitet werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen.

Diese Datei enthält Unicode-Zeichen, die mit anderen Zeichen verwechselt werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen.

{% extends "base.html" %}
{% block title %}Batch-Lizenzen erstellen{% endblock %}
{% block content %}
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>🔑 Batch-Lizenzen erstellen</h2>
<a href="{{ url_for('customers.customers_licenses') }}" class="btn btn-secondary">← Zurück zur Übersicht</a>
</div>
<div class="alert alert-info">
<strong> Batch-Generierung:</strong> Erstellen Sie mehrere Lizenzen auf einmal für einen Kunden.
Die Lizenzen werden automatisch generiert und können anschließend als CSV exportiert werden.
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Customer Type Indicator -->
<div id="customerTypeIndicator" class="alert d-none mb-3" role="alert">
<i class="fas fa-info-circle"></i> <span id="customerTypeMessage"></span>
</div>
<form method="post" action="{{ url_for('batch.batch_create') }}" accept-charset="UTF-8">
<div class="row g-3">
<div class="col-md-12">
<label for="customerSelect" class="form-label">Kunde auswählen</label>
<select class="form-select" id="customerSelect" name="customer_id" required>
<option value="">🔍 Kunde suchen oder neuen Kunden anlegen...</option>
<option value="new"> Neuer Kunde</option>
</select>
</div>
<div class="col-md-6" id="customerNameDiv" style="display: none;">
<label for="customerName" class="form-label">Kundenname</label>
<input type="text" class="form-control" id="customerName" name="customer_name"
placeholder="Firma GmbH" accept-charset="UTF-8">
</div>
<div class="col-md-6" id="emailDiv" style="display: none;">
<label for="email" class="form-label">E-Mail</label>
<input type="email" class="form-control" id="email" name="email"
placeholder="kontakt@firma.de" accept-charset="UTF-8">
</div>
<div class="col-md-3">
<label for="quantity" class="form-label">Anzahl Lizenzen</label>
<input type="number" class="form-control" id="quantity" name="quantity"
min="1" max="100" value="10" required>
<div class="form-text">Max. 100 Lizenzen pro Batch</div>
</div>
<div class="col-md-3">
<label for="licenseType" class="form-label">Lizenztyp</label>
<select class="form-select" id="licenseType" name="license_type" required>
<option value="full">Vollversion</option>
<option value="test">Testversion</option>
</select>
</div>
<div class="col-md-2">
<label for="validFrom" class="form-label">Gültig ab</label>
<input type="date" class="form-control" id="validFrom" name="valid_from" required>
</div>
<div class="col-md-1">
<label for="duration" class="form-label">Laufzeit</label>
<input type="number" class="form-control" id="duration" name="duration" value="1" min="1" required>
</div>
<div class="col-md-2">
<label for="durationType" class="form-label">Einheit</label>
<select class="form-select" id="durationType" name="duration_type" required>
<option value="days">Tage</option>
<option value="months">Monate</option>
<option value="years" selected>Jahre</option>
</select>
</div>
<div class="col-md-2">
<label for="validUntil" class="form-label">Gültig bis</label>
<input type="date" class="form-control" id="validUntil" name="valid_until" readonly style="background-color: #e9ecef;">
</div>
</div>
<!-- Resource Pool Allocation -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-server"></i> Ressourcen-Zuweisung pro Lizenz
<small class="text-muted float-end" id="resourceStatus"></small>
</h5>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label for="domainCount" class="form-label">
<i class="fas fa-globe"></i> Domains
</label>
<select class="form-select" id="domainCount" name="domain_count" required>
{% for i in range(11) %}
<option value="{{ i }}" {% if i == 1 %}selected{% endif %}>{{ i }}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Verfügbar: <span id="domainsAvailable" class="fw-bold">-</span>
| Benötigt: <span id="domainsNeeded" class="fw-bold">-</span>
</small>
</div>
<div class="col-md-4">
<label for="ipv4Count" class="form-label">
<i class="fas fa-network-wired"></i> IPv4-Adressen
</label>
<select class="form-select" id="ipv4Count" name="ipv4_count" required>
{% for i in range(11) %}
<option value="{{ i }}" {% if i == 1 %}selected{% endif %}>{{ i }}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Verfügbar: <span id="ipv4Available" class="fw-bold">-</span>
| Benötigt: <span id="ipv4Needed" class="fw-bold">-</span>
</small>
</div>
<div class="col-md-4">
<label for="phoneCount" class="form-label">
<i class="fas fa-phone"></i> Telefonnummern
</label>
<select class="form-select" id="phoneCount" name="phone_count" required>
{% for i in range(11) %}
<option value="{{ i }}" {% if i == 1 %}selected{% endif %}>{{ i }}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Verfügbar: <span id="phoneAvailable" class="fw-bold">-</span>
| Benötigt: <span id="phoneNeeded" class="fw-bold">-</span>
</small>
</div>
</div>
<div class="alert alert-warning mt-3 mb-0" role="alert">
<i class="fas fa-exclamation-triangle"></i>
<strong>Batch-Ressourcen:</strong> Jede Lizenz erhält die angegebene Anzahl an Ressourcen.
Bei 10 Lizenzen mit je 2 Domains werden insgesamt 20 Domains benötigt.
</div>
</div>
</div>
<!-- Device Limit -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-laptop"></i> Gerätelimit pro Lizenz
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label for="deviceLimit" class="form-label">
Maximale Anzahl Geräte pro Lizenz
</label>
<select class="form-select" id="deviceLimit" name="device_limit" required>
{% for i in range(1, 11) %}
<option value="{{ i }}" {% if i == 3 %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Jede generierte Lizenz kann auf maximal dieser Anzahl von Geräten gleichzeitig aktiviert werden.
</small>
</div>
</div>
</div>
</div>
<div class="mt-4 d-flex gap-2">
<button type="submit" class="btn btn-primary btn-lg">
🔑 Batch generieren
</button>
<button type="button" class="btn btn-outline-secondary" onclick="previewKeys()">
👁️ Vorschau
</button>
</div>
</form>
<!-- Vorschau Modal -->
<div class="modal fade" id="previewModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Vorschau der generierten Keys</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Es werden <strong id="previewQuantity">10</strong> Lizenzen im folgenden Format generiert:</p>
<div class="bg-light p-3 rounded font-monospace" id="previewFormat">
AF-F-YYYYMM-XXXX-YYYY-ZZZZ
</div>
<p class="mt-3 mb-0">Die Keys werden automatisch eindeutig generiert und in der Datenbank gespeichert.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Funktion zur Berechnung des Ablaufdatums
function calculateValidUntil() {
const validFrom = document.getElementById('validFrom').value;
const duration = parseInt(document.getElementById('duration').value) || 1;
const durationType = document.getElementById('durationType').value;
if (!validFrom) return;
const startDate = new Date(validFrom);
let endDate = new Date(startDate);
switch(durationType) {
case 'days':
endDate.setDate(endDate.getDate() + duration);
break;
case 'months':
endDate.setMonth(endDate.getMonth() + duration);
break;
case 'years':
endDate.setFullYear(endDate.getFullYear() + duration);
break;
}
// Kein Tag abziehen - die Lizenz ist bis einschließlich des Enddatums gültig
document.getElementById('validUntil').value = endDate.toISOString().split('T')[0];
}
// Event Listener für Änderungen
document.getElementById('validFrom').addEventListener('change', calculateValidUntil);
document.getElementById('duration').addEventListener('input', calculateValidUntil);
document.getElementById('durationType').addEventListener('change', calculateValidUntil);
// Setze heutiges Datum als Standard
document.addEventListener('DOMContentLoaded', function() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('validFrom').value = today;
// Initialize customer is_fake map
window.customerIsFakeMap = {};
// Berechne initiales Ablaufdatum
calculateValidUntil();
// Initialisiere Select2 für Kundenauswahl
$('#customerSelect').select2({
theme: 'bootstrap-5',
placeholder: '🔍 Kunde suchen oder neuen Kunden anlegen...',
allowClear: true,
ajax: {
url: '/api/customers',
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term,
page: params.page || 1
};
},
processResults: function (data, params) {
params.page = params.page || 1;
// Store is_fake status for each customer
const results = data.results || [];
results.forEach(customer => {
if (customer.id !== 'new') {
window.customerIsFakeMap[customer.id] = customer.is_fake || false;
}
});
// "Neuer Kunde" Option immer oben anzeigen
if (params.page === 1) {
results.unshift({
id: 'new',
text: ' Neuer Kunde',
isNew: true
});
}
return {
results: results,
pagination: data.pagination
};
},
cache: true
},
minimumInputLength: 0,
language: {
inputTooShort: function() { return ''; },
noResults: function() { return 'Keine Kunden gefunden'; },
searching: function() { return 'Suche...'; },
loadingMore: function() { return 'Lade weitere Ergebnisse...'; }
}
});
// Event Handler für Kundenauswahl
$('#customerSelect').on('select2:select', function (e) {
const selectedValue = e.params.data.id;
const nameDiv = document.getElementById('customerNameDiv');
const emailDiv = document.getElementById('emailDiv');
const nameInput = document.getElementById('customerName');
const emailInput = document.getElementById('email');
if (selectedValue === 'new') {
// Zeige Eingabefelder für neuen Kunden
nameDiv.style.display = 'block';
emailDiv.style.display = 'block';
nameInput.required = true;
emailInput.required = true;
// Zeige Indikator für neuen Kunden
showCustomerTypeIndicator('new');
} else {
// Verstecke Eingabefelder bei bestehendem Kunden
nameDiv.style.display = 'none';
emailDiv.style.display = 'none';
nameInput.required = false;
emailInput.required = false;
nameInput.value = '';
emailInput.value = '';
// Zeige Indikator basierend auf Kundendaten
if (e.params.data.is_fake !== undefined) {
showCustomerTypeIndicator(e.params.data.is_fake ? 'fake' : 'real');
}
}
// Update resource availability check when customer changes
checkResourceAvailability();
});
// Clear handler
$('#customerSelect').on('select2:clear', function (e) {
document.getElementById('customerNameDiv').style.display = 'none';
document.getElementById('emailDiv').style.display = 'none';
document.getElementById('customerName').required = false;
document.getElementById('email').required = false;
hideCustomerTypeIndicator();
window.customerIsFakeMap = {};
checkResourceAvailability();
});
// Resource Availability Check
checkResourceAvailability();
// Event Listener für Resource Count und Quantity Änderungen
document.getElementById('domainCount').addEventListener('change', checkResourceAvailability);
document.getElementById('ipv4Count').addEventListener('change', checkResourceAvailability);
document.getElementById('phoneCount').addEventListener('change', checkResourceAvailability);
document.getElementById('quantity').addEventListener('input', checkResourceAvailability);
});
// Vorschau-Funktion
function previewKeys() {
const quantity = document.getElementById('quantity').value;
const type = document.getElementById('licenseType').value;
const typeChar = type === 'full' ? 'F' : 'T';
const date = new Date();
const dateStr = date.getFullYear() + ('0' + (date.getMonth() + 1)).slice(-2);
document.getElementById('previewQuantity').textContent = quantity;
document.getElementById('previewFormat').textContent = `AF-${typeChar}-${dateStr}-XXXX-YYYY-ZZZZ`;
const modal = new bootstrap.Modal(document.getElementById('previewModal'));
modal.show();
}
// Validierung
document.getElementById('quantity').addEventListener('input', function(e) {
if (e.target.value > 100) {
e.target.value = 100;
}
if (e.target.value < 1) {
e.target.value = 1;
}
});
// Funktion zur Prüfung der Ressourcen-Verfügbarkeit für Batch
function checkResourceAvailability() {
const quantity = parseInt(document.getElementById('quantity').value) || 1;
const domainCount = parseInt(document.getElementById('domainCount').value) || 0;
const ipv4Count = parseInt(document.getElementById('ipv4Count').value) || 0;
const phoneCount = parseInt(document.getElementById('phoneCount').value) || 0;
// Berechne Gesamtbedarf
const totalDomains = domainCount * quantity;
const totalIpv4 = ipv4Count * quantity;
const totalPhones = phoneCount * quantity;
// Update "Benötigt" Anzeigen
document.getElementById('domainsNeeded').textContent = totalDomains;
document.getElementById('ipv4Needed').textContent = totalIpv4;
document.getElementById('phoneNeeded').textContent = totalPhones;
// Get customer's is_fake status
const selectedCustomer = document.getElementById('customer_id');
let isFake = 'false';
if (selectedCustomer && selectedCustomer.value && window.customerIsFakeMap) {
isFake = window.customerIsFakeMap[selectedCustomer.value] ? 'true' : 'false';
}
// API-Call zur Verfügbarkeitsprüfung
fetch(`/api/resources/check-availability?domain=${totalDomains}&ipv4=${totalIpv4}&phone=${totalPhones}&is_fake=${isFake}`)
.then(response => response.json())
.then(data => {
// Update der Verfügbarkeitsanzeigen
updateAvailabilityDisplay('domainsAvailable', data.domain_available, totalDomains);
updateAvailabilityDisplay('ipv4Available', data.ipv4_available, totalIpv4);
updateAvailabilityDisplay('phoneAvailable', data.phone_available, totalPhones);
// Gesamtstatus aktualisieren
updateBatchResourceStatus(data, totalDomains, totalIpv4, totalPhones, quantity);
})
.catch(error => {
console.error('Fehler bei Verfügbarkeitsprüfung:', error);
});
}
// Hilfsfunktion zur Anzeige der Verfügbarkeit
function updateAvailabilityDisplay(elementId, available, requested) {
const element = document.getElementById(elementId);
element.textContent = available;
const neededElement = element.parentElement.querySelector('.fw-bold:last-child');
if (requested > 0 && available < requested) {
element.classList.remove('text-success');
element.classList.add('text-danger');
neededElement.classList.add('text-danger');
neededElement.classList.remove('text-success');
} else if (available < 50) {
element.classList.remove('text-success', 'text-danger');
element.classList.add('text-warning');
} else {
element.classList.remove('text-danger', 'text-warning');
element.classList.add('text-success');
neededElement.classList.remove('text-danger');
neededElement.classList.add('text-success');
}
}
// Gesamtstatus der Ressourcen-Verfügbarkeit für Batch
function updateBatchResourceStatus(data, totalDomains, totalIpv4, totalPhones, quantity) {
const statusElement = document.getElementById('resourceStatus');
let hasIssue = false;
let message = '';
if (totalDomains > 0 && data.domain_available < totalDomains) {
hasIssue = true;
message = `⚠️ Nicht genügend Domains (${data.domain_available}/${totalDomains})`;
} else if (totalIpv4 > 0 && data.ipv4_available < totalIpv4) {
hasIssue = true;
message = `⚠️ Nicht genügend IPv4-Adressen (${data.ipv4_available}/${totalIpv4})`;
} else if (totalPhones > 0 && data.phone_available < totalPhones) {
hasIssue = true;
message = `⚠️ Nicht genügend Telefonnummern (${data.phone_available}/${totalPhones})`;
} else {
message = `✅ Ressourcen für ${quantity} Lizenzen verfügbar`;
}
statusElement.textContent = message;
statusElement.className = hasIssue ? 'text-danger' : 'text-success';
// Disable submit button if not enough resources
const submitButton = document.querySelector('button[type="submit"]');
submitButton.disabled = hasIssue;
if (hasIssue) {
submitButton.classList.add('btn-secondary');
submitButton.classList.remove('btn-primary');
} else {
submitButton.classList.add('btn-primary');
submitButton.classList.remove('btn-secondary');
}
}
// Funktion zur Anzeige des Kundentyp-Indikators
function showCustomerTypeIndicator(type) {
const indicator = document.getElementById('customerTypeIndicator');
const message = document.getElementById('customerTypeMessage');
indicator.classList.remove('d-none', 'alert-info', 'alert-warning', 'alert-success');
if (type === 'new') {
indicator.classList.add('alert-info');
message.textContent = 'Neue Kunden werden in der Testphase als TEST-Kunden erstellt. Alle Batch-Lizenzen werden automatisch als TEST-Lizenzen markiert.';
} else if (type === 'fake') {
indicator.classList.add('alert-warning');
message.textContent = 'Dies ist ein TEST-Kunde. Alle Batch-Lizenzen werden automatisch als TEST-Lizenzen markiert und von der Software ignoriert.';
} else if (type === 'real') {
indicator.classList.add('alert-success');
message.textContent = 'Dies ist ein PRODUKTIV-Kunde. Alle Batch-Lizenzen werden als produktive Lizenzen erstellt.';
}
}
function hideCustomerTypeIndicator() {
document.getElementById('customerTypeIndicator').classList.add('d-none');
}
</script>
{% endblock %}