changes to the processing state
This commit is contained in:
+12
-6
@@ -2517,27 +2517,33 @@ def booking_payment():
|
||||
|
||||
app_obj = current_app._get_current_object()
|
||||
user_id = session.get("user_id")
|
||||
username = session.get("username") # Hole Username für Übergabe
|
||||
username = session.get("username")
|
||||
user_email = session.get("email") or form_data.get("contact_email")
|
||||
|
||||
# Startet den asynchronen Provisioning-Thread
|
||||
thread = threading.Thread(
|
||||
target=background_booking_provisioning,
|
||||
args=(app_obj, prov_id, subdomain, package_list, user_id, form_data["school_name"], user_email, username) # Username am Ende hinzugefügt
|
||||
args=(app_obj, prov_id, subdomain, package_list, user_id, form_data["school_name"], user_email, username)
|
||||
)
|
||||
thread.start()
|
||||
print(f"[INFO] Background Provisioning Trigger gestartet für Subdomain: {subdomain}")
|
||||
|
||||
# Antworte AJAX-Aufrufen direkt mit JSON
|
||||
# ANPASSUNG FÜR BESSERE WARTEZEIT:
|
||||
# Wir geben Metadaten mit, wie lange das Setup ca. dauern wird.
|
||||
if is_json_request:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"slug": subdomain,
|
||||
"message": "Provisioning gestartet."
|
||||
"message": "Instanz wird eingerichtet...",
|
||||
"estimated_seconds": 45, # Erwartete Dauer in Sekunden (anpassbar)
|
||||
"status_poll_interval_ms": 3000 # Wie oft soll das Frontend fragen? (3 Sek)
|
||||
}), 200
|
||||
|
||||
flash(f"Buchung für {selected_package} wurde erfasst. Die Instanz wird jetzt eingerichtet.", "success")
|
||||
return redirect(url_for("my_instance_management"))
|
||||
# Falls es KEIN AJAX-Request war:
|
||||
# Wir hängen den Parameter "wait_for_slug" an, damit die "my_instance_management"-Seite weiß,
|
||||
# dass sie sofort ein Lade-Overlay für diesen spezifischen Tenant öffnen muss.
|
||||
flash(f"Buchung für {selected_package} wurde erfasst. Deine Instanz wird jetzt automatisch eingerichtet.", "success")
|
||||
return redirect(url_for("my_instance_management", wait_for_slug=subdomain))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FATAL ERROR] In booking_payment Route:")
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* Loading State UI */
|
||||
/* NEU: Loading State & Fortschrittsbalken UI */
|
||||
#setup-loading-state,
|
||||
#setup-success-state {
|
||||
display: none;
|
||||
@@ -171,23 +171,44 @@
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 5px solid #e8f4f4;
|
||||
border-top-color: #166271;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 2rem;
|
||||
.progress-wrapper {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 2.5rem auto 1rem;
|
||||
}
|
||||
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
background: #e8f4f4;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, #166271, #1b8a3e);
|
||||
border-radius: 999px;
|
||||
transition: width 0.5s ease-out, background-color 0.3s;
|
||||
}
|
||||
|
||||
.progress-percent {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: #173a42;
|
||||
margin-top: 0.8rem;
|
||||
font-variant-numeric: tabular-nums; /* Sorgt dafür, dass die Zahlen beim Hochzählen nicht wackeln */
|
||||
}
|
||||
|
||||
.status-messages {
|
||||
font-size: 1.1rem;
|
||||
color: #49626b;
|
||||
color: #5e757d;
|
||||
font-weight: 600;
|
||||
height: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
/* Success State UI */
|
||||
@@ -274,7 +295,6 @@
|
||||
<span class="booking-review__label">Paket</span>
|
||||
<div class="booking-review__value">{{ package }}</div>
|
||||
</div>
|
||||
<!-- OPTIMIERT: Eindeutiger Hinweis auf Rechnungsstellung -->
|
||||
<div class="booking-review__card" style="border-left: 4px solid #1b8a3e;">
|
||||
<span class="booking-review__label">Zahlungsart</span>
|
||||
<div class="booking-review__value" style="color: #1b8a3e;">Kauf auf Rechnung</div>
|
||||
@@ -308,7 +328,6 @@
|
||||
<input id="contact_email" name="contact_email" type="email" required value="{{ form_data.contact_email }}">
|
||||
</div>
|
||||
|
||||
<!-- OPTIMIERT: Rechnungsdaten inkl. Kassenzeichen -->
|
||||
<div class="booking-review__section-title">2. Rechnungsadresse (Schulträger)</div>
|
||||
<div class="booking-review__field booking-review__field--full">
|
||||
<label for="billing_organization">Einrichtung / Amt *</label>
|
||||
@@ -353,7 +372,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fehleranzeige, falls der Server beim Submit meckert -->
|
||||
<div id="form-error-alert" style="display:none; color:#b02a37; margin-top:1rem; font-weight:bold;"></div>
|
||||
|
||||
<div class="booking-review__actions">
|
||||
@@ -367,10 +385,18 @@
|
||||
<!-- ========================================== -->
|
||||
<div id="setup-loading-state">
|
||||
<h2>Instanz wird bereitgestellt</h2>
|
||||
<p>Ihre Bestellung wird verarbeitet. Die Rechnung wird zeitgleich generiert.</p>
|
||||
<div class="spinner"></div>
|
||||
<p>Ihre Umgebung wird isoliert aufgesetzt und Zertifikate werden generiert.</p>
|
||||
|
||||
<!-- NEU: Fortschrittsbalken -->
|
||||
<div class="progress-wrapper">
|
||||
<div class="progress-bar-container">
|
||||
<div id="provisioning-progress-bar" class="progress-bar-fill"></div>
|
||||
</div>
|
||||
<div id="provisioning-percent" class="progress-percent">0%</div>
|
||||
</div>
|
||||
|
||||
<div class="status-messages" id="loading-status-text">Validierung der Daten...</div>
|
||||
<p style="margin-top: 2rem; color: #5e757d; font-size: 0.9rem;">Bitte schließen Sie dieses Fenster nicht. Dies dauert in der Regel 15-30 Sekunden.</p>
|
||||
<p style="margin-top: 1.5rem; color: #5e757d; font-size: 0.9rem;">Bitte schließen Sie dieses Fenster nicht. Dies dauert etwa 30 bis 45 Sekunden.</p>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
@@ -391,7 +417,6 @@
|
||||
<p><strong>Initial-Passwort:</strong> <code id="success-pass">-</code></p>
|
||||
</div>
|
||||
|
||||
<!-- OPTIMIERT: Rechnungs-Download, Weiterleitung und Drucken -->
|
||||
<div class="booking-review__actions" style="justify-content: center; margin-top: 2rem;">
|
||||
<a id="btn-download-invoice" href="#" class="btn success-btn" target="_blank" style="display: none;">
|
||||
📄 Rechnung (PDF) herunterladen
|
||||
@@ -416,7 +441,10 @@
|
||||
const form = document.getElementById('checkout-form');
|
||||
const errorAlert = document.getElementById('form-error-alert');
|
||||
const statusText = document.getElementById('loading-status-text');
|
||||
const spinner = document.querySelector('.spinner'); // Referenz auf den Ladekreis
|
||||
|
||||
// Fortschritts-Elemente
|
||||
const progressBar = document.getElementById('provisioning-progress-bar');
|
||||
const progressPercent = document.getElementById('provisioning-percent');
|
||||
|
||||
// Subdomain Live-Prüfung
|
||||
const slugInput = document.getElementById('tenant_slug');
|
||||
@@ -424,12 +452,8 @@
|
||||
const submitBtn = document.getElementById('booking_submit');
|
||||
let slugTimer = null;
|
||||
|
||||
// OPTIMIERT: Alles im 'input'-Event (fängt auch Maus-Paste und Autocomplete)
|
||||
slugInput.addEventListener('input', (e) => {
|
||||
// 1. Sanitization
|
||||
e.target.value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||
|
||||
// 2. Debounced API Check
|
||||
clearTimeout(slugTimer);
|
||||
slugTimer = setTimeout(() => checkSubdomain(e.target.value), 600);
|
||||
});
|
||||
@@ -495,12 +519,13 @@
|
||||
throw new Error(result.error || 'Es gab ein Problem bei der Buchung.');
|
||||
}
|
||||
|
||||
// === HIER WAR DER FEHLER: JETZT WIRD AUF LOADING GEWECHSELT UND GEPOLLT ===
|
||||
// Wechsel auf den Loading-Screen
|
||||
formState.style.display = 'none';
|
||||
loadingState.style.display = 'block';
|
||||
|
||||
// Polling starten, um auf Fertigstellung der Instanz zu warten
|
||||
startPolling(tenantSlug);
|
||||
// Backend-Schätzung für die Dauer nutzen, Default 45 Sekunden
|
||||
const estimatedSec = result.estimated_seconds || 45;
|
||||
startPolling(tenantSlug, estimatedSec);
|
||||
|
||||
} catch (error) {
|
||||
errorAlert.textContent = error.message;
|
||||
@@ -511,15 +536,17 @@
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Polling Logik (Warten auf den Server)
|
||||
// Polling Logik mit Fortschrittsbalken
|
||||
// ==========================================
|
||||
async function startPolling(slug) {
|
||||
async function startPolling(slug, estimatedSeconds) {
|
||||
// --- 1. Text-Nachrichten rotieren ---
|
||||
const statusMessages = [
|
||||
"Registriere Schuldatenbank...",
|
||||
"Erstelle Auftragsverarbeitungsvertrag...",
|
||||
"Generiere Rechnung für Schulträger...",
|
||||
"Konfiguriere Nginx & Zertifikate...",
|
||||
"Führe finale Health-Checks durch..."
|
||||
"Erstelle Systemkonfiguration...",
|
||||
"Registriere dedizierte Schuldatenbank...",
|
||||
"Generiere Rechnung & Auftragsverarbeitungsvertrag...",
|
||||
"Konfiguriere SSL-Zertifikate & Webserver...",
|
||||
"Führe finale Health-Checks durch...",
|
||||
"Fast geschafft..."
|
||||
];
|
||||
|
||||
let msgIndex = 0;
|
||||
@@ -528,17 +555,30 @@
|
||||
statusText.textContent = statusMessages[msgIndex];
|
||||
msgIndex++;
|
||||
}
|
||||
}, 3500);
|
||||
}, Math.floor((estimatedSeconds * 1000) / statusMessages.length));
|
||||
|
||||
// --- 2. Fortschrittsbalken Animation ---
|
||||
let elapsed = 0;
|
||||
let currentPercent = 0;
|
||||
const estimatedMs = estimatedSeconds * 1000;
|
||||
|
||||
const progressInterval = setInterval(() => {
|
||||
elapsed += 250; // Jede Viertelsekunde updaten für flüssigen Lauf
|
||||
// Kurve flacht gegen Ende ab (asymptotisch bis 99%)
|
||||
currentPercent = Math.min(99, Math.floor((elapsed / estimatedMs) * 100));
|
||||
progressBar.style.width = currentPercent + '%';
|
||||
progressPercent.textContent = currentPercent + '%';
|
||||
}, 250);
|
||||
|
||||
// --- 3. Backend-Polling ---
|
||||
let retryCount = 0;
|
||||
const maxRetries = 30; // OPTIMIERT: Max 90 Sekunden Polling (30 * 3s)
|
||||
const maxRetries = 40; // Max (40 * 3s) = 120 Sekunden
|
||||
|
||||
const checkStatus = async () => {
|
||||
retryCount++;
|
||||
|
||||
// Timeout-Abbruch
|
||||
if (retryCount > maxRetries) {
|
||||
handlePollingError("Zeitüberschreitung. Die Bereitstellung dauert ungewöhnlich lange. Sie werden per E-Mail informiert, sobald das System bereit ist.", msgInterval);
|
||||
handleError("Die Bereitstellung dauert ungewöhnlich lange. Sie erhalten die Zugangsdaten per E-Mail, sobald das System bereit ist.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -548,36 +588,50 @@
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'ready') {
|
||||
// SETUP FERTIG!
|
||||
if (data.status === 'ready' || data.status === 'active') {
|
||||
// SETUP ERFOLGREICH!
|
||||
clearInterval(msgInterval);
|
||||
showSuccessState(data.url, data.username, data.password, data.invoice_url);
|
||||
} else if (data.status === 'failed') {
|
||||
clearInterval(progressInterval);
|
||||
|
||||
// Schöne End-Animation
|
||||
progressBar.style.width = '100%';
|
||||
progressBar.style.background = '#1b8a3e'; // Direkt auf massiv-grün wechseln
|
||||
progressPercent.textContent = '100%';
|
||||
statusText.textContent = "Einrichtung erfolgreich abgeschlossen!";
|
||||
statusText.style.color = '#1b8a3e';
|
||||
|
||||
// Lass den Nutzer kurz die 100% sehen, bevor der Screen wechselt
|
||||
setTimeout(() => {
|
||||
showSuccessState(data.url, data.username, data.password, data.invoice_url);
|
||||
}, 1200);
|
||||
|
||||
} else if (data.status === 'failed' || data.status === 'error') {
|
||||
// SETUP FEHLGESCHLAGEN
|
||||
handlePollingError("Fehler bei der Bereitstellung. Bitte kontaktieren Sie unseren Support.", msgInterval);
|
||||
handleError("Fehler bei der Bereitstellung. Bitte kontaktieren Sie unseren Support.");
|
||||
} else {
|
||||
// SETUP LÄUFT NOCH -> in 3 Sekunden erneut prüfen
|
||||
setTimeout(checkStatus, 3000);
|
||||
}
|
||||
} catch (err) {
|
||||
// Bei Netzwerkfehler weiter versuchen, solange maxRetries nicht erreicht ist
|
||||
// Bei kurzem Netzwerk-Schluckauf einfach weiter pollig
|
||||
setTimeout(checkStatus, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// Starte den ersten Check nach 2 Sekunden
|
||||
setTimeout(checkStatus, 2000);
|
||||
}
|
||||
|
||||
// OPTIMIERT: Hilfsfunktion für Polling-Fehler (verhindert Sackgassen-UI)
|
||||
function handlePollingError(message, intervalId) {
|
||||
clearInterval(intervalId);
|
||||
statusText.textContent = message;
|
||||
statusText.style.color = '#b02a37';
|
||||
if (spinner) spinner.style.display = 'none'; // Spinner ausblenden
|
||||
|
||||
// Optional: Einen Zurück/Support-Button einfügen
|
||||
statusText.insertAdjacentHTML('afterend', '<br><a href="#" onclick="window.location.reload(); return false;" class="btn secondary" style="margin-top:1rem;">Seite neu laden</a>');
|
||||
function handleError(message) {
|
||||
clearInterval(msgInterval);
|
||||
clearInterval(progressInterval);
|
||||
statusText.textContent = message;
|
||||
statusText.style.color = '#b02a37';
|
||||
progressBar.style.background = '#b02a37'; // Balken wird rot
|
||||
|
||||
if (!document.getElementById('retry-btn')) {
|
||||
statusText.insertAdjacentHTML('afterend', '<br><button id="retry-btn" onclick="window.location.reload();" class="btn secondary" style="margin-top:1.5rem;">Seite neu laden</button>');
|
||||
}
|
||||
}
|
||||
|
||||
// Starte den ersten Status-Check nach 3 Sekunden
|
||||
setTimeout(checkStatus, 3000);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -593,7 +647,6 @@
|
||||
document.getElementById('success-pass').textContent = pass;
|
||||
document.getElementById('btn-go-to-tenant').href = url;
|
||||
|
||||
// Rechnungs-Button einblenden, falls URL vorhanden
|
||||
const invoiceBtn = document.getElementById('btn-download-invoice');
|
||||
if (invoiceUrl) {
|
||||
invoiceBtn.href = invoiceUrl;
|
||||
|
||||
Reference in New Issue
Block a user