diff --git a/AstraOS/Frontend/HTML/ets2_dlcs.html b/AstraOS/Frontend/HTML/ets2_dlcs.html new file mode 100644 index 0000000..e8817bc --- /dev/null +++ b/AstraOS/Frontend/HTML/ets2_dlcs.html @@ -0,0 +1,90 @@ +{% extends "sidebar.html" %} + +{% block title %}AstraOS - ETS2 DLCs{% endblock %} + +{% block content %} +
+
+

ETS2 DLCs

+
+ +
+ + + + + + + + + + + + {% for package in dlc_packages %} + + + + + + + + {% else %} + + + + {% endfor %} + +
NameDLC DatumOrdnerpfadErstellt vonAktionen
{{ package.name }}{{ package.dlc_date }}{{ package.folder_path }}{{ package.created_by or 'N/A' }} +
+ + Download + + {% if can_manage_ets2_dlcs %} + + {% endif %} +
+
Noch keine DLC-Pakete vorhanden.
+
+ + {% if can_manage_ets2_dlcs %} +
+

Neues DLC-Paket erstellen

+
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ {% endif %} + + +
+{% endblock %} + diff --git a/AstraOS/Frontend/HTML/sidebar.html b/AstraOS/Frontend/HTML/sidebar.html index cbb76f9..51ea68a 100644 --- a/AstraOS/Frontend/HTML/sidebar.html +++ b/AstraOS/Frontend/HTML/sidebar.html @@ -239,7 +239,7 @@
diff --git a/AstraOS/astra_os.py b/AstraOS/astra_os.py index dbd746a..7c4b5d0 100644 --- a/AstraOS/astra_os.py +++ b/AstraOS/astra_os.py @@ -7,7 +7,7 @@ from datetime import timedelta, datetime from functools import wraps from bson.objectid import ObjectId import paramiko -from flask import jsonify +from flask import jsonify, send_file, after_this_request import logging from flask_socketio import SocketIO, emit, join_room, leave_room import subprocess @@ -17,6 +17,8 @@ import requests import ipaddress import socket import ssl +import zipfile +import tempfile from dotenv import load_dotenv load_dotenv() async_mode = "eventlet" @@ -68,6 +70,7 @@ try: roles_collection = db["roles"] dashboard_collection = db["dashboard_stats"] discord_bots_collection = db["discord_bots_data"] + ets2_dlcs_collection = db["ets2_dlcs_packages"] ip_analyzer_results_collection = db["ip_analyzer_results"] age_verification_collection = db["age_verification"] print("INFO: MongoDB-Verbindung erfolgreich hergestellt.") @@ -81,6 +84,8 @@ try: "maintenance_management", "bypass_maintenance", "manage_discord_bots", + "view_ets2_dlcs", + "manage_ets2_dlcs", "view_ip_analyzer", "view_age_verification", "manage_age_verification" @@ -100,6 +105,12 @@ try: } ]) print("INFO: Standard-Rollen ('Owner', 'Benutzer') wurden erstellt.") + else: + # Bestehende Owner-Rollen auf neue Permissions anheben. + roles_collection.update_many( + {"name": "Owner"}, + {"$addToSet": {"permissions": {"$each": AVAILABLE_PERMISSIONS}}} + ) if accounts_collection.count_documents({}) == 0: print("WARNUNG: Keine Benutzer in der Datenbank gefunden. Erstelle Standard-Admin-Benutzer...") owner_role = roles_collection.find_one({"name": "Owner"}) @@ -133,11 +144,13 @@ except Exception as e: roles_collection = None dashboard_collection = None discord_bots_collection = None + ets2_dlcs_collection = None ip_analyzer_results_collection = None print(f"FATAL: Verbindung zur MongoDB fehlgeschlagen: {e}") db = None accounts_collection = None logs_collection = None + ets2_dlcs_collection = None def _venv_paths(): venv_bin = "/home/astraos/Astra-OS-Panel/venv/bin" @@ -973,6 +986,152 @@ def logs(): total_pages=total_pages, active_page='logs') + +@app.route('/ets2_dlcs') +@login_required +@permission_required('view_ets2_dlcs') +def ets2_dlcs(): + if ets2_dlcs_collection is None: + flash("Datenbankverbindung nicht verfügbar.", "danger") + return redirect(url_for('dashboard')) + + packages = list(ets2_dlcs_collection.find().sort('created_at', -1)) + return render_template( + 'ets2_dlcs.html', + active_page='ets2_dlcs', + dlc_packages=packages, + can_manage_ets2_dlcs='manage_ets2_dlcs' in session.get('permissions', []) + ) + + +@app.route('/ets2_dlcs/create', methods=['POST']) +@login_required +@permission_required('manage_ets2_dlcs') +def create_ets2_dlc_package(): + if ets2_dlcs_collection is None: + flash("Datenbankverbindung nicht verfügbar.", "danger") + return redirect(url_for('ets2_dlcs')) + + package_name = (request.form.get('package_name') or '').strip() + folder_path_input = (request.form.get('folder_path') or '').strip() + dlc_date = (request.form.get('dlc_date') or '').strip() + + if not package_name or not folder_path_input or not dlc_date: + flash("Name, Ordnerpfad und Datum sind erforderlich.", "danger") + return redirect(url_for('ets2_dlcs')) + + try: + datetime.strptime(dlc_date, '%Y-%m-%d') + except ValueError: + flash("Ungültiges Datumsformat. Bitte YYYY-MM-DD verwenden.", "danger") + return redirect(url_for('ets2_dlcs')) + + folder_path = os.path.abspath(folder_path_input) + if not os.path.isdir(folder_path): + flash("Der angegebene Ordnerpfad existiert nicht oder ist kein Ordner.", "danger") + return redirect(url_for('ets2_dlcs')) + + if ets2_dlcs_collection.find_one({'name': package_name}): + flash("Ein DLC-Paket mit diesem Namen existiert bereits.", "danger") + return redirect(url_for('ets2_dlcs')) + + ets2_dlcs_collection.insert_one({ + 'name': package_name, + 'folder_path': folder_path, + 'dlc_date': dlc_date, + 'created_by': session.get('username'), + 'created_at': datetime.utcnow() + }) + add_log("ETS2 DLC-Paket erstellt", f"Name: {package_name}, Pfad: {folder_path}") + flash("DLC-Paket wurde erfolgreich erstellt.", "success") + return redirect(url_for('ets2_dlcs')) + + +@app.route('/ets2_dlcs/delete/', methods=['POST']) +@login_required +@permission_required('manage_ets2_dlcs') +def delete_ets2_dlc_package(package_id): + if ets2_dlcs_collection is None: + flash("Datenbankverbindung nicht verfügbar.", "danger") + return redirect(url_for('ets2_dlcs')) + + try: + package_object_id = ObjectId(package_id) + except Exception: + flash("Ungültige Paket-ID.", "danger") + return redirect(url_for('ets2_dlcs')) + + package = ets2_dlcs_collection.find_one({'_id': package_object_id}) + if not package: + flash("DLC-Paket nicht gefunden.", "danger") + return redirect(url_for('ets2_dlcs')) + + ets2_dlcs_collection.delete_one({'_id': package_object_id}) + add_log("ETS2 DLC-Paket gelöscht", f"Name: {package.get('name', 'N/A')}") + flash("DLC-Paket wurde gelöscht.", "success") + return redirect(url_for('ets2_dlcs')) + + +@app.route('/ets2_dlcs/download/') +@login_required +@permission_required('view_ets2_dlcs') +def download_ets2_dlc_package(package_id): + if ets2_dlcs_collection is None: + flash("Datenbankverbindung nicht verfügbar.", "danger") + return redirect(url_for('ets2_dlcs')) + + try: + package_object_id = ObjectId(package_id) + except Exception: + flash("Ungültige Paket-ID.", "danger") + return redirect(url_for('ets2_dlcs')) + + package = ets2_dlcs_collection.find_one({'_id': package_object_id}) + if not package: + flash("DLC-Paket nicht gefunden.", "danger") + return redirect(url_for('ets2_dlcs')) + + folder_path = package.get('folder_path') + if not folder_path or not os.path.isdir(folder_path): + flash("Der gespeicherte Ordnerpfad ist ungültig oder nicht mehr vorhanden.", "danger") + return redirect(url_for('ets2_dlcs')) + + temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + temp_zip_path = temp_zip.name + temp_zip.close() + + try: + with zipfile.ZipFile(temp_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + has_content = False + for root, _, files in os.walk(folder_path): + for filename in files: + has_content = True + file_path = os.path.join(root, filename) + arcname = os.path.relpath(file_path, folder_path) + zipf.write(file_path, arcname) + + if not has_content: + zipf.writestr('EMPTY_PACKAGE.txt', 'Dieses DLC-Paket enthaelt aktuell keine Dateien.') + except Exception as e: + try: + os.remove(temp_zip_path) + except Exception: + pass + flash(f"Fehler beim Erstellen des Downloads: {e}", "danger") + return redirect(url_for('ets2_dlcs')) + + @after_this_request + def cleanup_temp_file(response): + try: + os.remove(temp_zip_path) + except Exception: + pass + return response + + safe_name = secure_filename(package.get('name', 'ets2_dlc_package')) or 'ets2_dlc_package' + add_log("ETS2 DLC-Paket heruntergeladen", f"Name: {package.get('name', 'N/A')}") + return send_file(temp_zip_path, as_attachment=True, download_name=f"{safe_name}.zip") + def stream_bot_output(bot_id, process): def stream_to_socket(pipe, log_type): try: