From 67249cb6333af8cfb9d3f57c917033316538d597 Mon Sep 17 00:00:00 2001 From: AIIrondev Date: Wed, 26 Aug 2026 14:57:14 +0200 Subject: [PATCH] changes to the deletion process --- Website/main.py | 264 ++----------- Website/templates/admin_instances.html | 515 ++++--------------------- 2 files changed, 102 insertions(+), 677 deletions(-) diff --git a/Website/main.py b/Website/main.py index 88e8688..52c696d 100644 --- a/Website/main.py +++ b/Website/main.py @@ -458,7 +458,8 @@ def _list_school_instances() -> list: rows = [] client = None try: - client, col = _get_collection("school_instances") + # Greife auf "instances" zu (passend zum Async-Provisioning) + client, col = _get_collection("instances") rows = list(col.find().sort([("updated_at", -1), ("created_at", -1)])) except PyMongoError: return [] @@ -472,17 +473,17 @@ def _list_school_instances() -> list: { "id": str(row.get("_id") or ""), "school_name": _sanitize_text(row.get("school_name") or "", 120), - "owner_username": _sanitize_text(row.get("owner_username") or "", 80), + "owner_username": _sanitize_text(row.get("owner_username") or row.get("username") or "", 80), "subdomain": _sanitize_text(row.get("subdomain") or "", 63), "domain": _sanitize_text(row.get("domain") or "", 190), - "https_port": int(row.get("https_port") or 0), + "https_port": int(row.get("https_port") or row.get("port") or 0), "instance_dir": _sanitize_text(row.get("instance_dir") or "", 300), "app_image_tag": _sanitize_text(row.get("app_image_tag") or "latest", 80), "library_enabled": bool(row.get("library_enabled", False)), "status": _sanitize_text(row.get("status") or "Unbekannt", 40), "nginx_status": _sanitize_text(row.get("nginx_status") or "unbekannt", 80), "last_message": _sanitize_text(row.get("last_message") or "", 500), - "updated_at": row.get("updated_at") or "", + "updated_at": row.get("updated_at") or row.get("created_at") or "", } ) return normalized @@ -2580,237 +2581,16 @@ def instance_request_delete(): -@app.route('/admin/instances', methods=['GET', 'POST']) +@app.route('/admin/instances', methods=['GET']) @admin_required def admin_instances(): - version_options = _parse_instance_version_options() - available_users = _list_available_instance_users() - - if request.method == 'POST': - action = _sanitize_text(request.form.get("action") or "create", 20).lower() - posted_subdomain = _sanitize_text(request.form.get("subdomain") or "", 120) - school_name = _sanitize_text(request.form.get("school_name") or "", 120) - raw_subdomain = _sanitize_text(request.form.get("subdomain") or "", 120) - owner_username = _sanitize_text(request.form.get("owner_username") or "", 80) - app_image_tag = _sanitize_text(request.form.get("app_image_tag") or "latest", 80) - library_enabled = (request.form.get("library_enabled") or "").strip().lower() in {"1", "on", "true", "yes"} - subdomain = _slugify_subdomain(posted_subdomain or raw_subdomain or school_name) - - if action == "reload_nginx": - reload_ok, reload_message = _reload_host_nginx() - if not reload_ok: - lowered = (reload_message or "").lower() - if "nicht gefunden" in lowered or "not found" in lowered or "kein nginx-reload-befehl" in lowered: - flash("Host-Nginx kann aus dem Website-Container nicht direkt neu geladen werden.", "error") - flash(_host_reload_hint(), "info") - flash("Nach erfolgreichem Host-Reload bitte 'Reload auf Host bestätigt' ausführen.", "info") - else: - flash(reload_message or "Nginx-Reload fehlgeschlagen.", "error") - return redirect(url_for("admin_instances")) - - updated_rows = _promote_manual_nginx_status( - "Nginx erfolgreich neu geladen (Admin-Aktion)." - ) - flash("Nginx wurde erfolgreich neu geladen.", "success") - if updated_rows > 0: - flash(f"{updated_rows} Instanz(en) von manual_required auf ok gesetzt.", "info") - else: - flash("Keine Instanz mit nginx_status=manual_required gefunden.", "info") - if reload_message: - flash(reload_message, "info") - return redirect(url_for("admin_instances")) - - if action == "confirm_nginx_reload": - updated_rows = _promote_manual_nginx_status( - "Host-Nginx manuell neu geladen und in Admin bestätigt." - ) - if updated_rows > 0: - flash(f"{updated_rows} Instanz(en) von manual_required auf ok gesetzt.", "success") - else: - flash("Keine Instanz mit nginx_status=manual_required gefunden.", "info") - return redirect(url_for("admin_instances")) - - if action == "delete": - if not _is_valid_subdomain(subdomain): - flash("Ungültige Subdomain für Löschung.", "error") - return redirect(url_for("admin_instances")) - - existing_record = _get_school_instance_by_subdomain(subdomain) - target_dir = _instance_dir_path(subdomain) - dir_exists = bool(target_dir and os.path.isdir(target_dir)) - if not existing_record and not dir_exists: - flash("Instanz nicht gefunden.", "error") - return redirect(url_for("admin_instances")) - - delete_ok, delete_message = _delete_instance_stack(subdomain) - if not delete_ok: - flash(delete_message or "Instanz konnte nicht gelöscht werden.", "error") - return redirect(url_for("admin_instances")) - - if existing_record and not _delete_school_instance(subdomain): - flash( - "Instanz wurde technisch gelöscht, aber der Datenbankeintrag konnte nicht entfernt werden.", - "error", - ) - return redirect(url_for("admin_instances")) - - flash(f"Instanz {subdomain} wurde gelöscht.", "success") - if delete_message: - flash(delete_message, "info") - return redirect(url_for("admin_instances")) - - if action == "toggle_library": - target = _get_school_instance_by_subdomain(subdomain) - if not target: - flash("Instanz nicht gefunden.", "error") - return redirect(url_for("admin_instances")) - - instance_dir = _resolve_instance_dir(subdomain) - if not instance_dir: - flash("Instanzverzeichnis nicht gefunden.", "error") - return redirect(url_for("admin_instances")) - - target_enabled = (request.form.get("library_enabled") or "").strip().lower() in {"1", "on", "true", "yes"} - ok, message = _set_instance_library_enabled(instance_dir, target_enabled) - if not ok: - _upsert_school_instance( - { - "subdomain": subdomain, - "status": "Fehler", - "nginx_status": "error", - "last_message": message, - } - ) - flash(message, "error") - return redirect(url_for("admin_instances")) - - restart_ok, restart_output = _restart_instance_stack(instance_dir) - client = None - try: - client, col = _get_collection("school_instances") - col.update_one( - {"subdomain": subdomain}, - { - "$set": { - "school_name": target.get("school_name") or "", - "owner_username": target.get("owner_username") or "", - "subdomain": subdomain, - "domain": target.get("domain") or f"{subdomain}.{INSTANCE_PARENT_DOMAIN}", - "https_port": int(target.get("https_port") or 0), - "instance_dir": instance_dir, - "app_image_tag": target.get("app_image_tag") or "latest", - "library_enabled": target_enabled, - "status": "Läuft" if restart_ok else "Fehler", - "nginx_status": "ok" if restart_ok else "error", - "last_message": "Bibliothek aktiviert/deaktiviert und Instanz neu gestartet." if restart_ok else _tail_output(restart_output, 18), - "updated_at": _utc_now_iso(), - }, - "$setOnInsert": {"created_at": _utc_now_iso()}, - }, - upsert=True, - ) - except PyMongoError: - pass - finally: - if client: - client.close() - - if restart_ok: - flash(f"Bibliothek wurde {'aktiviert' if target_enabled else 'deaktiviert'} und die Instanz neu gestartet.", "success") - if restart_output: - flash(_tail_output(restart_output, 12), "info") - else: - flash(f"Bibliothek wurde gespeichert, aber der Neustart ist fehlgeschlagen.\n{_tail_output(restart_output, 18)}", "error") - return redirect(url_for("admin_instances")) - - if action not in {"create", "start"}: - flash("Ungültige Aktion für Instanzverwaltung.", "error") - return redirect(url_for("admin_instances")) - - if not available_users: - flash("Keine freien Nutzer verfügbar. Bitte zuerst einen neuen Nutzer anlegen oder eine bestehende Instanz löschen.", "error") - return redirect(url_for("admin_instances")) - - if not school_name: - flash("Bitte einen Schulnamen angeben.", "error") - return redirect(url_for("admin_instances")) - - if not owner_username: - flash("Bitte einen Nutzer zuweisen.", "error") - return redirect(url_for("admin_instances")) - - owner_doc = _find_user(owner_username) - if not owner_doc: - flash("Ausgewählter Nutzer wurde nicht gefunden.", "error") - return redirect(url_for("admin_instances")) - - if not school_name: - school_name = _sanitize_text(owner_doc.get("display_name") or owner_username, 120) - - if app_image_tag not in version_options: - flash("Ungültige Version ausgewählt.", "error") - return redirect(url_for("admin_instances")) - - existing_for_owner = _get_instance_for_user(owner_username, "") - if existing_for_owner: - existing_sub = _sanitize_text(existing_for_owner.get("subdomain") or "", 63) - if existing_sub and existing_sub != subdomain: - flash( - f"Nutzer {owner_username} ist bereits der Instanz {existing_sub} zugewiesen. " - "Bitte erst diese Zuweisung ändern.", - "error", - ) - return redirect(url_for("admin_instances")) - - if not _is_valid_subdomain(subdomain): - flash("Ungültige Subdomain. Erlaubt sind a-z, 0-9 und Bindestriche (3-63 Zeichen).", "error") - return redirect(url_for("admin_instances")) - - success, message, details = _run_instance_provision( - action, - school_name, - subdomain, - app_image_tag=app_image_tag, - library_enabled=library_enabled, - ) - - instance_data = { - "school_name": school_name, - "owner_username": owner_username, - "subdomain": details.get("SUBDOMAIN") or subdomain, - "domain": details.get("DOMAIN") or f"{subdomain}.{INSTANCE_PARENT_DOMAIN}", - "https_port": int((details.get("HTTPS_PORT") or "0") or 0), - "instance_dir": details.get("INSTANCE_DIR") or os.path.join(INSTANCE_BASE_DIR, subdomain), - "app_image_tag": details.get("APP_IMAGE_TAG") or app_image_tag, - "library_enabled": library_enabled if details.get("LIBRARY_ENABLED") is None else details.get("LIBRARY_ENABLED") == "1", - "status": "Läuft" if success else "Fehler", - "nginx_status": details.get("NGINX_STATUS") or ("ok" if success else "error"), - "last_message": message, - } - _upsert_school_instance(instance_data) - - if success: - flash(f"Instanz gestartet: {instance_data['domain']}", "success") - if message: - flash(message, "info") - else: - flash(message or "Instanz konnte nicht gestartet werden.", "error") - - return redirect(url_for("admin_instances")) instances = _list_school_instances() - instance_dashboard = _build_instance_dashboard(instances) + return render_template( "admin_instances.html", instances=instances, - users=_list_users_for_admin(), - available_users=available_users, - instance_dashboard=instance_dashboard, - version_options=version_options, - instance_repo_url=INSTANCE_REPO_URL, parent_domain=INSTANCE_PARENT_DOMAIN, - base_dir=INSTANCE_BASE_DIR, - provision_script=INSTANCE_PROVISION_SCRIPT, ) @@ -3869,6 +3649,36 @@ def tenant_status(): return jsonify({"status": "pending"}), 200 +@app.route('/admin/test/drop_all_instances', methods=['POST']) +@login_required +def admin_drop_all_instances(): + # 1. Sicherheits-Check: Nur Admins dürfen das! + # Passe diesen Check an deine bestehende Rollenverwaltung an + if not session.get("is_admin") and session.get("role") != "admin": + flash("Zugriff verweigert. Diese Aktion ist nur für Administratoren erlaubt.", "error") + return redirect(url_for('my_instance_management')) # oder zum Dashboard + + inst_client, inst_col = _get_collection("instances") + req_client, req_col = _get_collection("instance_requests") + + try: + # Löscht alle Dokumente in den beiden Collections + deleted_instances = inst_col.delete_many({}).deleted_count + deleted_requests = req_col.delete_many({}).deleted_count + + flash(f"Test-Reset erfolgreich: {deleted_instances} Instanzen und {deleted_requests} Anfragen wurden unwiderruflich gelöscht.", "success") + print(f"[ADMIN ACTION] Alle Instanzen ({deleted_instances}) und Requests ({deleted_requests}) wurden von {session.get('username')} gelöscht.") + + except Exception as e: + print(f"[ERROR] Fehler beim Löschen der Datenbank: {e}") + flash("Ein Fehler ist aufgetreten. Datenbank konnte nicht bereinigt werden.", "error") + finally: + inst_client.close() + req_client.close() + + # Leite den Admin danach zurück zu einer sicheren Seite + return redirect(url_for('my_instance_management')) + def main(): app.run(host="0.0.0.0", port=4999, debug=False) diff --git a/Website/templates/admin_instances.html b/Website/templates/admin_instances.html index ea75621..7fc8cd1 100644 --- a/Website/templates/admin_instances.html +++ b/Website/templates/admin_instances.html @@ -1,137 +1,29 @@ {% extends "base.html" %} -{% block title %}Admin | Instanzen{% endblock %} +{% block title %}Admin | Tenant Instanzen{% endblock %} {% block content %}
-

