Die Dateien wurden wie folgt geändert und hinzugefügt:

1. backend/logs - 'admin', 'admin_api', 'app', 'calendar', 'data_management', 'drucker_steuerung', 'energy_monitoring', 'guest', 'hardware_integration', 'job_queue_system', 'jobs', 'models', 'monitoring_analytics', 'permissions', 'scheduler', 'security_suite', 'startup', '
This commit is contained in:
2025-06-20 01:06:37 +02:00
parent 0fcf04833f
commit 321626e9d3
754 changed files with 3013 additions and 174 deletions

View File

@ -828,6 +828,43 @@ def inject_current_route():
current_route = getattr(request, 'endpoint', None) or ''
return {'current_route': current_route}
@app.context_processor
def inject_moment():
"""
Injiziert eine moment-ähnliche Funktion für Template-Kompatibilität.
Behebt UndefinedError: 'moment' is not defined in Templates.
Ersetzt moment.js durch native Python datetime-Funktionalität.
"""
def moment():
"""Mock moment() Funktion die ein datetime-ähnliches Objekt zurückgibt"""
class MomentLike:
def __init__(self):
self.dt = datetime.now()
def format(self, format_str):
"""Konvertiert moment.js Format-Strings zu Python strftime"""
# Moment.js -> Python strftime Mapping
format_mapping = {
'DD.MM.YYYY': '%d.%m.%Y',
'DD/MM/YYYY': '%d/%m/%Y',
'YYYY-MM-DD': '%Y-%m-%d',
'HH:mm': '%H:%M',
'HH:mm:ss': '%H:%M:%S',
'DD.MM.YYYY HH:mm': '%d.%m.%Y %H:%M'
}
# Direkte Ersetzung wenn bekanntes Format
if format_str in format_mapping:
return self.dt.strftime(format_mapping[format_str])
# Fallback: Standard deutsche Formatierung
return self.dt.strftime('%d.%m.%Y')
return MomentLike()
return {'moment': moment}
@app.template_filter('format_datetime')
def format_datetime_filter(value, format='%d.%m.%Y %H:%M'):
"""Template-Filter für Datums-Formatierung"""

Binary file not shown.

View File

