feat: Refactor provisioning to instance requests and enhance subdomain availability check
This commit is contained in:
+100
-70
@@ -2076,10 +2076,22 @@ def booking_payment():
|
|||||||
# Do NOT create an invoice directly. Instead request provisioning via server_steering
|
# Do NOT create an invoice directly. Instead request provisioning via server_steering
|
||||||
booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
||||||
|
|
||||||
# Build a subdomain from the school name (fallback to username) and ensure uniqueness
|
# Build a subdomain from the school name (fallback to username)
|
||||||
base = _slugify_subdomain(form_data.get("school_name") or session.get("username") or booking_number)
|
# Use lowercase, sanitized school name and check for existing instances.
|
||||||
subdomain = f"{base}-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
raw_base = form_data.get("school_name") or session.get("username") or booking_number
|
||||||
subdomain = subdomain[:63]
|
base = _slugify_subdomain(raw_base).lower()[:51]
|
||||||
|
|
||||||
|
# Check existing instances and require the user to choose a different name on conflict
|
||||||
|
try:
|
||||||
|
existing = steering.instace.list() or []
|
||||||
|
except Exception:
|
||||||
|
existing = []
|
||||||
|
|
||||||
|
if base in existing:
|
||||||
|
flash("Die gewünschte Subdomain (aus dem Schulnamen) existiert bereits. Bitte wähle einen anderen Schulnamen.", "error")
|
||||||
|
return render_template("schnell_buchung.html", **review_data)
|
||||||
|
|
||||||
|
subdomain = base[:63]
|
||||||
|
|
||||||
provision_status = "not_started"
|
provision_status = "not_started"
|
||||||
provision_port = None
|
provision_port = None
|
||||||
@@ -2141,13 +2153,13 @@ def booking_payment():
|
|||||||
"source": "booking_payment",
|
"source": "booking_payment",
|
||||||
}
|
}
|
||||||
|
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov_col.insert_one(provision_payload)
|
req_col.insert_one(provision_payload)
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
except PyMongoError:
|
except PyMongoError:
|
||||||
flash("Buchungsanfrage konnte nicht gesendet werden.", "error")
|
flash("Buchungsanfrage konnte nicht gesendet werden.", "error")
|
||||||
@@ -2169,20 +2181,20 @@ def booking_payment():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/provision/delete', methods=['POST'])
|
@app.route('/instance_request/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def provision_delete():
|
def instance_request_delete():
|
||||||
prov_id = _sanitize_text(request.form.get("prov_id") or "", 64)
|
prov_id = _sanitize_text(request.form.get("prov_id") or "", 64)
|
||||||
if not prov_id:
|
if not prov_id:
|
||||||
flash("Ungültige Anfrage.", "error")
|
flash("Ungültige Anfrage.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
|
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov = prov_col.find_one({"_id": ObjectId(prov_id)})
|
prov = req_col.find_one({"_id": ObjectId(prov_id)})
|
||||||
if not prov:
|
if not prov:
|
||||||
flash("Provisionierungsanfrage nicht gefunden.", "error")
|
flash("Instanz-Anfrage nicht gefunden.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
|
|
||||||
# Only owner or admin can delete
|
# Only owner or admin can delete
|
||||||
@@ -2190,13 +2202,13 @@ def provision_delete():
|
|||||||
flash("Keine Berechtigung zum Löschen dieser Anfrage.", "error")
|
flash("Keine Berechtigung zum Löschen dieser Anfrage.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
|
|
||||||
prov_col.delete_one({"_id": prov.get("_id")})
|
req_col.delete_one({"_id": prov.get("_id")})
|
||||||
flash("Provisionierungsanfrage gelöscht.", "success")
|
flash("Instanz-Anfrage gelöscht.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("Provisionierungsanfrage konnte nicht gelöscht werden.", "error")
|
flash("Instanz-Anfrage konnte nicht gelöscht werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
|
|
||||||
@@ -2776,19 +2788,19 @@ def my_invoices():
|
|||||||
if client:
|
if client:
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
# Load provisioning requests for this user
|
# Load instance requests for this user
|
||||||
provision_requests = []
|
instance_requests = []
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
provision_requests = list(prov_col.find({"username": session.get("username")}).sort("created_at", -1))
|
instance_requests = list(req_col.find({"username": session.get("username")}).sort("created_at", -1))
|
||||||
for item in provision_requests:
|
for item in instance_requests:
|
||||||
item["id"] = str(item.get("_id"))
|
item["id"] = str(item.get("_id"))
|
||||||
except PyMongoError:
|
except PyMongoError:
|
||||||
flash("Provisionierungsanfragen konnten nicht geladen werden.", "error")
|
flash("Instanz-Anfragen konnten nicht geladen werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if 'prov_client' in locals() and prov_client:
|
if 'req_client' in locals() and req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
return render_template("my_invoices.html", invoices=invoices, provision_requests=provision_requests)
|
return render_template("my_invoices.html", invoices=invoices, instance_requests=instance_requests)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/my/instance', methods=['GET', 'POST'])
|
@app.route('/my/instance', methods=['GET', 'POST'])
|
||||||
@@ -3140,13 +3152,13 @@ def admin_invoices():
|
|||||||
flash("Rechnung angelegt.", "success")
|
flash("Rechnung angelegt.", "success")
|
||||||
|
|
||||||
elif action == "create_from_provision" and prov_id:
|
elif action == "create_from_provision" and prov_id:
|
||||||
# Create an invoice from a provision request
|
# Create an invoice from an instance request
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov = prov_col.find_one({"_id": ObjectId(prov_id)})
|
prov = req_col.find_one({"_id": ObjectId(prov_id)})
|
||||||
if not prov:
|
if not prov:
|
||||||
flash("Provisionierungsanfrage nicht gefunden.", "error")
|
flash("Instanz-Anfrage nicht gefunden.", "error")
|
||||||
return redirect(url_for("admin_invoices"))
|
return redirect(url_for("admin_invoices"))
|
||||||
|
|
||||||
username = prov.get("username") or ""
|
username = prov.get("username") or ""
|
||||||
@@ -3176,35 +3188,35 @@ def admin_invoices():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
prov_col.update_one({"_id": prov.get("_id")}, {"$set": {"provision_status": "invoice_created", "updated_at": _utc_now_iso()}})
|
req_col.update_one({"_id": prov.get("_id")}, {"$set": {"provision_status": "invoice_created", "updated_at": _utc_now_iso()}})
|
||||||
flash("Rechnung aus Provisionierungsanfrage erstellt.", "success")
|
flash("Rechnung aus Instanz-Anfrage erstellt.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("Fehler beim Erstellen der Rechnung aus Provisionierungsanfrage.", "error")
|
flash("Fehler beim Erstellen der Rechnung aus Instanz-Anfrage.", "error")
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
elif action == "update_provision" and prov_id:
|
elif action == "update_provision" and prov_id:
|
||||||
# Update provision request status (accept/reject/complete)
|
# Update provision request status (accept/reject/complete)
|
||||||
new_status = _sanitize_text(request.form.get("prov_status") or "", 40)
|
new_status = _sanitize_text(request.form.get("prov_status") or "", 40)
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": new_status, "updated_at": _utc_now_iso()}})
|
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": new_status, "updated_at": _utc_now_iso()}})
|
||||||
flash("Provisionierungsanfrage aktualisiert.", "success")
|
flash("Instanz-Anfrage aktualisiert.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("Provisionierungsanfrage konnte nicht aktualisiert werden.", "error")
|
flash("Instanz-Anfrage konnte nicht aktualisiert werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
elif action in {"run_provision", "retry_provision", "configure_provision"} and prov_id:
|
elif action in {"run_provision", "retry_provision", "configure_provision"} and prov_id:
|
||||||
# Admin-triggered provisioning actions: start/retry/configure
|
# Admin-triggered provisioning actions: start/retry/configure
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov = prov_col.find_one({"_id": ObjectId(prov_id)})
|
prov = req_col.find_one({"_id": ObjectId(prov_id)})
|
||||||
if not prov:
|
if not prov:
|
||||||
flash("Provisionierungsanfrage nicht gefunden.", "error")
|
flash("Instanz-Anfrage nicht gefunden.", "error")
|
||||||
return redirect(url_for("admin_invoices"))
|
return redirect(url_for("admin_invoices"))
|
||||||
|
|
||||||
subdomain = prov.get("subdomain") or _slugify_subdomain(prov.get("booking_data", {}).get("school_name") or prov.get("username") or prov.get("booking_number") or "prov")
|
subdomain = prov.get("subdomain") or _slugify_subdomain(prov.get("booking_data", {}).get("school_name") or prov.get("username") or prov.get("booking_number") or "prov")
|
||||||
@@ -3243,25 +3255,25 @@ def admin_invoices():
|
|||||||
provision_config_ok = False
|
provision_config_ok = False
|
||||||
provision_error = str(e)
|
provision_error = str(e)
|
||||||
|
|
||||||
prov_col.update_one({"_id": prov.get("_id")}, {"$set": {"provision_status": provision_status, "provision_port": provision_port or "", "provision_config_ok": provision_config_ok, "provision_error": provision_error, "updated_at": _utc_now_iso()}})
|
req_col.update_one({"_id": prov.get("_id")}, {"$set": {"provision_status": provision_status, "provision_port": provision_port or "", "provision_config_ok": provision_config_ok, "provision_error": provision_error, "updated_at": _utc_now_iso()}})
|
||||||
flash("Provisionierungsaktion ausgeführt.", "success")
|
flash("Provisionierungsaktion ausgeführt.", "success")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash(f"Provisionierungsaktion fehlgeschlagen: {str(e)}", "error")
|
flash(f"Provisionierungsaktion fehlgeschlagen: {str(e)}", "error")
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
elif action == "delete_provision" and prov_id:
|
elif action == "delete_instance_request" and prov_id:
|
||||||
prov_client = None
|
req_client = None
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
prov_col.delete_one({"_id": ObjectId(prov_id)})
|
req_col.delete_one({"_id": ObjectId(prov_id)})
|
||||||
flash("Provisionierungsanfrage gelöscht.", "success")
|
flash("Instanz-Anfrage gelöscht.", "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("Provisionierungsanfrage konnte nicht gelöscht werden.", "error")
|
flash("Instanz-Anfrage konnte nicht gelöscht werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if prov_client:
|
if req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
elif action == "update" and invoice_id:
|
elif action == "update" and invoice_id:
|
||||||
amount_text = _sanitize_text(request.form.get("amount_eur") or "0", 20)
|
amount_text = _sanitize_text(request.form.get("amount_eur") or "0", 20)
|
||||||
@@ -3315,21 +3327,21 @@ def admin_invoices():
|
|||||||
if client:
|
if client:
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
# Load provisioning requests for admin review
|
# Load instance requests for admin review
|
||||||
provision_requests = []
|
instance_requests = []
|
||||||
try:
|
try:
|
||||||
prov_client, prov_col = _get_collection("provision_requests")
|
req_client, req_col = _get_collection("instance_requests")
|
||||||
provision_requests = list(prov_col.find().sort("created_at", -1))
|
instance_requests = list(req_col.find().sort("created_at", -1))
|
||||||
for item in provision_requests:
|
for item in instance_requests:
|
||||||
item["id"] = str(item.get("_id"))
|
item["id"] = str(item.get("_id"))
|
||||||
except PyMongoError:
|
except PyMongoError:
|
||||||
flash("Provisionierungsanfragen konnten nicht geladen werden.", "error")
|
flash("Instanz-Anfragen konnten nicht geladen werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if 'prov_client' in locals() and prov_client:
|
if 'req_client' in locals() and req_client:
|
||||||
prov_client.close()
|
req_client.close()
|
||||||
|
|
||||||
users = _list_users_for_admin()
|
users = _list_users_for_admin()
|
||||||
return render_template("admin_invoices.html", invoices=invoices, users=users, provision_requests=provision_requests)
|
return render_template("admin_invoices.html", invoices=invoices, users=users, provision_requests=instance_requests)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/datenschutz')
|
@app.route('/datenschutz')
|
||||||
@@ -3337,6 +3349,24 @@ def datenschutz():
|
|||||||
return render_template("datenschutz.html")
|
return render_template("datenschutz.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/instance_request/check_subdomain', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def check_subdomain():
|
||||||
|
name = (request.values.get('school_name') or '').strip()
|
||||||
|
slug = _slugify_subdomain(name)
|
||||||
|
if not slug:
|
||||||
|
return jsonify({"available": False, "slug": "", "message": "Ungültiger Name für Subdomain."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
existing = steering.instace.list() or []
|
||||||
|
except Exception:
|
||||||
|
existing = []
|
||||||
|
|
||||||
|
if slug in existing:
|
||||||
|
return jsonify({"available": False, "slug": slug, "message": "Subdomain bereits vergeben."})
|
||||||
|
return jsonify({"available": True, "slug": slug, "message": "Subdomain verfügbar."})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/impressum')
|
@app.route('/impressum')
|
||||||
def impressum():
|
def impressum():
|
||||||
return render_template("impressum.html")
|
return render_template("impressum.html")
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if provision_requests %}
|
{% if provision_requests %}
|
||||||
<h2>Provisionierungsanfragen</h2>
|
<h2>Instanz-Anfragen</h2>
|
||||||
{% for prov in provision_requests %}
|
{% for prov in provision_requests %}
|
||||||
<article class="entry">
|
<article class="entry">
|
||||||
<h3>{{ prov.booking_number or prov.booking_number }}</h3>
|
<h3>{{ prov.booking_number or prov.booking_number }}</h3>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
<button type="submit">Konfiguration ausführen</button>
|
<button type="submit">Konfiguration ausführen</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" style="display:inline-block; margin-right:0.6rem;">
|
<form method="post" style="display:inline-block; margin-right:0.6rem;">
|
||||||
<input type="hidden" name="action" value="delete_provision">
|
<input type="hidden" name="action" value="delete_instance_request">
|
||||||
<input type="hidden" name="prov_id" value="{{ prov.id }}">
|
<input type="hidden" name="prov_id" value="{{ prov.id }}">
|
||||||
<button type="submit" class="danger">Löschen</button>
|
<button type="submit" class="danger">Löschen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2>Provisionierungsanfragen</h2>
|
<h2>Instanz-Anfragen</h2>
|
||||||
<p>Hier siehst du den Status deiner angeforderten Instanzen/Buchungen, die vom Admin geprüft werden.</p>
|
<p>Hier siehst du den Status deiner angeforderten Instanzen/Buchungen, die vom Admin geprüft werden.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for prov in provision_requests %}
|
{% for prov in instance_requests %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ prov.booking_number or prov.booking_number }}</td>
|
<td>{{ prov.booking_number or prov.booking_number }}</td>
|
||||||
<td>{{ prov.package_label or prov.package }}</td>
|
<td>{{ prov.package_label or prov.package }}</td>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<td>{{ '%.2f'|format(prov.amount_eur or 0) }} EUR</td>
|
<td>{{ '%.2f'|format(prov.amount_eur or 0) }} EUR</td>
|
||||||
<td>
|
<td>
|
||||||
{{ prov.provision_status or 'not_started' }}
|
{{ prov.provision_status or 'not_started' }}
|
||||||
<form method="post" action="{{ url_for('provision_delete') }}" style="display:inline-block; margin-left:0.6rem;">
|
<form method="post" action="{{ url_for('instance_request_delete') }}" style="display:inline-block; margin-left:0.6rem;">
|
||||||
<input type="hidden" name="prov_id" value="{{ prov.id }}">
|
<input type="hidden" name="prov_id" value="{{ prov.id }}">
|
||||||
<button type="submit" class="danger">Löschen</button>
|
<button type="submit" class="danger">Löschen</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -208,6 +208,7 @@
|
|||||||
<div class="booking-review__field">
|
<div class="booking-review__field">
|
||||||
<label for="school_name">Schule/Einrichtung *</label>
|
<label for="school_name">Schule/Einrichtung *</label>
|
||||||
<input id="school_name" name="school_name" type="text" required maxlength="160" value="{{ form_data.school_name }}">
|
<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>
|
||||||
<div class="booking-review__field">
|
<div class="booking-review__field">
|
||||||
<label for="contact_email">E-Mail *</label>
|
<label for="contact_email">E-Mail *</label>
|
||||||
@@ -242,10 +243,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="booking-review__actions">
|
<div class="booking-review__actions">
|
||||||
<button class="btn" type="submit">{{ confirm_label }}</button>
|
<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>
|
<a class="btn secondary" href="{{ url_for('preise') }}">Zurück zu den Preisen</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const input = document.getElementById('school_name');
|
||||||
|
const feedback = document.getElementById('school_name_feedback');
|
||||||
|
const submit = document.getElementById('booking_submit');
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
function setFeedback(text, ok) {
|
||||||
|
feedback.textContent = text;
|
||||||
|
feedback.style.color = ok ? '#1b8a3e' : '#b02a37';
|
||||||
|
submit.disabled = !ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function check(name) {
|
||||||
|
if (!name || name.trim().length < 3) {
|
||||||
|
feedback.textContent = '';
|
||||||
|
submit.disabled = false;
|
||||||
|
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 j = await res.json();
|
||||||
|
setFeedback(j.message || (j.available ? 'verfügbar' : 'nicht verfügbar'), !!j.available);
|
||||||
|
} catch (e) {
|
||||||
|
setFeedback('Prüfung fehlgeschlagen (Netzwerk).', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener('input', (e) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
const val = e.target.value;
|
||||||
|
timer = setTimeout(() => check(val), 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
// initial check on load
|
||||||
|
if (input.value && input.value.trim()) {
|
||||||
|
check(input.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user