Schul-Instanzen starten

-

Neue Instanzen des Inventarsystems für Subdomains pro Schule anlegen, sowie die Bibliothek dauerhaft aktivieren oder deaktivieren.

-
- -
-
-

Instanzen gesamt

-

{{ instance_dashboard.kpis.total }}

-
-
-

Laufend

-

{{ instance_dashboard.kpis.running }}

-
-
-

Fehlerhaft

-

{{ instance_dashboard.kpis.error }}

-
-
-

Bibliothek aktiv

-

{{ instance_dashboard.kpis.library_on }}

-
-
-

Nutzer zugewiesen

-

{{ instance_dashboard.kpis.assigned_users }}

-
-
- -
-
-

Status

- -
-
-

Bibliothek

- -
-
-

Versionen

- -
-
-

Instanzen pro Nutzer

- -
-
-

Aktivität (letzte 7 Tage)

- -
-
- -
-
- - - - - - {% if not available_users %} -

Alle Nutzer sind bereits einer Instanz zugewiesen. Lege einen neuen Nutzer an oder lösche eine bestehende Instanz.

- {% endif %} - - - - - - - - - - - - - -
- - +

Tenant Instanzen Übersicht

+

Übersicht aller zugewiesenen und aktiven Schul-Instanzen im System.

-

Vorhandene Instanzen

-
-
- - -
-
- - -
-
+

Vorhandene Tenant Instanzen

{% if instances %} - - + + + - - - + @@ -139,185 +31,84 @@ {% for item in instances %} - + - - - + - - - + + + + {% endfor %}
SchuleNutzerSubdomainNutzer (Owner)Subdomain / DomainHTTPS-Port Version BibliothekAktionHTTPS-Port StatusNginxNginx Status Letzte Meldung Aktualisiert
{{ item.school_name }}{{ item.school_name or '-' }} {{ item.owner_username or '-' }} {% if item.domain %} - {{ item.domain }} + + {{ item.domain }} + + {% elif item.subdomain %} + {{ item.subdomain }}.{{ parent_domain }} {% else %} - {% endif %} {{ item.app_image_tag or 'latest' }}{{ 'an' if item.library_enabled else 'aus' }} -
- - - - -
-
- - - -
-
{{ item.https_port or '-' }}{{ item.app_image_tag or 'latest' }} - {{ item.status }} + + {{ 'Aktiv' if item.library_enabled else 'Inaktiv' }} + {{ item.nginx_status }}{{ item.last_message }}{{ item.updated_at }} + + {{ item.status or 'Unbekannt' }} + + {{ item.nginx_status or 'unbekannt' }}{{ item.last_message or '-' }}{{ item.updated_at or '-' }}
{% else %} -

Noch keine Instanzen vorhanden.

+

Aktuell sind keine Tenant-Instanzen in der Datenbank vorhanden.

{% endif %}
- - - - -{% endblock %} +{% endblock %} \ No newline at end of file