Introduction of pasword and acount changes for the user
This commit is contained in:
-206
@@ -2599,212 +2599,6 @@ def admin_instances():
|
||||
parent_domain=INSTANCE_PARENT_DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/instances/stats')
|
||||
@admin_required
|
||||
def admin_instances_stats():
|
||||
instances = _list_school_instances()
|
||||
return jsonify(_collect_runtime_stats(instances))
|
||||
|
||||
|
||||
@app.route('/admin/system', methods=['GET', 'POST'])
|
||||
@admin_required
|
||||
def admin_system_tools():
|
||||
if request.method == 'POST':
|
||||
action = _sanitize_text(request.form.get("action") or "", 40)
|
||||
subdomain = _sanitize_text(request.form.get("subdomain") or "", 63)
|
||||
|
||||
if action == "restart_core":
|
||||
compose_file = os.path.join(BASE_DIR, "docker-compose.yml")
|
||||
ok, output = _run_command(
|
||||
["docker", "compose", "-f", compose_file, "restart", "website", "mongodb"],
|
||||
cwd=BASE_DIR,
|
||||
timeout=180,
|
||||
)
|
||||
flash("Core-Services wurden neugestartet." if ok else f"Core-Restart fehlgeschlagen:\n{_tail_output(output, 10)}", "success" if ok else "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
if action in {"backup_instance", "update_instance", "restart_instance"}:
|
||||
instance_dir = _resolve_instance_dir(subdomain)
|
||||
if not instance_dir:
|
||||
flash("Instanz nicht gefunden oder ungültige Subdomain.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
if action == "backup_instance":
|
||||
command = ["bash", "./backup.sh", "--mode", "auto"]
|
||||
timeout = 1800
|
||||
title = f"Backup für {subdomain}"
|
||||
elif action == "update_instance":
|
||||
command = ["bash", "./update.sh"]
|
||||
timeout = 2400
|
||||
title = f"Update für {subdomain}"
|
||||
else:
|
||||
title = f"Restart für {subdomain}"
|
||||
ok, output = _restart_instance_stack(instance_dir)
|
||||
if ok:
|
||||
flash(f"{title} erfolgreich.\n{_tail_output(output, 12)}", "success")
|
||||
_upsert_school_instance(
|
||||
{
|
||||
"subdomain": subdomain,
|
||||
"status": "Läuft",
|
||||
"nginx_status": "ok",
|
||||
"last_message": "Instanz über System-Tools neu gestartet.",
|
||||
}
|
||||
)
|
||||
else:
|
||||
flash(f"{title} fehlgeschlagen.\n{_tail_output(output, 18)}", "error")
|
||||
_upsert_school_instance(
|
||||
{
|
||||
"subdomain": subdomain,
|
||||
"status": "Fehler",
|
||||
"nginx_status": "error",
|
||||
"last_message": _sanitize_text(_tail_output(output, 6), 500),
|
||||
}
|
||||
)
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
ok, output = _run_command(command, cwd=instance_dir, timeout=timeout)
|
||||
if ok:
|
||||
flash(f"{title} erfolgreich.\n{_tail_output(output, 12)}", "success")
|
||||
else:
|
||||
flash(f"{title} fehlgeschlagen.\n{_tail_output(output, 18)}", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
if action == "create_instance_admin":
|
||||
username = _sanitize_text(request.form.get("admin_username") or "", 80)
|
||||
password = (request.form.get("admin_password") or "").strip()
|
||||
first_name = _sanitize_text(request.form.get("admin_first_name") or "Admin", 80)
|
||||
last_name = _sanitize_text(request.form.get("admin_last_name") or "User", 80)
|
||||
|
||||
ok, msg = _create_instance_admin_user(subdomain, username, password, first_name, last_name)
|
||||
flash(msg, "success" if ok else "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
flash("Unbekannte System-Aktion.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
instances = _list_school_instances()
|
||||
system_snapshot = _build_server_management_snapshot(instances)
|
||||
return render_template("admin_system.html", instances=instances, system_snapshot=system_snapshot)
|
||||
|
||||
|
||||
@app.route('/admin/system/stats')
|
||||
@admin_required
|
||||
def admin_system_stats():
|
||||
instances = _list_school_instances()
|
||||
return jsonify(_build_server_management_snapshot(instances))
|
||||
|
||||
|
||||
@app.route('/admin/system/logs/live')
|
||||
@admin_required
|
||||
def admin_system_live_logs():
|
||||
return jsonify(_collect_homepage_service_logs())
|
||||
|
||||
|
||||
@app.route('/admin/system/logs/core')
|
||||
@admin_required
|
||||
def admin_download_core_logs():
|
||||
ok, output = _collect_core_logs()
|
||||
if not ok:
|
||||
flash(f"Core-Logs konnten nicht geladen werden.\n{_tail_output(output, 14)}", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
filename = f"core-logs-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}.log"
|
||||
return send_file(
|
||||
BytesIO(output.encode("utf-8")),
|
||||
mimetype="text/plain",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/system/logs/instance/<subdomain>')
|
||||
@admin_required
|
||||
def admin_download_instance_logs(subdomain):
|
||||
ok, output = _collect_instance_logs(subdomain)
|
||||
if not ok:
|
||||
flash(f"Instanz-Logs konnten nicht geladen werden.\n{_tail_output(output, 14)}", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
safe_name = _slugify_subdomain(subdomain)
|
||||
filename = f"instance-{safe_name}-logs-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}.log"
|
||||
return send_file(
|
||||
BytesIO(output.encode("utf-8")),
|
||||
mimetype="text/plain",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/system/backup/export/<subdomain>')
|
||||
@admin_required
|
||||
def admin_export_instance_backup(subdomain):
|
||||
ok, filename, archive_path = _build_instance_backup_archive(subdomain)
|
||||
if not ok or not archive_path:
|
||||
flash(filename or "Backup-Export fehlgeschlagen.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
@after_this_request
|
||||
def _cleanup_archive(response):
|
||||
try:
|
||||
os.remove(archive_path)
|
||||
except OSError:
|
||||
pass
|
||||
return response
|
||||
|
||||
return send_file(
|
||||
archive_path,
|
||||
mimetype="application/gzip",
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/system/backup/import/<subdomain>', methods=['POST'])
|
||||
@admin_required
|
||||
def admin_import_instance_backup(subdomain):
|
||||
instance_dir = _resolve_instance_dir(subdomain)
|
||||
if not instance_dir:
|
||||
flash("Instanzverzeichnis nicht gefunden.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
upload = request.files.get("backup_file")
|
||||
if not upload or not upload.filename:
|
||||
flash("Bitte eine Backup-Datei auswählen.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
filename = secure_filename(upload.filename)
|
||||
if not (filename.endswith(".tar.gz") or filename.endswith(".tgz")):
|
||||
flash("Nur .tar.gz oder .tgz Backups sind erlaubt.", "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
fd, temp_path = tempfile.mkstemp(prefix="instance-restore-", suffix=".tar.gz")
|
||||
os.close(fd)
|
||||
try:
|
||||
upload.save(temp_path)
|
||||
ok, message = _safe_extract_tar_archive(temp_path, instance_dir)
|
||||
if not ok:
|
||||
flash(message, "error")
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
restart_ok, restart_output = _restart_instance_stack(instance_dir)
|
||||
if restart_ok:
|
||||
flash(f"Backup für {subdomain} erfolgreich eingespielt und Instanz neu gestartet.", "success")
|
||||
if restart_output:
|
||||
flash(_tail_output(restart_output, 10), "info")
|
||||
else:
|
||||
flash(f"Backup eingespielt, aber Neustart fehlgeschlagen:\n{_tail_output(restart_output, 14)}", "error")
|
||||
except Exception as exc:
|
||||
flash(f"Backup konnte nicht eingespielt werden: {exc}", "error")
|
||||
finally:
|
||||
try:
|
||||
os.remove(temp_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return redirect(url_for("admin_system_tools"))
|
||||
|
||||
|
||||
@app.route('/admin/blog', methods=['GET', 'POST'])
|
||||
@admin_required
|
||||
def admin_blog():
|
||||
|
||||
@@ -540,11 +540,11 @@
|
||||
<details class="nav-dropdown">
|
||||
<summary class="nav-btn">Admin</summary>
|
||||
<div class="dropdown-menu">
|
||||
<a href="{{ url_for('admin_users') }}">Userverwaltung</a> <!-- War bereits hier vorhanden -->
|
||||
<a href="{{ url_for('user_profile') }}">Mein Profil</a>
|
||||
<a href="{{ url_for('admin_users') }}">Userverwaltung</a>
|
||||
<a href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
|
||||
<a href="{{ url_for('admin_chats') }}">Admin-Chat</a>
|
||||
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
|
||||
<a href="{{ url_for('admin_system_tools') }}">System-Tools</a>
|
||||
<a href="{{ url_for('admin_blog') }}">Blog verwalten</a>
|
||||
<a href="{{ url_for('admin_instances') }}">Schul-Instanzen</a>
|
||||
</div>
|
||||
@@ -554,7 +554,6 @@
|
||||
<details class="nav-dropdown">
|
||||
<summary class="nav-btn">Nutzerbereich</summary>
|
||||
<div class="dropdown-menu">
|
||||
<!-- NEU: Profilverwaltung für reguläre Nutzer im Desktop Dropdown -->
|
||||
<a href="{{ url_for('user_profile') }}">Mein Profil</a>
|
||||
<a href="{{ url_for('my_tutorials') }}">Tutorials</a>
|
||||
<a href="{{ url_for('my_invoices') }}">Meine Rechnungen</a>
|
||||
|
||||
Reference in New Issue
Block a user