better process for the dispalying of the process

This commit is contained in:
2026-08-26 21:56:10 +02:00
parent e30a9ac822
commit 3256134a39
+71 -88
View File
@@ -71,7 +71,6 @@
line-height: 1.65;
}
/* Formular Styles */
.booking-review__form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -163,7 +162,7 @@
margin-top: 1.5rem;
}
/* NEU: Loading State & Fortschrittsbalken UI */
/* Loading State UI (1:30 Min Process) */
#setup-loading-state,
#setup-success-state {
display: none;
@@ -173,13 +172,13 @@
.progress-wrapper {
width: 100%;
max-width: 480px;
max-width: 520px;
margin: 2.5rem auto 1rem;
}
.progress-bar-container {
width: 100%;
height: 16px;
height: 18px;
background: #e8f4f4;
border-radius: 999px;
overflow: hidden;
@@ -191,23 +190,23 @@
width: 0%;
background: linear-gradient(90deg, #166271, #1b8a3e);
border-radius: 999px;
transition: width 0.5s ease-out, background-color 0.3s;
transition: width 0.4s ease-out;
}
.progress-percent {
font-size: 1.5rem;
font-size: 1.6rem;
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 */
font-variant-numeric: tabular-nums;
}
.status-messages {
font-size: 1.1rem;
color: #5e757d;
font-size: 1.05rem;
color: #166271;
font-weight: 600;
height: 2rem;
margin-top: 1.5rem;
min-height: 2.5rem;
margin-top: 1.2rem;
transition: color 0.3s;
}
@@ -270,7 +269,6 @@
.booking-review__actions .btn { width: 100%; min-width: 0; }
}
/* Verstecke UI-Elemente beim Drucken */
@media print {
header, footer, nav, .booking-review__actions { display: none !important; }
body { background: white; }
@@ -283,9 +281,7 @@
<section class="booking-review">
<div class="booking-review__panel">
<!-- ========================================== -->
<!-- STATE 1: FORMULAR (Checkout) -->
<!-- ========================================== -->
<!-- STATE 1: FORMULAR (Checkout) -->
<div id="checkout-form-state">
<span class="booking-review__eyebrow">Buchung prüfen & abschließen</span>
<h1>{{ title }}</h1>
@@ -380,14 +376,11 @@
</form>
</div>
<!-- ========================================== -->
<!-- STATE 2: LOADING (Asynchrones Setup) -->
<!-- ========================================== -->
<!-- STATE 2: LOADING (Fixiert auf 1:30 Min. / 90s Ablauf) -->
<div id="setup-loading-state">
<h2>Instanz wird bereitgestellt</h2>
<p>Ihre Umgebung wird isoliert aufgesetzt und Zertifikate werden generiert.</p>
<p>Ihre isolierte Umgebung wird eingerichtet, SSL-Zertifikate ausgestellt und Datenbanken initialisiert.</p>
<!-- NEU: Fortschrittsbalken -->
<div class="progress-wrapper">
<div class="progress-bar-container">
<div id="provisioning-progress-bar" class="progress-bar-fill"></div>
@@ -395,17 +388,15 @@
<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: 1.5rem; color: #5e757d; font-size: 0.9rem;">Bitte schließen Sie dieses Fenster nicht. Dies dauert etwa 30 bis 45 Sekunden.</p>
<div class="status-messages" id="loading-status-text">Starte Tenant Watcher...</div>
<p style="margin-top: 1.5rem; color: #5e757d; font-size: 0.9rem;">Bitte lassen Sie dieses Fenster geöffnet. Die automatische Einrichtung dauert ca. 1:30 Minuten.</p>
</div>
<!-- ========================================== -->
<!-- STATE 3: SUCCESS (Bereit zur Nutzung) -->
<!-- ========================================== -->
<!-- STATE 3: SUCCESS (Bereit zur Nutzung) -->
<div id="setup-success-state">
<div class="success-icon"></div>
<h2>Ihre Schule ist online!</h2>
<p>Das Setup wurde erfolgreich abgeschlossen. Ihre Rechnung wurde an die angegebene E-Mail-Adresse versandt.</p>
<p>Das Setup wurde erfolgreich abgeschlossen. Ihre Rechnung sowie Zugangsdaten wurden zusätzlich per E-Mail versandt.</p>
<div class="warning-text">
Aus Datenschutzgründen (AVV) müssen Sie das generierte Passwort nach dem ersten Login zwingend ändern!
@@ -413,8 +404,8 @@
<div class="credentials-box">
<p><strong>Login-URL:</strong> <br><a id="success-url" href="#" target="_blank" style="color: #66d9e8;">-</a></p>
<p><strong>Benutzername (Admin):</strong> <code id="success-user">-</code></p>
<p><strong>Initial-Passwort:</strong> <code id="success-pass">-</code></p>
<p><strong>Benutzername (Admin):</strong> <code id="success-user">admin</code></p>
<p><strong>Initial-Passwort:</strong> <code id="success-pass">admin123</code></p>
</div>
<div class="booking-review__actions" style="justify-content: center; margin-top: 2rem;">
@@ -433,7 +424,6 @@
<script>
(() => {
// DOM Elemente
const formState = document.getElementById('checkout-form-state');
const loadingState = document.getElementById('setup-loading-state');
const successState = document.getElementById('setup-success-state');
@@ -442,11 +432,9 @@
const errorAlert = document.getElementById('form-error-alert');
const statusText = document.getElementById('loading-status-text');
// 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');
const feedback = document.getElementById('tenant_slug_feedback');
const submitBtn = document.getElementById('booking_submit');
@@ -484,9 +472,6 @@
}
}
// ==========================================
// AJAX Form Submit & Transition to Loading
// ==========================================
form.addEventListener('submit', async (e) => {
e.preventDefault();
errorAlert.style.display = 'none';
@@ -519,101 +504,102 @@
throw new Error(result.error || 'Es gab ein Problem bei der Buchung.');
}
// Wechsel auf den Loading-Screen
formState.style.display = 'none';
loadingState.style.display = 'block';
// Backend-Schätzung für die Dauer nutzen, Default 45 Sekunden
const estimatedSec = result.estimated_seconds || 45;
// Fixiert auf genau 90 Sekunden (1:30 Min.)
const estimatedSec = result.estimated_seconds || 90;
startPolling(tenantSlug, estimatedSec);
} catch (error) {
errorAlert.textContent = error.message;
errorAlert.style.display = 'block';
submitBtn.disabled = false;
submitBtn.textContent = originalBtnText; // Button zurücksetzen
submitBtn.textContent = originalBtnText;
}
});
// ==========================================
// Polling Logik mit Fortschrittsbalken
// ==========================================
async function startPolling(slug, estimatedSeconds) {
// --- 1. Text-Nachrichten rotieren ---
const statusMessages = [
"Erstelle Systemkonfiguration...",
"Registriere dedizierte Schuldatenbank...",
"Generiere Rechnung & Auftragsverarbeitungsvertrag...",
"Konfiguriere SSL-Zertifikate & Webserver...",
"Führe finale Health-Checks durch...",
"Fast geschafft..."
function startPolling(slug, estimatedSeconds) {
// Exakt auf die Prozessschritte aus dem Log abgestimmte Statusmeldungen für den 90s Ablauf
const statusSteps = [
{ time: 0, msg: "Trigger verarbeitet: Starte Provisionierung für " + slug + "..." },
{ time: 8, msg: "Freier Netzwerk-Port wird zugewiesen (Port 10004)..." },
{ time: 18, msg: "Certbot & Let's Encrypt: SSL-Zertifikat wird angefordert..." },
{ time: 32, msg: "SSL-Zertifikat ausgestellt & Nginx VirtualHost konfiguriert..." },
{ time: 42, msg: "Starte Anwendungs-Container (Docker Stack / MongoDB / Redis)..." },
{ time: 54, msg: "Datenbank initialisiert. Standard-Admin wird angelegt..." },
{ time: 64, msg: "Konfiguriere gebuchte Module (Bibliothek = ON)..." },
{ time: 74, msg: "Führe automatischen Systemneustart (restart.sh) durch..." },
{ time: 84, msg: "Bereinige verwaiste App-Container & schließe Setup ab..." }
];
let msgIndex = 0;
const msgInterval = setInterval(() => {
if (msgIndex < statusMessages.length) {
statusText.textContent = statusMessages[msgIndex];
msgIndex++;
}
}, Math.floor((estimatedSeconds * 1000) / statusMessages.length));
// --- 2. Fortschrittsbalken Animation ---
let elapsed = 0;
let currentPercent = 0;
const estimatedMs = estimatedSeconds * 1000;
let msgIdx = 0;
statusText.textContent = statusSteps[0].msg;
// Zeit-getriebenes Update der Status-Texte
const msgInterval = setInterval(() => {
msgIdx++;
if (msgIdx < statusSteps.length) {
statusText.textContent = statusSteps[msgIdx].msg;
} else {
clearInterval(msgInterval);
}
}, (estimatedSeconds * 1000) / statusSteps.length);
// Fortschrittsbalken-Animation (0% -> 99% gleichmäßig verteilt über 90 Sekunden)
let elapsedMs = 0;
const totalMs = 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));
elapsedMs += 200;
let currentPercent = Math.min(99, Math.floor((elapsedMs / totalMs) * 100));
progressBar.style.width = currentPercent + '%';
progressPercent.textContent = currentPercent + '%';
}, 250);
}, 200);
// --- 3. Backend-Polling ---
// Polling-Logik zur Abfrage beim Backend
let retryCount = 0;
const maxRetries = 40; // Max (40 * 3s) = 120 Sekunden
const maxRetries = 60; // 60 * 3s = 180s maximale Pufferzeit
const checkStatus = async () => {
retryCount++;
if (retryCount > maxRetries) {
handleError("Die Bereitstellung dauert ungewöhnlich lange. Sie erhalten die Zugangsdaten per E-Mail, sobald das System bereit ist.");
handleError("Die Bereitstellung dauert nger als erwartet. Sie erhalten eine E-Mail, sobald die Instanz bereit ist.");
return;
}
try {
const res = await fetch(`/instance_request/status?slug=${slug}`);
if (!res.ok) throw new Error("Network response was not ok");
if (!res.ok) throw new Error("Netzwerkfehler");
const data = await res.json();
if (data.status === 'ready' || data.status === 'active') {
// SETUP ERFOLGREICH!
clearInterval(msgInterval);
clearInterval(progressInterval);
// Schöne End-Animation
// Abschlussanimation auf 100%
progressBar.style.width = '100%';
progressBar.style.background = '#1b8a3e'; // Direkt auf massiv-grün wechseln
progressBar.style.background = '#1b8a3e';
progressPercent.textContent = '100%';
statusText.textContent = "Einrichtung erfolgreich abgeschlossen!";
statusText.textContent = "[SUCCESS] Tenant bereitgestellt: " + slug + " (Port: 10004)";
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);
showSuccessState(
data.url || `https://${slug}.invario-software.de`,
data.username || "admin",
data.password || "admin123",
data.invoice_url
);
}, 1000);
} else if (data.status === 'failed' || data.status === 'error') {
// SETUP FEHLGESCHLAGEN
handleError("Fehler bei der Bereitstellung. Bitte kontaktieren Sie unseren Support.");
handleError("Fehler bei der Bereitstellung. Bitte kontaktieren Sie den Support.");
} else {
// SETUP LÄUFT NOCH -> in 3 Sekunden erneut prüfen
setTimeout(checkStatus, 3000);
}
} catch (err) {
// Bei kurzem Netzwerk-Schluckauf einfach weiter pollig
setTimeout(checkStatus, 3000);
}
};
@@ -623,20 +609,17 @@
clearInterval(progressInterval);
statusText.textContent = message;
statusText.style.color = '#b02a37';
progressBar.style.background = '#b02a37'; // Balken wird rot
progressBar.style.background = '#b02a37';
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);
// Erstes Polling nach 5 Sekunden starten
setTimeout(checkStatus, 5000);
}
// ==========================================
// Erfolgs-Bildschirm anzeigen
// ==========================================
function showSuccessState(url, user, pass, invoiceUrl) {
loadingState.style.display = 'none';
successState.style.display = 'block';