better process for the dispalying of the process
This commit is contained in:
@@ -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 lä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';
|
||||
|
||||
Reference in New Issue
Block a user