@ -213,94 +213,178 @@ def edit_user_page(user_id):
flash("Fehler beim Laden der Benutzerdaten", "error")
return redirect(url_for('admin.users_overview'))
@admin_blueprint.route("/printers")
@admin_blueprint.route("/users/create", methods=["POST"])
@admin_required
def printers_overview():
"""Druckerübersicht für Administratoren mit Hardware-Integration"""
def create_user():
"""Erstellt einen neuen Benutzer"""
try:
from utils.hardware_integration import get_drucker_steuerung
# Form-Daten aus POST-Request
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
name = request.form.get('name', '').strip()
password = request.form.get('password', '')
role = request.form.get('role', 'user')
department = request.form.get('department', '').strip()
position = request.form.get('position', '').strip()
phone = request.form.get('phone', '').strip()
active = request.form.get('active') == 'on'
# Validierung
if not username or not email or not password:
flash("Benutzername, E-Mail und Passwort sind erforderlich", "error")
return redirect(url_for('admin.add_user_page'))
with get_cached_session() as db_session:
# Alle Drucker laden (nicht nur TBA Marienfelde)
printers = db_session.query(Printer).order_by(Printer.created_at.desc()).all()
# Prüfen ob Benutzer bereits existiert
existing_user = db_session.query(User).filter(
(User.username == username) | (User.email == email)
).first()
# Hardware-Steuerung für Echtzeit-Status
drucker_steuerung = get_drucker_steuerung()
status_data = drucker_steuerung.template_daten_sammeln()
if existing_user:
flash("Benutzer mit diesem Namen oder E-Mail existiert bereits", "error")
return redirect(url_for('admin.add_user_page'))
# Erweiterte Drucker-Informationen mit Hardware-Status
enriched_printers = []
online_count = 0
# Neuen Benutzer erstellen
new_user = User(
username=username,
email=email,
name=name if name else None,
role=role,
department=department if department else None,
position=position if position else None,
phone=phone if phone else None,
active=active,
created_at=datetime.now(),
updated_at=datetime.now()
)
for printer in printers:
printer_data = {
'id': printer.id,
'name': printer.name,
'model': printer.model,
'location': printer.location,
'ip_address': printer.ip_address,
'plug_ip': printer.plug_ip,
'status': printer.status,
'active': printer.active,
'created_at': printer.created_at,
'last_checked': printer.last_checked,
# Hardware-Status hinzufügen
'plug_online': False,
'plug_power_state': 'unknown',
'energy_usage': {},
'has_active_jobs': False,
'active_jobs_count': 0
}
# Hardware-Status aus Steuerung hinzufügen
if printer.id in status_data.get('drucker_status', {}):
hw_status = status_data['drucker_status'][printer.id]
printer_data.update({
'plug_online': hw_status.get('plug_online', False),
'plug_power_state': hw_status.get('plug_state', 'unknown'),
'energy_usage': hw_status.get('energy_usage', {}),
'last_seen': hw_status.get('last_seen')
})
if hw_status.get('plug_online', False):
online_count += 1
# Aktive Jobs für diesen Drucker zählen
active_jobs_count = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(['pending', 'printing', 'scheduled'])
).count()
printer_data['active_jobs_count'] = active_jobs_count
printer_data['has_active_jobs'] = active_jobs_count > 0
enriched_printers.append(printer_data)
# Passwort hashen
new_user.set_password(password)
# Grundlegende Statistiken sammeln
total_users = db_session.query(User).count()
total_printers = len(printers)
total_jobs = db_session.query(Job).count()
db_session.add(new_user)
db_session.commit()
# Aktive Jobs zählen
active_jobs = db_session.query(Job).filter(
Job.status.in_(['pending', 'printing', 'paused'])
).count()
stats = {
'total_users': total_users,
'total_printers': total_printers,
'total_jobs': total_jobs,
'active_jobs': active_jobs,
'online_printers': online_count
}
admin_logger.info(f"Druckerübersicht geladen von {current_user.username}: {total_printers} Drucker, {online_count} online")
return render_template('admin.html', stats=stats, printers=enriched_printers, active_tab='printers')
admin_logger.info(f"Neuer Benutzer '{username}' erstellt von {current_user.username}")
flash(f"Benutzer '{username}' erfolgreich erstellt", "success")
return redirect(url_for('admin.users_overview'))
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Druckerübersicht: {str(e)}")
flash("Fehler beim Laden der Druckerdaten", "error")
return render_template('admin.html', stats={}, printers=[], active_tab='printers')
admin_logger.error(f"Fehler beim Erstellen des Benutzers: {str(e)}")
flash("Fehler beim Erstellen des Benutzers", "error")
return redirect(url_for('admin.add_user_page'))
@admin_blueprint.route("/users/<int:user_id>/update", methods=["POST"])
@admin_required
def update_user(user_id):
"""Aktualisiert einen vorhandenen Benutzer"""
try:
with get_cached_session() as db_session:
user = db_session.query(User).filter(User.id == user_id).first()
if not user:
flash("Benutzer nicht gefunden", "error")
return redirect(url_for('admin.users_overview'))
# Aktualisierung verhindern wenn es der einzige Admin ist
if user.is_admin and request.form.get('role') != 'admin':
admin_count = db_session.query(User).filter(User.role == 'admin').count()
if admin_count <= 1:
flash("Kann den letzten Administrator nicht degradieren", "error")
return redirect(url_for('admin.edit_user_page', user_id=user_id))
# Form-Daten abrufen
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
name = request.form.get('name', '').strip()
role = request.form.get('role', 'user')
department = request.form.get('department', '').strip()
position = request.form.get('position', '').strip()
phone = request.form.get('phone', '').strip()
active = request.form.get('active') == 'on'
new_password = request.form.get('new_password', '').strip()
# Validierung
if not username or not email:
flash("Benutzername und E-Mail sind erforderlich", "error")
return redirect(url_for('admin.edit_user_page', user_id=user_id))
# Prüfen ob Username/Email bereits von anderem Benutzer verwendet
existing_user = db_session.query(User).filter(
User.id != user_id,
(User.username == username) | (User.email == email)
).first()
if existing_user:
flash("Benutzername oder E-Mail bereits von anderem Benutzer verwendet", "error")
return redirect(url_for('admin.edit_user_page', user_id=user_id))
# Benutzer-Daten aktualisieren
user.username = username
user.email = email
user.name = name if name else None
user.role = role
user.department = department if department else None
user.position = position if position else None
user.phone = phone if phone else None
user.active = active
user.updated_at = datetime.now()
# Passwort aktualisieren falls angegeben
if new_password:
user.set_password(new_password)
db_session.commit()
admin_logger.info(f"Benutzer '{username}' (ID: {user_id}) aktualisiert von {current_user.username}")
flash(f"Benutzer '{username}' erfolgreich aktualisiert", "success")
return redirect(url_for('admin.users_overview'))
except Exception as e:
admin_logger.error(f"Fehler beim Aktualisieren des Benutzers: {str(e)}")
flash("Fehler beim Aktualisieren des Benutzers", "error")
return redirect(url_for('admin.edit_user_page', user_id=user_id))
@admin_blueprint.route("/users/<int:user_id>/delete", methods=["POST"])
@admin_required
def delete_user(user_id):
"""Löscht einen Benutzer"""
try:
with get_cached_session() as db_session:
user = db_session.query(User).filter(User.id == user_id).first()
if not user:
flash("Benutzer nicht gefunden", "error")
return redirect(url_for('admin.users_overview'))
# Sich selbst löschen verhindern
if user.id == current_user.id:
flash("Sie können sich nicht selbst löschen", "error")
return redirect(url_for('admin.users_overview'))
# Letzten Admin löschen verhindern
if user.is_admin:
admin_count = db_session.query(User).filter(User.role == 'admin').count()
if admin_count <= 1:
flash("Kann den letzten Administrator nicht löschen", "error")
return redirect(url_for('admin.users_overview'))
username = user.username
# Benutzer löschen
db_session.delete(user)
db_session.commit()
admin_logger.info(f"Benutzer '{username}' (ID: {user_id}) gelöscht von {current_user.username}")
flash(f"Benutzer '{username}' erfolgreich gelöscht", "success")
return redirect(url_for('admin.users_overview'))
except Exception as e:
admin_logger.error(f"Fehler beim Löschen des Benutzers: {str(e)}")
flash("Fehler beim Löschen des Benutzers", "error")
return redirect(url_for('admin.users_overview'))
@admin_blueprint.route("/printers/add")
@admin_required
@ -329,7 +413,7 @@ def add_printer_page():
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Drucker-Hinzufügen-Seite: {str(e)}")
flash("Fehler beim Laden der Seite", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
@admin_blueprint.route("/printers/<int:printer_id>/edit")
@admin_required
@ -343,7 +427,7 @@ def edit_printer_page(printer_id):
if not printer:
flash("Drucker nicht gefunden", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
# Hardware-Status für diesen Drucker abrufen
drucker_steuerung = get_drucker_steuerung()
@ -408,7 +492,7 @@ def edit_printer_page(printer_id):
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Drucker-Bearbeitung: {str(e)}")
flash("Fehler beim Laden der Druckerdaten", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
@admin_blueprint.route("/printers/<int:printer_id>/manage")
@admin_required
@ -422,7 +506,7 @@ def manage_printer_page(printer_id):
if not printer:
flash("Drucker nicht gefunden", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
# Hardware-Steuerung für Echtzeit-Daten
drucker_steuerung = get_drucker_steuerung()
@ -524,7 +608,7 @@ def manage_printer_page(printer_id):
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Drucker-Verwaltung: {str(e)}")
flash("Fehler beim Laden der Drucker-Verwaltung", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
@admin_blueprint.route("/printers/<int:printer_id>/settings")
@admin_required
@ -538,7 +622,7 @@ def printer_settings_page(printer_id):
if not printer:
flash("Drucker nicht gefunden", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
# Hardware-Status für erweiterte Einstellungen
drucker_steuerung = get_drucker_steuerung()
@ -604,7 +688,137 @@ def printer_settings_page(printer_id):
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Drucker-Einstellungen: {str(e)}")
flash("Fehler beim Laden der Drucker-Einstellungen", "error")
return redirect(url_for('admin.printers_overview'))
return redirect(url_for('admin.admin_dashboard'))
@admin_blueprint.route("/printers/<int:printer_id>/configure")
@admin_required
def printer_configuration_page(printer_id):
"""Erweiterte Drucker-Konfigurationsseite - entspricht showPrinterConfiguration() JavaScript-Funktion"""
try:
from utils.hardware_integration import get_drucker_steuerung
with get_cached_session() as db_session:
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
flash("Drucker nicht gefunden", "error")
return redirect(url_for('admin.admin_dashboard'))
# Hardware-Status für erweiterte Konfiguration
drucker_steuerung = get_drucker_steuerung()
status_data = drucker_steuerung.template_daten_sammeln()
# Umfassende Konfigurationsdaten sammeln
config_data = {
# Grunddaten des Druckers
'printer': {
'id': printer.id,
'name': printer.name,
'model': printer.model,
'location': printer.location,
'ip_address': printer.ip_address,
'plug_ip': printer.plug_ip,
'status': printer.status,
'active': printer.active,
'created_at': printer.created_at,
'updated_at': printer.updated_at,
'last_checked': printer.last_checked,
'description': getattr(printer, 'description', ''),
},
# Hardware-Status und Energiemonitoring
'hardware_status': status_data.get('drucker_status', {}).get(printer.id, {}),
# Konfigurationskategorien
'config_categories': {
'basic': {
'name': 'Grundeinstellungen',
'description': 'Name, Modell, Standort',
'icon': 'cog'
},
'network': {
'name': 'Netzwerk-Konfiguration',
'description': 'IP-Adressen, Verbindungseinstellungen',
'icon': 'wifi'
},
'hardware': {
'name': 'Hardware-Integration',
'description': 'Smart-Plug, Sensoren, Monitoring',
'icon': 'cpu'
},
'automation': {
'name': 'Automatisierung',
'description': 'Auto-Power, Zeitpläne, Benachrichtigungen',
'icon': 'zap'
},
'maintenance': {
'name': 'Wartung & Diagnostik',
'description': 'Kalibrierung, Tests, Logs',
'icon': 'tool'
},
'advanced': {
'name': 'Erweiterte Optionen',
'description': 'Experten-Einstellungen, Debug-Modi',
'icon': 'settings'
}
},
# Live-Verbindungsstatus
'connectivity': {
'printer_reachable': False,
'plug_reachable': False,
'last_ping': None,
'response_time': None
},
# Energieverbrauchshistorie
'energy_history': [],
# Verfügbare Aktionen
'available_actions': [
'test_connection',
'reset_settings',
'calibrate',
'factory_reset',
'export_config',
'import_config'
]
}
# Live-Konnektivitätstests
try:
if printer.plug_ip:
reachable, plug_status = drucker_steuerung.check_outlet_status(printer.plug_ip, printer_id=printer.id)
config_data['connectivity']['plug_reachable'] = reachable
config_data['connectivity']['plug_status'] = plug_status
except Exception as connectivity_error:
admin_logger.warning(f"Konnektivitätstest für Drucker {printer.id} fehlgeschlagen: {str(connectivity_error)}")
# Grundlegende Statistiken für das Template
total_users = db_session.query(User).count()
total_printers = db_session.query(Printer).count()
total_jobs = db_session.query(Job).count()
active_jobs = db_session.query(Job).filter(
Job.status.in_(['pending', 'printing', 'paused'])
).count()
stats = {
'total_users': total_users,
'total_printers': total_printers,
'total_jobs': total_jobs,
'active_jobs': active_jobs
}
admin_logger.info(f"Drucker-Konfiguration für '{printer.name}' aufgerufen von {current_user.username}")
return render_template('admin_printer_configuration.html',
config_data=config_data,
stats=stats,
active_tab='printers')
except Exception as e:
admin_logger.error(f"Fehler beim Laden der Drucker-Konfiguration: {str(e)}")
flash("Fehler beim Laden der Drucker-Konfiguration", "error")
return redirect(url_for('admin.admin_dashboard'))
@admin_blueprint.route("/guest-requests")
@admin_required
@ -3839,3 +4053,202 @@ def get_users_api():
admin_api_logger.error(f"Fehler beim Abrufen der Benutzer: {str(e)}")
return jsonify({"error": "Fehler beim Abrufen der Benutzer"}), 500
# ===== SYSTEM ERROR RECOVERY ENDPOINTS =====
@admin_api_blueprint.route('/error-recovery/status', methods=['GET'])
@admin_required
def error_recovery_status():
"""
Gibt den Status des Error-Recovery-Systems zurück.
Returns:
JSON: Error-Recovery-Status und verfügbare Aktionen
"""
try:
admin_api_logger.debug(f"Error-Recovery-Status angefordert von {current_user.username}")
# Sammle System-Gesundheitsdaten
with get_cached_session() as db_session:
# Aktuelle Datenbankverbindung testen
try:
db_session.execute(text("SELECT 1"))
db_health = True
except Exception:
db_health = False
# Anzahl laufender Jobs prüfen
try:
running_jobs = db_session.query(Job).filter(
Job.status.in_(['printing', 'pending', 'paused'])
).count()
except Exception:
running_jobs = 0
# Anzahl Drucker mit Problemen
try:
offline_printers = db_session.query(Printer).filter(
Printer.status.in_(['offline', 'error', 'unknown'])
).count()
except Exception:
offline_printers = 0
# Verfügbare Recovery-Aktionen bestimmen
available_actions = []
if not db_health:
available_actions.extend(['restart_db', 'repair_db'])
if running_jobs == 0:
available_actions.append('restart_services')
if offline_printers > 0:
available_actions.append('reconnect_printers')
# Immer verfügbar
available_actions.extend(['clear_cache', 'check_logs', 'system_health'])
recovery_status = {
'system_healthy': db_health and running_jobs == 0 and offline_printers == 0,
'database_status': 'healthy' if db_health else 'error',
'running_jobs': running_jobs,
'offline_printers': offline_printers,
'available_actions': available_actions,
'last_check': datetime.now().isoformat()
}
return jsonify({
'success': True,
'recovery_status': recovery_status
})
except Exception as e:
admin_api_logger.error(f"Fehler beim Abrufen des Error-Recovery-Status: {str(e)}")
return jsonify({
'success': False,
'error': f'Fehler beim Abrufen des Status: {str(e)}'
}), 500
@admin_api_blueprint.route('/error-recovery/toggle', methods=['POST'])
@admin_required
def error_recovery_toggle():
"""
Schaltet Error-Recovery-Funktionen um oder führt Recovery-Aktionen durch.
Request JSON:
action (str): Die auszuführende Aktion ('restart_db', 'clear_cache', etc.)
Returns:
JSON: Erfolgsstatus und Details der durchgeführten Aktion
"""
try:
data = request.get_json()
action = data.get('action')
if not action:
return jsonify({
'success': False,
'error': 'Keine Aktion angegeben'
}), 400
admin_api_logger.info(f"Error-Recovery-Aktion '{action}' angefordert von {current_user.username}")
result = {
'action': action,
'performed_by': current_user.username,
'timestamp': datetime.now().isoformat(),
'details': {}
}
# Führe angeforderte Aktion durch
if action == 'clear_cache':
# Cache leeren
try:
from utils.utilities_collection import clear_cache
clear_cache()
result['details']['cache_cleared'] = True
result['message'] = 'Cache erfolgreich geleert'
except Exception as e:
result['details']['cache_error'] = str(e)
result['message'] = f'Fehler beim Cache-Leeren: {str(e)}'
elif action == 'restart_db':
# Datenbank-Verbindungen neu starten
try:
from models import engine
engine.dispose() # Alle Verbindungen schließen
result['details']['db_connections_reset'] = True
result['message'] = 'Datenbankverbindungen erfolgreich neu gestartet'
except Exception as e:
result['details']['db_error'] = str(e)
result['message'] = f'Fehler beim DB-Neustart: {str(e)}'
elif action == 'reconnect_printers':
# Drucker-Verbindungen neu aufbauen
try:
with get_cached_session() as db_session:
offline_printers = db_session.query(Printer).filter(
Printer.status.in_(['offline', 'error', 'unknown'])
).all()
reconnected = 0
for printer in offline_printers:
try:
# Einfache Reconnect-Logic
printer.status = 'idle'
printer.last_checked = datetime.now()
reconnected += 1
except Exception:
pass
db_session.commit()
result['details']['printers_reconnected'] = reconnected
result['message'] = f'{reconnected} Drucker erfolgreich reconnected'
except Exception as e:
result['details']['reconnect_error'] = str(e)
result['message'] = f'Fehler beim Drucker-Reconnect: {str(e)}'
elif action == 'system_health':
# System-Health-Check durchführen
try:
health_check = {
'memory_usage': 'OK',
'disk_space': 'OK',
'database_size': 'OK',
'service_status': 'OK'
}
# Einfache Checks
try:
import psutil
memory_percent = psutil.virtual_memory().percent
health_check['memory_usage'] = f'{memory_percent:.1f}%'
disk_percent = psutil.disk_usage('/').percent
health_check['disk_space'] = f'{disk_percent:.1f}%'
except ImportError:
health_check['system_monitoring'] = 'psutil nicht verfügbar'
result['details']['health_check'] = health_check
result['message'] = 'System-Health-Check durchgeführt'
except Exception as e:
result['details']['health_error'] = str(e)
result['message'] = f'Fehler beim Health-Check: {str(e)}'
else:
return jsonify({
'success': False,
'error': f'Unbekannte Aktion: {action}'
}), 400
return jsonify({
'success': True,
'result': result
})
except Exception as e:
admin_api_logger.error(f"Fehler bei Error-Recovery-Toggle: {str(e)}")
return jsonify({
'success': False,
'error': f'Systemfehler: {str(e)}'
}), 500

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More