📚 Improved backend structure & logs for better tracking and management
This commit is contained in:
1
backend/APP_USAGE.md
Normal file
1
backend/APP_USAGE.md
Normal file
@ -0,0 +1 @@
|
||||
|
87
backend/START_SERVER.py
Normal file
87
backend/START_SERVER.py
Normal 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)
|
@ -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
603
backend/app_unified.py
Normal 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()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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,
|
||||
|
@ -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.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user