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