Slight changes with the checkout system
This commit is contained in:
@@ -3406,6 +3406,26 @@ def logout():
|
||||
flash('Logged out successfully', 'info')
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/booking_payment_async', methods=['POST'])
|
||||
def booking_payment_async():
|
||||
# ... Speichere Daten in DB ...
|
||||
# ... Starte das manage-tenants.sh Skript ASYNCHRON (z.B. Celery) ...
|
||||
return jsonify({"success": True, "tenant_slug": request.form['tenant_slug']}), 200
|
||||
|
||||
|
||||
@app.route('/instance_request/status')
|
||||
def tenant_status():
|
||||
slug = request.args.get('slug')
|
||||
# Prüfe in der Datenbank, ob das Skript für diesen Slug schon fertig ist
|
||||
if ist_fertig(slug):
|
||||
return jsonify({
|
||||
"status": "ready",
|
||||
"url": f"https://{slug}.invario-software.de",
|
||||
"username": "admin",
|
||||
"password": generiertes_passwort
|
||||
})
|
||||
else:
|
||||
return jsonify({"status": "pending"})
|
||||
|
||||
def main():
|
||||
app.run(host="0.0.0.0", port=4999, debug=False)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Buchung prüfen{% endblock %}
|
||||
{% block title %}Buchung prüfen & abschließen{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
@@ -17,6 +17,8 @@
|
||||
border-radius: 26px;
|
||||
padding: clamp(1.4rem, 2.6vw, 2.2rem);
|
||||
box-shadow: 0 18px 44px rgba(18, 49, 54, 0.08);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.booking-review__eyebrow {
|
||||
@@ -31,11 +33,6 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.booking-review__lead {
|
||||
margin-top: 0.75rem;
|
||||
max-width: 66ch;
|
||||
}
|
||||
|
||||
.booking-review__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -54,7 +51,6 @@
|
||||
display: block;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
color: #5e757d;
|
||||
margin-bottom: 0.45rem;
|
||||
@@ -75,12 +71,24 @@
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* Formular Styles */
|
||||
.booking-review__form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.9rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.booking-review__section-title {
|
||||
grid-column: 1 / -1;
|
||||
font-size: 1.15rem;
|
||||
color: #166271;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
border-bottom: 2px solid #e8f4f4;
|
||||
padding-bottom: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.booking-review__field {
|
||||
display: flex;
|
||||
@@ -98,18 +106,34 @@
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
|
||||
.booking-review__field input,
|
||||
.booking-review__field textarea {
|
||||
.booking-review__field input {
|
||||
border: 1px solid #cedbdd;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
color: #173a42;
|
||||
padding: 0.75rem 0.82rem;
|
||||
}
|
||||
|
||||
.booking-review__field textarea {
|
||||
min-height: 110px;
|
||||
resize: vertical;
|
||||
|
||||
.input-group--subdomain {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-group--subdomain input {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-right: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.input-group--subdomain .domain-suffix {
|
||||
background: #e8f4f4;
|
||||
border: 1px solid #cedbdd;
|
||||
padding: 0.75rem 0.82rem;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
color: #5e757d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.booking-review__consent {
|
||||
@@ -130,13 +154,6 @@
|
||||
|
||||
.booking-review__consent input {
|
||||
margin-top: 0.2rem;
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.booking-review__consent a {
|
||||
color: #0a4c74;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.booking-review__actions {
|
||||
@@ -146,26 +163,78 @@
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.booking-review__actions .btn,
|
||||
.booking-review__actions a {
|
||||
width: auto;
|
||||
min-width: 190px;
|
||||
/* NEU: Loading State UI */
|
||||
#setup-loading-state,
|
||||
#setup-success-state {
|
||||
display: none;
|
||||
text-align: center;
|
||||
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;
|
||||
}
|
||||
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
|
||||
.status-messages {
|
||||
font-size: 1.1rem;
|
||||
color: #49626b;
|
||||
font-weight: 600;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
/* NEU: Success State UI */
|
||||
.success-icon {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: #1b8a3e;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
margin: 0 auto 1.5rem;
|
||||
}
|
||||
|
||||
.credentials-box {
|
||||
background: #173a42;
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin: 1.5rem auto;
|
||||
max-width: 400px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.credentials-box code {
|
||||
display: block;
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 0.5rem;
|
||||
border-radius: 6px;
|
||||
margin-top: 0.3rem;
|
||||
font-size: 1.1rem;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #b02a37;
|
||||
font-weight: 700;
|
||||
background: #f8d7da;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.booking-review__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.booking-review__form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.booking-review__actions .btn,
|
||||
.booking-review__actions a {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.booking-review__grid, .booking-review__form-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -173,123 +242,278 @@
|
||||
{% block content %}
|
||||
<section class="booking-review">
|
||||
<div class="booking-review__panel">
|
||||
<span class="booking-review__eyebrow">Buchung prüfen</span>
|
||||
<h1>{{ title }}</h1>
|
||||
<p class="booking-review__lead">{{ intro }}</p>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- STATE 1: FORMULAR (Checkout) -->
|
||||
<!-- ========================================== -->
|
||||
<div id="checkout-form-state">
|
||||
<span class="booking-review__eyebrow">Buchung prüfen & abschließen</span>
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<div class="booking-review__grid">
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Paket</span>
|
||||
<div class="booking-review__value">{{ package }}</div>
|
||||
</div>
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Preis</span>
|
||||
<div class="booking-review__value">{{ package_price }} € <br><small>Kauf auf Rechnung (30 Tage)</small></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="booking-review__grid" aria-label="Buchungsübersicht">
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Paket</span>
|
||||
<div class="booking-review__value">{{ package }}</div>
|
||||
</div>
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Preis</span>
|
||||
<div class="booking-review__value">{{ package_price }} €</div>
|
||||
</div>
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Angemeldet als</span>
|
||||
<div class="booking-review__value">{{ display_name }}</div>
|
||||
<div class="booking-review__note">
|
||||
Die Software-Instanz wird nach dem Absenden <strong>sofort für 30 Tage freigeschaltet</strong>. Die finale Laufzeit aktiviert sich automatisch nach Zahlungseingang durch Ihren Schulträger.
|
||||
</div>
|
||||
|
||||
<!-- Das Formular schickt die Daten an /booking_payment_async -->
|
||||
<form id="checkout-form" method="POST" action="{{ url_for('booking_payment_async') }}">
|
||||
<input type="hidden" name="package" value="{{ package_key }}">
|
||||
|
||||
<div class="booking-review__form-grid">
|
||||
<div class="booking-review__section-title">1. Angaben zur Schule & Ansprechpartner</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="school_name">Name der Schule *</label>
|
||||
<input id="school_name" name="school_name" type="text" required value="{{ form_data.school_name }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="school_number">Dienststellennummer *</label>
|
||||
<input id="school_number" name="school_number" type="text" required placeholder="Zur Legitimation" value="{{ form_data.school_number }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="contact_person">Ansprechpartner *</label>
|
||||
<input id="contact_person" name="contact_person" type="text" required value="{{ form_data.contact_person }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="contact_email">Dienstliche E-Mail *</label>
|
||||
<input id="contact_email" name="contact_email" type="email" required value="{{ form_data.contact_email }}">
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<input id="billing_organization" name="billing_organization" type="text" required value="{{ form_data.billing_organization }}">
|
||||
</div>
|
||||
<div class="booking-review__field booking-review__field--full">
|
||||
<label for="billing_street">Straße & Hausnummer *</label>
|
||||
<input id="billing_street" name="billing_street" type="text" required value="{{ form_data.billing_street }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="billing_zip">PLZ *</label>
|
||||
<input id="billing_zip" name="billing_zip" type="text" required value="{{ form_data.billing_zip }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="billing_city">Ort *</label>
|
||||
<input id="billing_city" name="billing_city" type="text" required value="{{ form_data.billing_city }}">
|
||||
</div>
|
||||
|
||||
<div class="booking-review__section-title">3. Technische Einrichtung</div>
|
||||
<div class="booking-review__field booking-review__field--full">
|
||||
<label for="tenant_slug">Gewünschte Schul-URL *</label>
|
||||
<div class="input-group--subdomain">
|
||||
<input id="tenant_slug" name="tenant_slug" type="text" required pattern="[a-z0-9-]+" placeholder="gymnasium-neustadt" value="{{ form_data.tenant_slug }}">
|
||||
<span class="domain-suffix">.euresoftware.de</span>
|
||||
</div>
|
||||
<div id="tenant_slug_feedback" style="margin-top:0.4rem; font-size:0.9rem; font-weight:600;"></div>
|
||||
</div>
|
||||
|
||||
<div class="booking-review__field booking-review__field--full booking-review__consent">
|
||||
<label for="agreements_accepted">
|
||||
<input id="agreements_accepted" name="agreements_accepted" type="checkbox" required>
|
||||
<span>Ich habe die Nutzungsbedingungen und den obligatorischen AVV gelesen und stimme diesen verbindlich zu.</span>
|
||||
</label>
|
||||
</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">
|
||||
<button id="booking_submit" class="btn" type="submit">Kostenpflichtig buchen & Instanz starten</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="booking-review__note">
|
||||
Bitte prüfen Sie die Angaben noch einmal. Wenn alles korrekt ist, senden sie ihre Informationen mit dem folgenden Button an uns, wir senden ihnen frühstmöglich eine Rechnung per Email.
|
||||
<!-- ========================================== -->
|
||||
<!-- STATE 2: LOADING (Asynchrones Setup) -->
|
||||
<!-- ========================================== -->
|
||||
<div id="setup-loading-state">
|
||||
<h2>Instanz wird bereitgestellt</h2>
|
||||
<p>Ihre Bestellung war erfolgreich. Die Rechnung wird gerade verschickt.</p>
|
||||
<div class="spinner"></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>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('booking_payment') }}">
|
||||
<input type="hidden" name="package" value="{{ package_key }}">
|
||||
<input type="hidden" name="flow" value="{{ flow }}">
|
||||
|
||||
<div class="booking-review__form-grid" aria-label="Pflichtdaten für Buchung">
|
||||
<div class="booking-review__field">
|
||||
<label for="contact_person">Ansprechpartner *</label>
|
||||
<input id="contact_person" name="contact_person" type="text" required maxlength="120" value="{{ form_data.contact_person }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="school_name">Schule/Einrichtung *</label>
|
||||
<input id="school_name" name="school_name" type="text" required maxlength="160" value="{{ form_data.school_name }}">
|
||||
<div id="school_name_feedback" style="margin-top:0.4rem; font-size:0.9rem; color:#6b6b6b;"></div>
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="contact_email">E-Mail *</label>
|
||||
<input id="contact_email" name="contact_email" type="email" required maxlength="160" value="{{ form_data.contact_email }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="contact_phone">Telefon</label>
|
||||
<input id="contact_phone" name="contact_phone" type="text" maxlength="60" value="{{ form_data.contact_phone }}">
|
||||
</div>
|
||||
<div class="booking-review__field booking-review__field--full">
|
||||
<label for="billing_street">Straße *</label>
|
||||
<input id="billing_street" name="billing_street" type="text" required maxlength="160" value="{{ form_data.billing_street }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="billing_zip">PLZ *</label>
|
||||
<input id="billing_zip" name="billing_zip" type="text" required maxlength="20" value="{{ form_data.billing_zip }}">
|
||||
</div>
|
||||
<div class="booking-review__field">
|
||||
<label for="billing_city">Ort *</label>
|
||||
<input id="billing_city" name="billing_city" type="text" required maxlength="120" value="{{ form_data.billing_city }}">
|
||||
</div>
|
||||
<div class="booking-review__field booking-review__field--full">
|
||||
<label for="notes">Zusatzinformationen</label>
|
||||
<textarea id="notes" name="notes" maxlength="600" placeholder="Optional: gewünschter Starttermin, besondere Anforderungen, Rückrufzeiten">{{ form_data.notes }}</textarea>
|
||||
</div>
|
||||
<div class="booking-review__field booking-review__field--full booking-review__consent">
|
||||
<label for="agreements_accepted">
|
||||
<input id="agreements_accepted" name="agreements_accepted" type="checkbox" value="yes" required {% if form_data.agreements_accepted %}checked{% endif %}>
|
||||
<span>Ich habe die <a href="{{ url_for('nutzungsbedingungen') }}" target="_blank" rel="noopener">Lizenzvereinbarung</a>, die <a href="{{ url_for('datenschutz') }}" target="_blank" rel="noopener">Datenschutzerklärung</a> und die DSGVO-Hinweise gelesen und akzeptiere sie.</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- ========================================== -->
|
||||
<!-- 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. Sie können sich nun einloggen.</p>
|
||||
|
||||
<div class="warning-text">
|
||||
Aus Datenschutzgründen (AVV) müssen Sie das generierte Passwort nach dem ersten Login zwingend ändern!
|
||||
</div>
|
||||
|
||||
<div class="booking-review__actions">
|
||||
<button id="booking_submit" class="btn" type="submit">{{ confirm_label }}</button>
|
||||
<a class="btn secondary" href="{{ url_for('preise') }}">Zurück zu den Preisen</a>
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<a id="btn-go-to-tenant" href="#" class="btn" target="_blank" style="width: 100%; max-width: 400px;">Jetzt zur Schulplattform wechseln</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const input = document.getElementById('school_name');
|
||||
const feedback = document.getElementById('school_name_feedback');
|
||||
const submit = document.getElementById('booking_submit');
|
||||
let timer = null;
|
||||
// DOM Elemente
|
||||
const formState = document.getElementById('checkout-form-state');
|
||||
const loadingState = document.getElementById('setup-loading-state');
|
||||
const successState = document.getElementById('setup-success-state');
|
||||
|
||||
const form = document.getElementById('checkout-form');
|
||||
const errorAlert = document.getElementById('form-error-alert');
|
||||
const statusText = document.getElementById('loading-status-text');
|
||||
|
||||
// Subdomain Live-Prüfung
|
||||
const slugInput = document.getElementById('tenant_slug');
|
||||
const feedback = document.getElementById('tenant_slug_feedback');
|
||||
const submitBtn = document.getElementById('booking_submit');
|
||||
let slugTimer = null;
|
||||
|
||||
function setFeedback(text, ok) {
|
||||
feedback.textContent = text;
|
||||
feedback.style.color = ok ? '#1b8a3e' : '#b02a37';
|
||||
submit.disabled = !ok;
|
||||
}
|
||||
slugInput.addEventListener('input', (e) => {
|
||||
e.target.value = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||
});
|
||||
|
||||
async function check(name) {
|
||||
if (!name || name.trim().length < 3) {
|
||||
feedback.textContent = '';
|
||||
submit.disabled = false;
|
||||
return;
|
||||
}
|
||||
slugInput.addEventListener('keyup', (e) => {
|
||||
clearTimeout(slugTimer);
|
||||
slugTimer = setTimeout(() => checkSubdomain(e.target.value), 600);
|
||||
});
|
||||
|
||||
async function checkSubdomain(name) {
|
||||
if (name.length < 3) return;
|
||||
try {
|
||||
const params = new URLSearchParams({ school_name: name });
|
||||
const res = await fetch(`/instance_request/check_subdomain?${params.toString()}`, { credentials: 'same-origin' });
|
||||
if (!res.ok) throw new Error('Netzwerkfehler');
|
||||
const res = await fetch(`/instance_request/check_subdomain?slug=${name}`);
|
||||
const j = await res.json();
|
||||
setFeedback(j.message || (j.available ? 'verfügbar' : 'nicht verfügbar'), !!j.available);
|
||||
if (j.available) {
|
||||
feedback.textContent = 'Adresse ist frei!';
|
||||
feedback.style.color = '#1b8a3e';
|
||||
submitBtn.disabled = false;
|
||||
} else {
|
||||
feedback.textContent = 'Adresse ist bereits vergeben.';
|
||||
feedback.style.color = '#b02a37';
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
} catch (e) {
|
||||
setFeedback('Prüfung fehlgeschlagen (Netzwerk).', false);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.addEventListener('input', (e) => {
|
||||
clearTimeout(timer);
|
||||
const val = e.target.value;
|
||||
timer = setTimeout(() => check(val), 500);
|
||||
});
|
||||
// ==========================================
|
||||
// AJAX Form Submit & Transition to Loading
|
||||
// ==========================================
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault(); // Verhindert das klassische Neuladen der Seite
|
||||
errorAlert.style.display = 'none';
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// initial check on load
|
||||
if (input.value && input.value.trim()) {
|
||||
check(input.value);
|
||||
const formData = new FormData(form);
|
||||
const tenantSlug = formData.get('tenant_slug');
|
||||
|
||||
try {
|
||||
// 1. Schicke Daten ans Backend (muss im Backend JSON zurückgeben!)
|
||||
const response = await fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Es gab ein Problem bei der Buchung.');
|
||||
}
|
||||
|
||||
// 2. Erfolg: Formular verstecken, Ladebildschirm zeigen
|
||||
formState.style.display = 'none';
|
||||
loadingState.style.display = 'block';
|
||||
|
||||
// 3. Starte Polling
|
||||
startPolling(tenantSlug);
|
||||
|
||||
} catch (error) {
|
||||
errorAlert.textContent = error.message;
|
||||
errorAlert.style.display = 'block';
|
||||
submitBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// Polling Logik (Warten auf den Server)
|
||||
// ==========================================
|
||||
async function startPolling(slug) {
|
||||
// Fake-Texte für den Nutzer, damit er weiß, dass das System arbeitet
|
||||
const statusMessages = [
|
||||
"Prüfe Serverkapazitäten...",
|
||||
"Richte Datenbank ein...",
|
||||
"Konfiguriere Nginx & Zertifikate...",
|
||||
"Führe finale Health-Checks durch..."
|
||||
];
|
||||
|
||||
let msgIndex = 0;
|
||||
const msgInterval = setInterval(() => {
|
||||
if (msgIndex < statusMessages.length) {
|
||||
statusText.textContent = statusMessages[msgIndex];
|
||||
msgIndex++;
|
||||
}
|
||||
}, 3500);
|
||||
|
||||
// Das eigentliche Polling zum Backend
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
// Backend-Route, die den Status des tenant_slug abfragt
|
||||
const res = await fetch(`/instance_request/status?slug=${slug}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status === 'ready') {
|
||||
// SETUP FERTIG!
|
||||
clearInterval(msgInterval);
|
||||
showSuccessState(data.url, data.username, data.password);
|
||||
} else if (data.status === 'failed') {
|
||||
// SETUP FEHLGESCHLAGEN
|
||||
clearInterval(msgInterval);
|
||||
statusText.textContent = "Fehler bei der Bereitstellung. Bitte Support kontaktieren.";
|
||||
statusText.style.color = '#b02a37';
|
||||
} else {
|
||||
// SETUP LÄUFT NOCH -> in 3 Sekunden erneut prüfen
|
||||
setTimeout(checkStatus, 3000);
|
||||
}
|
||||
} catch (err) {
|
||||
// Bei Netzwerkfehlern während des Pollings einfach weiter probieren
|
||||
setTimeout(checkStatus, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
// Starte den ersten Check nach 2 Sekunden
|
||||
setTimeout(checkStatus, 2000);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Erfolgs-Bildschirm anzeigen
|
||||
// ==========================================
|
||||
function showSuccessState(url, user, pass) {
|
||||
loadingState.style.display = 'none';
|
||||
successState.style.display = 'block';
|
||||
|
||||
document.getElementById('success-url').textContent = url;
|
||||
document.getElementById('success-url').href = url;
|
||||
document.getElementById('success-user').textContent = user;
|
||||
document.getElementById('success-pass').textContent = pass;
|
||||
document.getElementById('btn-go-to-tenant').href = url;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user