AstraOS-Panel/AstraOS/astra_os.py
2025-12-09 00:29:22 +01:00

819 lines
31 KiB
Python

import os
from flask import Flask, render_template, request, redirect, url_for, session, flash
from pymongo import MongoClient
from werkzeug.security import check_password_hash, generate_password_hash
from flask_wtf.csrf import CSRFProtect
from datetime import timedelta, datetime
from functools import wraps
from bson.objectid import ObjectId
import paramiko
from flask import jsonify
import logging
from flask_socketio import SocketIO, emit, join_room, leave_room
import subprocess
import threading
from werkzeug.utils import secure_filename
import requests
import ipaddress
# Verwenden Sie eventlet für die Produktion
async_mode = "threading"
app = Flask(__name__,
template_folder='Frontend/HTML',
static_folder='Frontend')
socketio = SocketIO(app, async_mode=async_mode)
# Verzeichnis für hochgeladene Bots
BOT_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Bots')
if not os.path.exists(BOT_UPLOAD_FOLDER):
os.makedirs(BOT_UPLOAD_FOLDER)
app.config['BOT_UPLOAD_FOLDER'] = BOT_UPLOAD_FOLDER
# Dictionary zum Speichern laufender Bot-Prozesse
running_bots = {}
# Filter, um die GET /api/server_load Anfragen aus den Logs auszublenden
class SuppressServerLoadFilter(logging.Filter):
def filter(self, record):
return "GET /api/server_load" not in record.getMessage()
# Den Filter zum Werkzeug-Logger hinzufügen
log = logging.getLogger('werkzeug')
log.addFilter(SuppressServerLoadFilter())
app.secret_key = os.urandom(32)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=8)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
csrf = CSRFProtect(app)
@app.context_processor
def inject_utility_processor():
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
is_maintenance_active = maintenance_status and maintenance_status.get('enabled', False)
return dict(
timedelta=timedelta,
maintenance_active=is_maintenance_active
)
try:
client = MongoClient("mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/")
db = client["astra_os"]
accounts_collection = db["accounts"]
logs_collection = db["logs"]
roles_collection = db["roles"]
dashboard_collection = db["dashboard_stats"]
discord_bots_collection = db["discord_bots_data"]
ip_analyzer_results_collection = db["ip_analyzer_results"]
print("INFO: MongoDB-Verbindung erfolgreich hergestellt.")
AVAILABLE_PERMISSIONS = [
"view_dashboard",
"view_user_management",
"manage_users",
"view_role_management",
"manage_roles",
"view_logs",
"maintenance_management",
"bypass_maintenance",
"manage_discord_bots",
"view_ip_analyzer"
]
if roles_collection.count_documents({}) == 0:
print("WARNUNG: Keine Rollen in der Datenbank gefunden. Erstelle Standard-Rollen...")
roles_collection.insert_many([
{
"name": "Owner",
"permissions": AVAILABLE_PERMISSIONS,
"is_deletable": False
},
{
"name": "Benutzer",
"permissions": ["view_dashboard"],
"is_deletable": True
}
])
print("INFO: Standard-Rollen ('Owner', 'Benutzer') wurden erstellt.")
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"})
hashed_password = generate_password_hash("1234")
accounts_collection.insert_one({
'username': 'admin',
'password': hashed_password,
'role_id': owner_role['_id'],
'creation_date': datetime.utcnow()
})
print("INFO: Standard-Admin-Benutzer ('admin', Passwort: '1234') mit Rolle 'Owner' wurde erstellt.")
# Standard-Dashboard-Einträge anlegen, falls Collection leer
if dashboard_collection.count_documents({}) == 0:
print("INFO: Leere dashboard_stats Collection - lege Standardstatistiken an.")
dashboard_collection.insert_many([
{"key": "active_users", "value": 1, "percent": 100},
{"key": "server_load", "percent": 0, "status": "Nicht Erreichbar"},
{"key": "open_tickets", "count": 0},
{"key": "system_status", "status": "Online"},
{"key": "scheduled_maintenance", "enabled": False, "time": None, "status": "Keine", "duration_minutes": None},
{"key": "last_user_created", "time": None, "by": None}
])
print("INFO: Standard Dashboard-Statistiken wurden erstellt.")
except Exception as e:
print(f"FATAL: Verbindung zur MongoDB fehlgeschlagen: {e}")
db = None
accounts_collection = None
logs_collection = None
roles_collection = None
dashboard_collection = None
discord_bots_collection = None
ip_analyzer_results_collection = None
print(f"FATAL: Verbindung zur MongoDB fehlgeschlagen: {e}")
db = None
accounts_collection = None
logs_collection = None
# MongoDB für Tickets (chainsaw_dc_data)
try:
tickets_client = MongoClient("mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/")
tickets_db = tickets_client["chainsaw_dc_data"]
tickets_collection = tickets_db["tickets"]
except Exception as e:
tickets_collection = None
print(f"FATAL: Verbindung zu Tickets-DB fehlgeschlagen: {e}")
def get_user_permissions(user_id):
user = accounts_collection.find_one({'_id': ObjectId(user_id)})
if not user or 'role_id' not in user:
return []
role = roles_collection.find_one({'_id': user['role_id']})
if not role:
return []
return role.get('permissions', [])
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
flash("Bitte melden Sie sich an, um diese Seite zu sehen.", "warning")
return redirect(url_for('login'))
# Berechtigungen bei jeder Anfrage aktualisieren, um Änderungen sofort zu übernehmen
session['permissions'] = get_user_permissions(session['user_id'])
# Wartungsmodus-Check
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"})
if maintenance_status and maintenance_status.get('enabled', False):
if 'bypass_maintenance' not in session['permissions']:
return redirect(url_for('maintenance'))
return f(*args, **kwargs)
return decorated_function
def permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return redirect(url_for('login'))
user_perms = get_user_permissions(session['user_id'])
if permission not in user_perms:
flash("Sie haben keine Berechtigung, diese Seite anzuzeigen.", "danger")
return redirect(url_for('dashboard'))
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/')
def home():
if 'user_id' in session:
return redirect(url_for('dashboard'))
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if 'user_id' in session:
return redirect(url_for('dashboard'))
# Wartungsmodus-Check vor dem Login-Versuch
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
if maintenance_status and maintenance_status.get('enabled', False):
return redirect(url_for('maintenance'))
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
flash("Benutzername und Passwort sind erforderlich.", "danger")
return render_template('login.html')
if accounts_collection is None:
flash("Datenbankverbindung nicht verfügbar. Bitte versuchen Sie es später erneut.", "danger")
return render_template('login.html'), 500
user = accounts_collection.find_one({'username': username})
if user and check_password_hash(user['password'], password):
session.permanent = True
session['user_id'] = str(user['_id'])
session['username'] = user['username']
session['permissions'] = get_user_permissions(str(user['_id']))
flash("Erfolgreich angemeldet!", "success")
return redirect(url_for('dashboard'))
else:
flash("Ungültiger Benutzername oder Passwort.", "danger")
return render_template('login.html')
return render_template('login.html')
@app.route('/dashboard')
@login_required
def dashboard():
# Aktive Benutzer
user_count = accounts_collection.count_documents({}) if accounts_collection is not None else 1
# Letzte Benutzererstellung
last_user = accounts_collection.find_one(sort=[("creation_date", -1)]) if accounts_collection is not None else None
last_user_time = last_user["creation_date"] if last_user and "creation_date" in last_user else None
last_user_by = last_user["username"] if last_user and "username" in last_user else None
# Offene Tickets
open_tickets = 0
if tickets_collection is not None:
open_tickets = tickets_collection.count_documents({"status": "open"})
# Server-Auslastung (wird per AJAX geladen, hier nur Platzhalter)
server_load = {"percent": 0, "status": "Lädt..."}
# Systemstatus (hier statisch, ggf. anpassen)
system_status = {"status": "Online"}
# Geplante Wartung (aus dashboard_stats falls vorhanden)
scheduled_maintenance = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
def _dt_to_str(v):
if v is None:
return None
try:
return v.strftime("%d. %b %Y, %H:%M")
except Exception:
return str(v)
maint = scheduled_maintenance or {"time": None, "status": "Keine", "duration_minutes": None}
maint["time_str"] = _dt_to_str(maint.get("time"))
stats = {
"active_users": {"value": user_count, "percent": 0}, # Prozent ggf. später berechnen
"server_load": server_load,
"open_tickets": {"count": open_tickets},
"system_status": system_status,
"scheduled_maintenance": maint,
"last_user_created": {
"time": last_user_time,
"by": last_user_by,
"time_str": _dt_to_str(last_user_time)
}
}
return render_template('dashboard.html', stats=stats, active_page='dashboard')
@app.route('/api/server_load')
@login_required
def api_server_load():
# SSH zu 89.144.42.175, user root, pw iwkms@1812:(
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("89.144.42.175", username="root", password="iwkms@1812:(")
stdin, stdout, stderr = ssh.exec_command("top -bn1 | grep 'Cpu(s)'")
output = stdout.read().decode()
ssh.close()
# Parse CPU usage
import re
match = re.search(r"(\d+\.\d+)\s*id", output)
if match:
idle = float(match.group(1))
usage = 100 - idle
return jsonify({"percent": round(usage, 1), "status": "Online"})
else:
return jsonify({"percent": 0, "status": "Fehler"})
except Exception as e:
return jsonify({"percent": 0, "status": "Nicht erreichbar"})
@app.route('/logout')
@login_required
def logout():
session.clear()
flash("Sie wurden erfolgreich abgemeldet.", "info")
return redirect(url_for('login'))
@app.route('/status')
@login_required
def status_page():
return render_template('status_page.html', active_page='status_page')
@app.route('/user_management')
@login_required
@permission_required('view_user_management')
def user_management():
if accounts_collection is None or roles_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return redirect(url_for('dashboard'))
users_list = list(accounts_collection.find())
roles_list = list(roles_collection.find())
role_map = {str(role['_id']): role['name'] for role in roles_list}
for user in users_list:
user['role_name'] = role_map.get(str(user.get('role_id')), 'Unbekannt')
return render_template('user_management.html', users=users_list, roles=roles_list, active_page='user_management')
@app.route('/add_user', methods=['POST'])
@login_required
@permission_required('manage_users')
def add_user():
if accounts_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return redirect(url_for('user_management'))
username = request.form.get('username')
password = request.form.get('password')
role_id = request.form.get('role')
if not username or not password or not role_id:
flash("Alle Felder sind erforderlich.", "danger")
return redirect(url_for('user_management'))
if accounts_collection.find_one({'username': username}):
flash("Ein Benutzer mit diesem Namen existiert bereits.", "danger")
return redirect(url_for('user_management'))
hashed_password = generate_password_hash(password)
accounts_collection.insert_one({
'username': username,
'password': hashed_password,
'role_id': ObjectId(role_id),
'creation_date': datetime.utcnow()
})
# Dashboard-Statistik aktualisieren
if dashboard_collection is not None:
dashboard_collection.update_one(
{"key": "last_user_created"},
{"$set": {
"time": datetime.utcnow(),
"by": username
}},
upsert=True
)
# Aktive Benutzer aktualisieren
user_count = accounts_collection.count_documents({})
dashboard_collection.update_one(
{"key": "active_users"},
{"$set": {"value": user_count}},
upsert=True
)
flash(f"Benutzer '{username}' wurde erfolgreich erstellt.", "success")
return redirect(url_for('user_management'))
@app.route('/delete_user/<user_id>', methods=['POST'])
@login_required
@permission_required('manage_users')
def delete_user(user_id):
if accounts_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return redirect(url_for('user_management'))
if session.get('user_id') == user_id:
flash("Sie können sich nicht selbst löschen.", "danger")
return redirect(url_for('user_management'))
accounts_collection.delete_one({'_id': ObjectId(user_id)})
flash("Benutzer wurde erfolgreich gelöscht.", "success")
return redirect(url_for('user_management'))
@app.route('/role_management')
@login_required
@permission_required('view_role_management')
def role_management():
if roles_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return redirect(url_for('dashboard'))
roles = list(roles_collection.find())
# Konvertiere ObjectId in String für die JSON-Serialisierung im Template
for role in roles:
role['_id'] = str(role['_id'])
return render_template('role_management.html', roles=roles, available_permissions=AVAILABLE_PERMISSIONS, active_page='role_management')
@app.route('/add_role', methods=['POST'])
@login_required
@permission_required('manage_roles')
def add_role():
role_name = request.form.get('role_name')
permissions = request.form.getlist('permissions')
if not role_name:
flash("Rollenname ist erforderlich.", "danger")
return redirect(url_for('role_management'))
if roles_collection.find_one({'name': role_name}):
flash("Eine Rolle mit diesem Namen existiert bereits.", "danger")
return redirect(url_for('role_management'))
roles_collection.insert_one({
"name": role_name,
"permissions": permissions,
"is_deletable": True
})
flash(f"Rolle '{role_name}' wurde erstellt.", "success")
return redirect(url_for('role_management'))
@app.route('/edit_role/<role_id>', methods=['POST'])
@login_required
@permission_required('manage_roles')
def edit_role(role_id):
role_name = request.form.get('role_name')
permissions = request.form.getlist('permissions')
if not role_name:
flash("Rollenname ist erforderlich.", "danger")
return redirect(url_for('role_management'))
roles_collection.update_one(
{'_id': ObjectId(role_id)},
{'$set': {'name': role_name, 'permissions': permissions}}
)
flash(f"Rolle '{role_name}' wurde aktualisiert.", "success")
return redirect(url_for('role_management'))
@app.route('/delete_role/<role_id>', methods=['POST'])
@login_required
@permission_required('manage_roles')
def delete_role(role_id):
role = roles_collection.find_one({'_id': ObjectId(role_id)})
if role and not role.get('is_deletable', True):
flash("Diese Rolle kann nicht gelöscht werden.", "danger")
return redirect(url_for('role_management'))
roles_collection.delete_one({'_id': ObjectId(role_id)})
flash("Rolle wurde gelöscht.", "success")
return redirect(url_for('role_management'))
# --- IP Analyzer ---
@app.route('/ip_analyzer', methods=['GET', 'POST'])
@login_required
@permission_required('view_ip_analyzer')
def ip_analyzer():
ip_data = None
error = None
similar_ips = []
if request.method == 'POST':
ip_address = request.form.get("ip_address")
is_cheater = request.form.get("is_cheater") == "on"
steam_id = request.form.get("steam_id") if is_cheater else None
if not ip_address:
error = "Bitte eine IP-Adresse eingeben."
elif is_cheater and not steam_id:
error = "SteamID ist erforderlich, wenn die IP als Cheater markiert wird."
else:
try:
api_key = "015ada9472664329affb2f26d7036d47" # API-Schlüssel aus altem Code
response = requests.get(f"https://vpnapi.io/api/{ip_address}?key={api_key}", timeout=5)
if response.status_code == 200:
ip_data = response.json()
# Sicherstellen, dass verschachtelte Objekte existieren
ip_data.setdefault('security', {})
ip_data.setdefault('location', {})
ip_data.setdefault('network', {})
# Ähnliche IPs finden
similar_ips = list(ip_analyzer_results_collection.find({
"$or": [
{"ip": ip_address},
{"data.location.city": ip_data.get('location', {}).get('city')},
{"data.network.autonomous_system_number": ip_data.get('network', {}).get('autonomous_system_number')}
]
}))
# Neuen Eintrag speichern
ip_analyzer_results_collection.insert_one({
"ip": ip_address,
"is_cheater": is_cheater,
"steam_id": steam_id,
"data": ip_data,
"added_by": session["username"],
"timestamp": datetime.utcnow()
})
flash("IP-Adresse erfolgreich analysiert und gespeichert.", "success")
else:
error = f"Fehler bei der API-Anfrage: Status {response.status_code}"
except requests.exceptions.RequestException as e:
error = f"Fehler bei der Verbindung zur API: {e}"
except Exception as e:
error = f"Ein unerwarteter Fehler ist aufgetreten: {e}"
return render_template('ip_analyzer.html',
active_page='ip_analyzer',
ip_data=ip_data,
error=error,
similar_ips=similar_ips)
# --- Discord Bot Management ---
def stream_bot_output(bot_id, process):
"""Liest stdout/stderr eines Prozesses, speichert den Verlauf und sendet es via SocketIO."""
def stream_to_socket(pipe, log_type):
try:
for line in iter(pipe.readline, ''):
log_entry = {'type': log_type, 'data': line}
# Verlauf speichern, wenn der Bot noch läuft
if bot_id in running_bots:
running_bots[bot_id]['history'].append(log_entry)
# Optional: Begrenzen Sie die Größe des Verlaufs, um Speicher zu sparen
max_history_lines = 1000
if len(running_bots[bot_id]['history']) > max_history_lines:
running_bots[bot_id]['history'].pop(0)
socketio.emit('console_output', log_entry, room=bot_id)
except Exception as e:
print(f"Stream-Fehler für Bot {bot_id}: {e}")
finally:
if pipe:
pipe.close()
stdout_thread = threading.Thread(target=stream_to_socket, args=(process.stdout, 'stdout'))
stderr_thread = threading.Thread(target=stream_to_socket, args=(process.stderr, 'stderr'))
stdout_thread.start()
stderr_thread.start()
process.wait() # Warten, bis der Prozess endet
# Prozess beendet, Status aktualisieren
if bot_id in running_bots:
del running_bots[bot_id]
socketio.emit('status_update', {'bot_id': bot_id, 'status': 'stopped'}, room=bot_id)
socketio.emit('console_output', {'bot_id': bot_id, 'type': 'system', 'data': '--- Bot-Prozess beendet ---'}, room=bot_id)
@app.route('/discord_bots')
@login_required
@permission_required('manage_discord_bots')
def discord_bots():
bots = list(discord_bots_collection.find())
for bot in bots:
bot['status'] = 'running' if str(bot['_id']) in running_bots else 'stopped'
return render_template('discord_bots.html', active_page='discord_bots', bots=bots)
@app.route('/upload_bot', methods=['POST'])
@login_required
@permission_required('manage_discord_bots')
def upload_bot():
bot_name = request.form.get('bot_name')
bot_file = request.files.get('bot_file')
if not bot_name or not bot_file or bot_file.filename == '':
flash("Bot-Name und Datei sind erforderlich.", "danger")
return redirect(url_for('discord_bots'))
if discord_bots_collection.find_one({'name': bot_name}):
flash("Ein Bot mit diesem Namen existiert bereits.", "danger")
return redirect(url_for('discord_bots'))
filename = secure_filename(bot_file.filename)
filepath = os.path.join(app.config['BOT_UPLOAD_FOLDER'], filename)
bot_file.save(filepath)
discord_bots_collection.insert_one({
'name': bot_name,
'filename': filename,
'filepath': filepath,
'created_at': datetime.utcnow()
})
flash(f"Bot '{bot_name}' wurde erfolgreich hochgeladen.", "success")
return redirect(url_for('discord_bots'))
@app.route('/delete_bot/<bot_id>', methods=['POST'])
@login_required
@permission_required('manage_discord_bots')
def delete_bot(bot_id):
bot_info = discord_bots_collection.find_one({'_id': ObjectId(bot_id)})
if not bot_info:
flash("Bot nicht gefunden.", "danger")
return redirect(url_for('discord_bots'))
# Bot stoppen, falls er läuft
if bot_id in running_bots:
process = running_bots[bot_id]['process']
try:
process.terminate()
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
del running_bots[bot_id]
socketio.emit('status_update', {'bot_id': bot_id, 'status': 'deleted'}, room=bot_id)
# Bot-Datei löschen
try:
filepath = bot_info.get('filepath')
if filepath and os.path.exists(filepath):
os.remove(filepath)
except Exception as e:
flash(f"Fehler beim Löschen der Bot-Datei: {e}", "danger")
# Fortfahren, um den DB-Eintrag trotzdem zu löschen
# Eintrag aus der Datenbank löschen
discord_bots_collection.delete_one({'_id': ObjectId(bot_id)})
flash(f"Bot '{bot_info['name']}' wurde erfolgreich gelöscht.", "success")
return redirect(url_for('discord_bots'))
@app.route('/bot_console/<bot_id>')
@login_required
@permission_required('manage_discord_bots')
def bot_console(bot_id):
bot = discord_bots_collection.find_one({'_id': ObjectId(bot_id)})
if not bot:
flash("Bot nicht gefunden.", "danger")
return redirect(url_for('discord_bots'))
bot['status'] = 'running' if bot_id in running_bots else 'stopped'
return render_template('bot_console.html', bot=bot, active_page='discord_bots')
@socketio.on('connect')
def handle_connect():
print('Client verbunden')
@socketio.on('join_console')
def handle_join_console(data):
bot_id = data['bot_id']
join_room(bot_id)
emit('console_output', {'type': 'system', 'data': f'Verbunden mit Konsole für Bot {bot_id}.'})
# Gespeicherten Verlauf an den neuen Client senden
if bot_id in running_bots and 'history' in running_bots[bot_id]:
for log in running_bots[bot_id]['history']:
emit('console_output', log)
@socketio.on('leave_console')
def handle_leave_console(data):
bot_id = data['bot_id']
leave_room(bot_id)
print(f'Client hat Konsole für Bot {bot_id} verlassen.')
@socketio.on('toggle_bot')
def handle_toggle_bot(data):
bot_id = data['bot_id']
action = data['action'] # 'start' or 'stop'
bot_info = discord_bots_collection.find_one({'_id': ObjectId(bot_id)})
if not bot_info:
emit('console_output', {'type': 'error', 'data': 'Bot nicht in der Datenbank gefunden.'}, room=bot_id)
return
if action == 'start' and bot_id not in running_bots:
filepath = bot_info.get('filepath')
if not os.path.exists(filepath):
emit('console_output', {'type': 'error', 'data': f'Bot-Datei nicht gefunden: {filepath}'}, room=bot_id)
return
try:
# Start process
process = subprocess.Popen(
['python', '-u', filepath],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding='utf-8',
errors='replace'
)
running_bots[bot_id] = {'process': process, 'history': []}
# Start thread to stream output
thread = threading.Thread(target=stream_bot_output, args=(bot_id, process))
thread.daemon = True
thread.start()
emit('status_update', {'bot_id': bot_id, 'status': 'running'}, room=bot_id)
emit('console_output', {'type': 'system', 'data': f'--- Bot-Prozess gestartet (PID: {process.pid}) ---'}, room=bot_id)
except Exception as e:
emit('console_output', {'type': 'error', 'data': f'Fehler beim Starten des Bots: {e}'}, room=bot_id)
elif action == 'stop' and bot_id in running_bots:
process = running_bots[bot_id]['process']
try:
process.terminate() # Graceful shutdown
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill() # Force kill
if bot_id in running_bots:
del running_bots[bot_id]
emit('status_update', {'bot_id': bot_id, 'status': 'stopped'}, room=bot_id)
emit('console_output', {'type': 'system', 'data': '--- Bot-Prozess wird gestoppt... ---'}, room=bot_id)
@app.route('/maintenance')
def maintenance():
maint_info = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else {}
if not maint_info or not maint_info.get('enabled', False):
return redirect(url_for('login'))
return render_template('maintenance.html', maint_info=maint_info)
@app.route('/maintenance_login', methods=['GET', 'POST'])
def maintenance_login():
if 'user_id' in session:
return redirect(url_for('dashboard'))
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
if not maintenance_status or not maintenance_status.get('enabled', False):
return redirect(url_for('login'))
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
flash("Benutzername und Passwort sind erforderlich.", "danger")
return render_template('maintenance_login.html')
if accounts_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return render_template('maintenance_login.html'), 500
user = accounts_collection.find_one({'username': username})
if user and check_password_hash(user['password'], password):
# Überprüfen, ob der Benutzer die Bypass-Berechtigung hat
user_perms = get_user_permissions(str(user['_id']))
if 'bypass_maintenance' in user_perms:
session.permanent = True
session['user_id'] = str(user['_id'])
session['username'] = user['username']
session['permissions'] = user_perms
flash("Erfolgreich angemeldet!", "success")
return redirect(url_for('dashboard'))
else:
flash("Dieser Account hat keine Berechtigung für den Wartungs-Login.", "danger")
return render_template('maintenance_login.html')
else:
flash("Ungültiger Benutzername oder Passwort.", "danger")
return render_template('maintenance_login.html')
return render_template('maintenance_login.html')
@app.route('/maintenance_management')
@login_required
@permission_required('maintenance_management')
def maintenance_management():
maint_info = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else {}
if maint_info and maint_info.get('time'):
# Konvertiere UTC-Zeit aus DB in ein für datetime-local passendes Format
maint_info['time_local_str'] = maint_info['time'].strftime('%Y-%m-%dT%H:%M')
return render_template('maintenance_management.html', active_page='maintenance_management', maint_info=maint_info)
@app.route('/update_maintenance', methods=['POST'])
@login_required
@permission_required('maintenance_management')
def update_maintenance():
if dashboard_collection is None:
flash("Datenbankverbindung nicht verfügbar.", "danger")
return redirect(url_for('maintenance_management'))
is_enabled = request.form.get('maintenance_enabled') == 'on'
status_message = request.form.get('status_message', 'Keine')
duration = request.form.get('duration_minutes')
time_str = request.form.get('maintenance_time')
update_data = {
"enabled": is_enabled,
"status": status_message,
"duration_minutes": int(duration) if duration and duration.isdigit() else None,
"time": datetime.fromisoformat(time_str) if time_str else None
}
dashboard_collection.update_one(
{"key": "scheduled_maintenance"},
{"$set": update_data},
upsert=True
)
flash("Wartungseinstellungen wurden aktualisiert.", "success")
return redirect(url_for('maintenance_management'))
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000, debug=True, allow_unsafe_werkzeug=True)