AstraOS Ver. 1.21:
Update: ETS2 DLCs management hinzugefügt
This commit is contained in:
parent
d89f4731e9
commit
1174c1b7d0
90
AstraOS/Frontend/HTML/ets2_dlcs.html
Normal file
90
AstraOS/Frontend/HTML/ets2_dlcs.html
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
{% extends "sidebar.html" %}
|
||||||
|
|
||||||
|
{% block title %}AstraOS - ETS2 DLCs{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div x-data="{ showDeleteModal: false, packageToDelete: null }" class="space-y-10">
|
||||||
|
<header class="flex justify-between items-center gap-4">
|
||||||
|
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">ETS2 DLCs</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="bg-black/40 rounded-xl backdrop-blur-lg overflow-hidden {% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
|
<table class="min-w-full divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/20{% endif %}">
|
||||||
|
<thead class="bg-black/20">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Name</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">DLC Datum</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Ordnerpfad</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Erstellt von</th>
|
||||||
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-300 uppercase tracking-wider">Aktionen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/30{% endif %}">
|
||||||
|
{% for package in dlc_packages %}
|
||||||
|
<tr class="transition {% if maintenance_active %}hover:bg-yellow-500/5{% else %}hover:bg-purple-500/5{% endif %}">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">{{ package.name }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ package.dlc_date }}</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-300 break-all">{{ package.folder_path }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ package.created_by or 'N/A' }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-right">
|
||||||
|
<div class="flex items-center justify-end gap-2">
|
||||||
|
<a href="{{ url_for('download_ets2_dlc_package', package_id=package._id) }}" class="px-3 py-2 text-sm font-semibold rounded-lg transition text-white {% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
{% if can_manage_ets2_dlcs %}
|
||||||
|
<button @click="packageToDelete = '{{ package._id }}'; showDeleteModal = true" class="px-3 py-2 text-sm font-semibold rounded-lg transition text-white bg-red-600/80 hover:bg-red-700">
|
||||||
|
Loschen
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="px-6 py-8 text-center text-gray-400">Noch keine DLC-Pakete vorhanden.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if can_manage_ets2_dlcs %}
|
||||||
|
<div class="bg-black/40 rounded-xl backdrop-blur-lg p-8 {% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
|
<h3 class="text-2xl font-bold mb-6 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Neues DLC-Paket erstellen</h3>
|
||||||
|
<form action="{{ url_for('create_ets2_dlc_package') }}" method="POST" class="space-y-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
|
<div>
|
||||||
|
<label for="package_name" class="block text-sm font-medium text-gray-300">Paketname</label>
|
||||||
|
<input type="text" name="package_name" id="package_name" required class="mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 {% if maintenance_active %}focus:ring-yellow-500{% else %}focus:ring-purple-500{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="folder_path" class="block text-sm font-medium text-gray-300">Ordnerpfad</label>
|
||||||
|
<input type="text" name="folder_path" id="folder_path" required placeholder="z.B. C:\\ETS2\\DLCs\\Package1" class="mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 {% if maintenance_active %}focus:ring-yellow-500{% else %}focus:ring-purple-500{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="dlc_date" class="block text-sm font-medium text-gray-300">DLC Datum</label>
|
||||||
|
<input type="date" name="dlc_date" id="dlc_date" required class="mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 {% if maintenance_active %}focus:ring-yellow-500{% else %}focus:ring-purple-500{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button type="submit" class="px-6 py-2 text-white font-semibold rounded-lg transition {% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">
|
||||||
|
Paket erstellen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div x-show="showDeleteModal" @keydown.escape.window="showDeleteModal = false" class="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50" style="display: none;">
|
||||||
|
<div @click.away="showDeleteModal = false" class="bg-gray-900 border border-red-500/50 rounded-xl shadow-2xl p-8 w-full max-w-md">
|
||||||
|
<h3 class="text-2xl font-bold text-red-400 mb-4">DLC-Paket loschen</h3>
|
||||||
|
<p class="text-gray-300 mb-6">Mochtest du dieses DLC-Paket wirklich loschen? Der Eintrag wird entfernt.</p>
|
||||||
|
<form :action="'/ets2_dlcs/delete/' + packageToDelete" method="POST" class="flex justify-end gap-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
|
<button type="button" @click="showDeleteModal = false" class="px-4 py-2 rounded-lg text-gray-300 hover:bg-gray-700 transition">Abbrechen</button>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-semibold rounded-lg transition">Loschen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
||||||
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
||||||
data-open="{% if active_page in ['discord_bots'] %}true{% else %}false{% endif %}">
|
data-open="{% if active_page in ['discord_bots', 'ets2_dlcs'] %}true{% else %}false{% endif %}">
|
||||||
<span>Private Tools</span>
|
<span>Private Tools</span>
|
||||||
<svg class="w-4 h-4 transform transition-transform"
|
<svg class="w-4 h-4 transform transition-transform"
|
||||||
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -257,6 +257,14 @@
|
||||||
🤖 Discord Bots
|
🤖 Discord Bots
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if 'view_ets2_dlcs' in session.get('permissions', []) %}
|
||||||
|
<a href="{{ url_for('ets2_dlcs') }}"
|
||||||
|
class="block px-3 py-1 rounded-md transition
|
||||||
|
{% if active_page == 'ets2_dlcs' %}{% if maintenance_active %}bg-yellow-500/10 text-yellow-300{% else %}bg-purple-500/10 text-purple-300{% endif %}{% else %}
|
||||||
|
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}{% endif %}">
|
||||||
|
🚚 ETS2 DLCs
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from datetime import timedelta, datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
import paramiko
|
import paramiko
|
||||||
from flask import jsonify
|
from flask import jsonify, send_file, after_this_request
|
||||||
import logging
|
import logging
|
||||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
@ -17,6 +17,8 @@ import requests
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
|
import zipfile
|
||||||
|
import tempfile
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
async_mode = "eventlet"
|
async_mode = "eventlet"
|
||||||
|
|
@ -68,6 +70,7 @@ try:
|
||||||
roles_collection = db["roles"]
|
roles_collection = db["roles"]
|
||||||
dashboard_collection = db["dashboard_stats"]
|
dashboard_collection = db["dashboard_stats"]
|
||||||
discord_bots_collection = db["discord_bots_data"]
|
discord_bots_collection = db["discord_bots_data"]
|
||||||
|
ets2_dlcs_collection = db["ets2_dlcs_packages"]
|
||||||
ip_analyzer_results_collection = db["ip_analyzer_results"]
|
ip_analyzer_results_collection = db["ip_analyzer_results"]
|
||||||
age_verification_collection = db["age_verification"]
|
age_verification_collection = db["age_verification"]
|
||||||
print("INFO: MongoDB-Verbindung erfolgreich hergestellt.")
|
print("INFO: MongoDB-Verbindung erfolgreich hergestellt.")
|
||||||
|
|
@ -81,6 +84,8 @@ try:
|
||||||
"maintenance_management",
|
"maintenance_management",
|
||||||
"bypass_maintenance",
|
"bypass_maintenance",
|
||||||
"manage_discord_bots",
|
"manage_discord_bots",
|
||||||
|
"view_ets2_dlcs",
|
||||||
|
"manage_ets2_dlcs",
|
||||||
"view_ip_analyzer",
|
"view_ip_analyzer",
|
||||||
"view_age_verification",
|
"view_age_verification",
|
||||||
"manage_age_verification"
|
"manage_age_verification"
|
||||||
|
|
@ -100,6 +105,12 @@ try:
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
print("INFO: Standard-Rollen ('Owner', 'Benutzer') wurden erstellt.")
|
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:
|
if accounts_collection.count_documents({}) == 0:
|
||||||
print("WARNUNG: Keine Benutzer in der Datenbank gefunden. Erstelle Standard-Admin-Benutzer...")
|
print("WARNUNG: Keine Benutzer in der Datenbank gefunden. Erstelle Standard-Admin-Benutzer...")
|
||||||
owner_role = roles_collection.find_one({"name": "Owner"})
|
owner_role = roles_collection.find_one({"name": "Owner"})
|
||||||
|
|
@ -133,11 +144,13 @@ except Exception as e:
|
||||||
roles_collection = None
|
roles_collection = None
|
||||||
dashboard_collection = None
|
dashboard_collection = None
|
||||||
discord_bots_collection = None
|
discord_bots_collection = None
|
||||||
|
ets2_dlcs_collection = None
|
||||||
ip_analyzer_results_collection = None
|
ip_analyzer_results_collection = None
|
||||||
print(f"FATAL: Verbindung zur MongoDB fehlgeschlagen: {e}")
|
print(f"FATAL: Verbindung zur MongoDB fehlgeschlagen: {e}")
|
||||||
db = None
|
db = None
|
||||||
accounts_collection = None
|
accounts_collection = None
|
||||||
logs_collection = None
|
logs_collection = None
|
||||||
|
ets2_dlcs_collection = None
|
||||||
|
|
||||||
def _venv_paths():
|
def _venv_paths():
|
||||||
venv_bin = "/home/astraos/Astra-OS-Panel/venv/bin"
|
venv_bin = "/home/astraos/Astra-OS-Panel/venv/bin"
|
||||||
|
|
@ -973,6 +986,152 @@ def logs():
|
||||||
total_pages=total_pages,
|
total_pages=total_pages,
|
||||||
active_page='logs')
|
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/<package_id>', 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/<package_id>')
|
||||||
|
@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_bot_output(bot_id, process):
|
||||||
def stream_to_socket(pipe, log_type):
|
def stream_to_socket(pipe, log_type):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue