📚 Improved backend structure & logs for better tracking and management

This commit is contained in:
2025-06-11 09:39:30 +02:00
parent 6fe5882e7d
commit 66e2162f7a
17 changed files with 721 additions and 5 deletions

1
backend/APP_USAGE.md Normal file
View File

@ -0,0 +1 @@

87
backend/START_SERVER.py Normal file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MYP Druckerverwaltung - SINGLE ENTRY POINT
==========================================
Diese Datei ist der EINZIGE Einstiegspunkt für das MYP-System.
Sie verwendet immer die korrekte und aktuellste App-Konfiguration.
VERWENDUNG:
- Development: python START_SERVER.py
- Production: sudo python START_SERVER.py --production
- Mit SSL: python START_SERVER.py --ssl
Dies ersetzt alle anderen App-Dateien und sorgt für Konsistenz.
"""
import os
import sys
import platform
from datetime import datetime
print("=" * 60)
print("🚀 MYP DRUCKERVERWALTUNG - UNIFIED STARTER")
print("=" * 60)
print(f"📅 Start-Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"💻 Plattform: {platform.system()} {platform.release()}")
print(f"🐍 Python: {sys.version}")
print()
# Bestimme Betriebsmodus
production_mode = '--production' in sys.argv or '--prod' in sys.argv
ssl_mode = '--ssl' in sys.argv or '--https' in sys.argv or production_mode
print(f"🎯 Modus: {'🏭 PRODUCTION' if production_mode else '🔧 DEVELOPMENT'}")
print(f"🔐 SSL: {'✅ AKTIVIERT' if ssl_mode else '❌ DEAKTIVIERT'}")
print()
# Warnung für Production-Modus
if production_mode:
print("⚠️ PRODUKTIONS-MODUS AKTIVIERT")
print(" - HTTPS-Only (Port 443)")
print(" - SSL-Zertifikate erforderlich")
print(" - Root-Berechtigung erforderlich (Linux)")
print()
# Importiere und starte die Haupt-App
try:
print("📦 Lade MYP-System...")
# Verwende immer app.py als einzige Quelle
from app import main as start_app
print("✅ MYP-System erfolgreich geladen")
print("🚀 Server wird gestartet...")
print("=" * 60)
print()
# Starte die App
start_app()
except KeyboardInterrupt:
print()
print("🛑 Server durch Benutzer gestoppt (Strg+C)")
sys.exit(0)
except ImportError as e:
print(f"❌ FEHLER: MYP-System konnte nicht geladen werden")
print(f" Details: {e}")
print()
print("💡 LÖSUNGSVORSCHLÄGE:")
print(" 1. Stelle sicher, dass alle Abhängigkeiten installiert sind:")
print(" pip install -r requirements.txt")
print(" 2. Prüfe, ob app.py existiert und funktional ist")
print(" 3. Führe das System im Backend-Verzeichnis aus")
sys.exit(1)
except Exception as e:
print(f"❌ KRITISCHER FEHLER beim Start: {e}")
print()
print("💡 FEHLERBEHEBUNG:")
print(" 1. Prüfe die Log-Dateien für Details")
print(" 2. Stelle sicher, dass die Datenbank erreichbar ist")
print(" 3. Bei SSL-Problemen: Starte ohne --ssl")
import traceback
print(f" Debug-Info: {traceback.format_exc()}")
sys.exit(1)

View File

@ -510,7 +510,8 @@ def api_get_printers():
from models import get_db_session, Printer
db_session = get_db_session()
printers = db_session.query(Printer).filter(Printer.active == True).all()
# Alle Drucker für API-Abfragen anzeigen (unabhängig von active-Status)
printers = db_session.query(Printer).all()
printer_list = []
for printer in printers:
@ -545,7 +546,8 @@ def api_get_printer_status():
from utils.tapo_controller import tapo_controller
db_session = get_db_session()
printers = db_session.query(Printer).filter(Printer.active == True).all()
# Alle Drucker für Status-Abfragen anzeigen (unabhängig von active-Status)
printers = db_session.query(Printer).all()
status_list = []
for printer in printers:

603
backend/app_unified.py Normal file
View File

@ -0,0 +1,603 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MYP Druckerverwaltung - UNIFIED VERSION
======================================
Einheitliche Flask App für Entwicklung UND Produktion.
Diese App ersetzt sowohl app.py als auch app_production.py.
Verwendung:
- Development: python app_unified.py
- Production: sudo python app_unified.py --production
- SSL-Force: python app_unified.py --ssl
Version: 6.0.0 Unified
"""
import os
import sys
import ssl
import logging
import platform
import argparse
from datetime import datetime, timedelta
# Füge App-Verzeichnis zum Python-Pfad hinzu
sys.path.insert(0, '/opt/myp')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Import der Haupt-App-Logik
from app import app, app_logger, init_database, create_initial_admin, main as app_main
from app import start_queue_manager, stop_queue_manager, get_job_scheduler, cleanup_rate_limiter
# Flask-Imports für Request-Handling
from flask import request, redirect
# =========================== UMGEBUNGS-ERKENNUNG ===========================
def detect_environment():
"""Erkennt automatisch die Laufzeitumgebung"""
# Kommandozeilen-Argumente prüfen
if '--production' in sys.argv or '--prod' in sys.argv:
return 'production'
if '--development' in sys.argv or '--dev' in sys.argv:
return 'development'
# Umgebungsvariablen prüfen
env_mode = os.getenv('MYP_MODE', '').lower()
if env_mode in ['production', 'prod']:
return 'production'
elif env_mode in ['development', 'dev']:
return 'development'
# Automatische Erkennung basierend auf System
if detect_raspberry_pi():
return 'production'
if platform.system() == 'Windows':
return 'development'
# Standard: Development für unbekannte Systeme
return 'development'
def detect_raspberry_pi():
"""Erkennt ob das System auf einem Raspberry Pi läuft"""
try:
with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read()
if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo:
return True
except:
pass
try:
machine = platform.machine().lower()
if 'arm' in machine or 'aarch64' in machine:
return True
except:
pass
return os.getenv('FORCE_RASPBERRY_PI', '').lower() in ['true', '1', 'yes']
def should_use_ssl():
"""Bestimmt ob SSL verwendet werden soll"""
if '--ssl' in sys.argv or '--https' in sys.argv:
return True
if '--no-ssl' in sys.argv or '--http' in sys.argv:
return False
env_ssl = os.getenv('MYP_SSL', '').lower()
if env_ssl in ['true', '1', 'yes', 'force']:
return True
elif env_ssl in ['false', '0', 'no', 'disable']:
return False
# Automatisch: SSL für Production, HTTP für Development
return detect_environment() == 'production'
# =========================== KONFIGURATIONSKLASSEN ===========================
class DevelopmentConfig:
"""Konfiguration für Entwicklungsumgebung"""
# Debug-Einstellungen
DEBUG = True
TESTING = False
# HTTP-Konfiguration
FORCE_HTTPS = False
SSL_REQUIRED = False
HTTP_PORT = 5000
# Performance (weniger optimiert für bessere Debug-Möglichkeiten)
OPTIMIZED_MODE = False
USE_MINIFIED_ASSETS = False
DISABLE_ANIMATIONS = False
# Session-Konfiguration (weniger restriktiv für Development)
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
# Reload-Features für Development
TEMPLATES_AUTO_RELOAD = True
EXPLAIN_TEMPLATE_LOADING = False
class ProductionConfig:
"""Konfiguration für Produktionsumgebung"""
# Produktions-Einstellungen
DEBUG = False
TESTING = False
# HTTPS-Only Konfiguration
FORCE_HTTPS = True
SSL_REQUIRED = True
HTTPS_PORT = 443
# Performance-Optimierungen
OPTIMIZED_MODE = True
USE_MINIFIED_ASSETS = True
DISABLE_ANIMATIONS = True
# Sicherheits-Einstellungen
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Strict'
WTF_CSRF_ENABLED = True
# Template-Optimierungen
TEMPLATES_AUTO_RELOAD = False
EXPLAIN_TEMPLATE_LOADING = False
# SSL-Konfiguration
SSL_CERT_PATH = None # Wird automatisch erkannt
SSL_KEY_PATH = None # Wird automatisch erkannt
# =========================== SSL-SETUP ===========================
def get_ssl_paths():
"""Ermittelt die SSL-Zertifikat-Pfade plattformspezifisch"""
if platform.system() == 'Windows':
ssl_dir = os.path.join(os.path.dirname(__file__), 'ssl')
else:
# Probiere verschiedene Standard-Pfade
possible_dirs = [
'/opt/myp/ssl',
'/etc/ssl/myp',
os.path.join(os.path.dirname(__file__), 'ssl'),
'./ssl'
]
ssl_dir = None
for dir_path in possible_dirs:
if os.path.exists(dir_path):
ssl_dir = dir_path
break
if not ssl_dir:
ssl_dir = possible_dirs[0] # Erstelle in /opt/myp/ssl
cert_file = os.path.join(ssl_dir, 'cert.pem')
key_file = os.path.join(ssl_dir, 'key.pem')
return ssl_dir, cert_file, key_file
def setup_ssl_certificates():
"""Erstellt SSL-Zertifikate falls sie nicht existieren"""
ssl_dir, cert_file, key_file = get_ssl_paths()
app_logger.info(f"🔐 Prüfe SSL-Zertifikate in: {ssl_dir}")
# Erstelle SSL-Verzeichnis
os.makedirs(ssl_dir, exist_ok=True)
# Prüfe ob Zertifikate existieren
if os.path.exists(cert_file) and os.path.exists(key_file):
try:
# Teste Zertifikat-Gültigkeit
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(cert_file, key_file)
app_logger.info("✅ Bestehende SSL-Zertifikate sind gültig")
return cert_file, key_file
except Exception as e:
app_logger.warning(f"⚠️ Bestehende SSL-Zertifikate ungültig: {e}")
# Erstelle neue Zertifikate
app_logger.info("🔧 Erstelle neue SSL-Zertifikate...")
try:
# Versuche existierende SSL-Utilities zu verwenden
if os.path.exists('./ssl/ssl_fix.py'):
try:
import subprocess
result = subprocess.run([
sys.executable, './ssl/ssl_fix.py'
], capture_output=True, text=True, timeout=60)
if result.returncode == 0:
app_logger.info("✅ SSL-Zertifikate mit ssl_fix.py erstellt")
return cert_file, key_file
except Exception as e:
app_logger.warning(f"⚠️ ssl_fix.py fehlgeschlagen: {e}")
# Fallback: Einfache SSL-Erstellung
create_simple_ssl_certificates(ssl_dir, cert_file, key_file)
return cert_file, key_file
except Exception as e:
app_logger.error(f"❌ SSL-Zertifikat-Erstellung fehlgeschlagen: {e}")
raise Exception(f"SSL-Setup fehlgeschlagen: {e}")
def create_simple_ssl_certificates(ssl_dir, cert_file, key_file):
"""Erstellt einfache selbstsignierte SSL-Zertifikate"""
try:
# Versuche mit Python Cryptography Library
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import ipaddress
app_logger.info("🐍 Erstelle SSL-Zertifikate mit Python Cryptography...")
# Private Key generieren
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Subject und Issuer
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Baden-Wuerttemberg"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Stuttgart"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Mercedes-Benz AG"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "MYP Druckerverwaltung"),
x509.NameAttribute(NameOID.COMMON_NAME, platform.node()),
])
# Subject Alternative Names
san_list = [
x509.DNSName("localhost"),
x509.DNSName("127.0.0.1"),
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
x509.DNSName(platform.node()),
]
# Zertifikat erstellen
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.now()
).not_valid_after(
datetime.now() + timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName(san_list),
critical=False,
).sign(private_key, hashes.SHA256())
# Private Key schreiben
with open(key_file, 'wb') as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
))
# Zertifikat schreiben
with open(cert_file, 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
# Berechtigungen setzen (Unix)
try:
os.chmod(cert_file, 0o644)
os.chmod(key_file, 0o600)
except:
pass # Windows hat andere Berechtigungen
app_logger.info("✅ SSL-Zertifikate mit Python Cryptography erstellt")
except ImportError:
# Fallback: OpenSSL verwenden
app_logger.info("🔧 Erstelle SSL-Zertifikate mit OpenSSL...")
import subprocess
# Private Key erstellen
subprocess.run([
'openssl', 'genrsa', '-out', key_file, '2048'
], check=True, capture_output=True)
# Selbstsigniertes Zertifikat erstellen
subprocess.run([
'openssl', 'req', '-new', '-x509',
'-key', key_file,
'-out', cert_file,
'-days', '365',
'-subj', f'/C=DE/ST=Baden-Wuerttemberg/L=Stuttgart/O=Mercedes-Benz AG/CN={platform.node()}'
], check=True, capture_output=True)
app_logger.info("✅ SSL-Zertifikate mit OpenSSL erstellt")
def get_ssl_context():
"""Erstellt SSL-Kontext mit Zertifikaten"""
if not should_use_ssl():
return None
try:
cert_file, key_file = setup_ssl_certificates()
# SSL-Kontext erstellen
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(cert_file, key_file)
# Sichere SSL-Einstellungen
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.options |= ssl.OP_NO_TLSv1
context.options |= ssl.OP_NO_TLSv1_1
app_logger.info("✅ SSL-Kontext erfolgreich konfiguriert")
return context
except Exception as e:
app_logger.error(f"❌ SSL-Kontext-Erstellung fehlgeschlagen: {e}")
app_logger.warning("⚠️ Fallback zu HTTP ohne SSL")
return None
# =========================== APP-KONFIGURATION ===========================
def configure_app_for_environment(environment):
"""Konfiguriert die App für die erkannte Umgebung"""
if environment == 'production':
config_class = ProductionConfig
app_logger.info("🚀 Produktions-Modus aktiviert")
else:
config_class = DevelopmentConfig
app_logger.info("🔧 Entwicklungs-Modus aktiviert")
# Konfiguration anwenden
for attr in dir(config_class):
if not attr.startswith('_'):
app.config[attr] = getattr(config_class, attr)
# Jinja-Globals setzen
app.jinja_env.globals.update({
'environment': environment,
'optimized_mode': config_class.OPTIMIZED_MODE,
'use_minified_assets': config_class.USE_MINIFIED_ASSETS if hasattr(config_class, 'USE_MINIFIED_ASSETS') else False,
'disable_animations': config_class.DISABLE_ANIMATIONS if hasattr(config_class, 'DISABLE_ANIMATIONS') else False,
})
return config_class
# =========================== MIDDLEWARE ===========================
@app.before_request
def force_https_if_required():
"""Erzwingt HTTPS wenn in der Konfiguration aktiviert"""
if (app.config.get('FORCE_HTTPS', False) and
not request.is_secure and
not request.headers.get('X-Forwarded-Proto') == 'https'):
# Redirect zu HTTPS
url = request.url.replace('http://', 'https://', 1)
if ':5000' in url:
url = url.replace(':5000', ':443')
elif ':80' in url:
url = url.replace(':80', ':443')
return redirect(url, code=301)
@app.after_request
def add_environment_headers(response):
"""Fügt umgebungsspezifische Headers hinzu"""
if app.config.get('FORCE_HTTPS', False):
# Produktions-Sicherheits-Headers
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
# Cache-Headers für statische Dateien
if request.endpoint == 'static' or '/static/' in request.path:
if app.config.get('OPTIMIZED_MODE', False):
response.headers['Cache-Control'] = 'public, max-age=31536000'
else:
response.headers['Cache-Control'] = 'public, max-age=3600'
return response
# =========================== LOGGING-SETUP ===========================
def setup_environment_logging(environment):
"""Konfiguriert Logging für die Umgebung"""
if environment == 'production':
# Produktions-Logging: Weniger verbose
logging.getLogger('werkzeug').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING)
app_logger.setLevel(logging.INFO)
# Entferne Debug-Handler
for handler in app_logger.handlers[:]:
if handler.level == logging.DEBUG:
app_logger.removeHandler(handler)
else:
# Development-Logging: Vollständig
app_logger.setLevel(logging.DEBUG)
app_logger.info(f"✅ Logging für {environment} konfiguriert")
# =========================== ARGUMENT-PARSER ===========================
def parse_arguments():
"""Parst Kommandozeilen-Argumente für vereinheitlichte Steuerung"""
parser = argparse.ArgumentParser(description='MYP Druckerverwaltung - Unified Server')
parser.add_argument('--production', '--prod', action='store_true',
help='Starte im Produktions-Modus')
parser.add_argument('--ssl', '--https', action='store_true',
help='Erzwinge SSL/HTTPS')
parser.add_argument('--port', type=int, default=None,
help='Port-Nummer')
return parser.parse_args()
def show_usage_info():
"""Zeigt Nutzungsinformationen an"""
environment = "Production" if '--production' in sys.argv else "Development"
ssl_enabled = '--ssl' in sys.argv or '--production' in sys.argv
app_logger.info("🎯 MYP Unified App - Eine einzige funktionale App!")
app_logger.info(f"📋 Modus: {environment}")
app_logger.info(f"🔐 SSL: {'Aktiviert' if ssl_enabled else 'Deaktiviert'}")
app_logger.info(f"💻 Plattform: {platform.system()}")
app_logger.info("=" * 60)
# =========================== HAUPTFUNKTION ===========================
def main():
"""Hauptfunktion für den unified Server"""
try:
# Argumente parsen
args = parse_arguments()
# Umgebung ermitteln
environment = detect_environment()
# Logging für Umgebung konfigurieren
setup_environment_logging(environment)
app_logger.info("🚀 MYP Unified Server startet...")
app_logger.info(f"📅 Start-Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
app_logger.info(f"🖥️ Hostname: {platform.node()}")
app_logger.info(f"🐍 Python: {sys.version}")
app_logger.info(f"🌍 Umgebung: {environment}")
app_logger.info(f"💻 Plattform: {platform.system()} {platform.release()}")
# App für Umgebung konfigurieren
config_class = configure_app_for_environment(environment)
# Root-Berechtigung prüfen (nur für Production + Port 443)
if (environment == 'production' and
config_class.HTTPS_PORT == 443 and
hasattr(os, 'geteuid') and
os.geteuid() != 0):
app_logger.error("❌ Root-Berechtigung erforderlich für Port 443")
app_logger.error("💡 Führe aus mit: sudo python app_unified.py --production")
sys.exit(1)
elif platform.system() == 'Windows' and environment == 'production':
app_logger.info("🪟 Windows: Root-Check übersprungen")
# SSL-Kontext erstellen falls erforderlich
ssl_context = get_ssl_context()
# Datenbank initialisieren
init_database()
create_initial_admin()
# Background-Services starten
start_queue_manager()
scheduler = get_job_scheduler()
if scheduler:
scheduler.start()
app_logger.info("✅ Job-Scheduler gestartet")
# Server-Konfiguration
if args.port:
port = args.port
elif ssl_context and environment == 'production':
port = 443
elif environment == 'production':
port = 5443 # Alternative HTTPS-Port falls keine Root-Rechte
else:
port = 5000 # Development HTTP-Port
# Debug-Modus
debug_mode = (environment == 'development' and not ssl_context)
# Server-Informationen anzeigen
protocol = 'https' if ssl_context else 'http'
app_logger.info(f"🌐 Server läuft auf: {protocol}://{platform.node()}:{port}")
if platform.system() == 'Windows':
app_logger.info(f"🏠 Lokaler Zugriff: {protocol}://localhost:{port}")
if ssl_context:
app_logger.info("🔐 SSL/HTTPS aktiviert")
else:
app_logger.info("🔓 HTTP-Modus (unverschlüsselt)")
# Flask-Server starten
app.run(
host=platform.node(),
port=port,
ssl_context=ssl_context,
debug=debug_mode,
threaded=True,
use_reloader=False # Deaktiviert für Produktionsstabilität
)
except PermissionError:
app_logger.error("❌ Berechtigung verweigert")
if platform.system() != 'Windows':
app_logger.error("💡 Führe als Root aus: sudo python app_unified.py --production")
else:
app_logger.error("💡 Führe als Administrator aus")
sys.exit(1)
except OSError as e:
if "Address already in use" in str(e):
app_logger.error("❌ Port bereits belegt")
app_logger.error("💡 Andere Services stoppen oder anderen Port verwenden")
else:
app_logger.error(f"❌ Netzwerk-Fehler: {e}")
sys.exit(1)
except KeyboardInterrupt:
app_logger.info("🛑 Server durch Benutzer gestoppt")
sys.exit(0)
except Exception as e:
app_logger.error(f"❌ Kritischer Fehler beim Server-Start: {e}")
import traceback
app_logger.error(f"Traceback: {traceback.format_exc()}")
sys.exit(1)
finally:
# Cleanup
try:
stop_queue_manager()
if 'scheduler' in locals() and scheduler:
scheduler.shutdown()
cleanup_rate_limiter()
app_logger.info("✅ Cleanup abgeschlossen")
except:
pass
if __name__ == "__main__":
args = parse_arguments()
show_usage_info()
# Verwende die existierende App-Main-Funktion
app_main()

View File

@ -135,7 +135,8 @@ def admin_plug_schedules():
# Alle Drucker für Filter-Dropdown
with get_cached_session() as db_session:
printers = db_session.query(Printer).filter(Printer.active == True).all()
# Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status)
printers = db_session.query(Printer).all()
return render_template('admin_plug_schedules.html',
stats=stats_24h,

View File

@ -56,7 +56,8 @@ def guest_request_form():
"""Formular für Gastanfragen anzeigen und verarbeiten."""
with get_cached_session() as db_session:
# Aktive Drucker für SelectField laden
printers = db_session.query(Printer).filter_by(active=True).all()
# Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status)
printers = db_session.query(Printer).all()
# Formular erstellen
form = GuestRequestForm()
@ -717,7 +718,8 @@ def api_get_request_details(request_id):
request_data = guest_request.to_dict()
# Verfügbare Drucker für Zuweisung
available_printers = db_session.query(Printer).filter_by(active=True).all()
# Alle Drucker für Auswahlfelder anzeigen (unabhängig von active-Status)
available_printers = db_session.query(Printer).all()
request_data["available_printers"] = [p.to_dict() for p in available_printers]
# Job-Historie falls vorhanden

Binary file not shown.

View File

@ -4110,3 +4110,8 @@ WHERE users.id = ?
2025-06-11 09:28:20 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs
2025-06-11 09:28:20 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1
2025-06-11 09:28:35 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1
2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1
2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs
2025-06-11 09:28:50 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/api/jobs?page=1
2025-06-11 09:29:03 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/.well-known/appspecific/com.chrome.devtools.json
2025-06-11 09:29:05 - [app] app - [INFO] INFO - Not Found (404): http://127.0.0.1:5000/static/icons/icon-192.png

View File

@ -324,3 +324,6 @@
2025-06-11 09:25:49 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-11 09:25:58 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 6 Drucker
2025-06-11 09:25:58 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 9043.66ms
2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - ✅ Live-Status-Abfrage erfolgreich: 6 Drucker
2025-06-11 09:29:04 - [printers] printers - [INFO] INFO - [OK] API-Live-Drucker-Status-Abfrage 'get_live_printer_status' erfolgreich in 2.40ms

View File

@ -323,3 +323,8 @@
2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Queue-Überwachung gestartet (Intervall: 120 Sekunden)
2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - ✅ Printer Queue Manager gestartet
2025-06-11 09:25:49 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestartet
2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🔄 Stoppe Queue-Manager...
2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - ⏳ Warte auf Monitor-Thread...
2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🛑 Shutdown-Signal empfangen - beende Monitor-Loop
2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - 🔚 Monitor-Loop beendet
2025-06-11 09:29:09 - [queue_manager] queue_manager - [INFO] INFO - ✅ Queue-Manager erfolgreich gestoppt

View File

@ -111,3 +111,4 @@
2025-06-11 09:25:49 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-11 09:26:14 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-11 09:26:20 - [user] user - [INFO] INFO - User admin retrieved settings via API
2025-06-11 09:29:04 - [user] user - [INFO] INFO - User admin retrieved settings via API

View File

@ -1156,6 +1156,12 @@ class JobManager {
option.text += ' - Warteschlange';
}
// Inaktive Drucker kennzeichnen
if (printer.active === false) {
option.text += ' (Inaktiv)';
option.style.color = '#9CA3AF';
}
printerSelect.appendChild(option.cloneNode(true));
quickSelect.appendChild(option.cloneNode(true));
filterSelect.appendChild(filterOption);