diff --git a/backend/COMMON_ERRORS.md b/backend/COMMON_ERRORS.md new file mode 100644 index 00000000..54af8f57 --- /dev/null +++ b/backend/COMMON_ERRORS.md @@ -0,0 +1,131 @@ +# Häufige Fehler und Lösungen + +## API-Route-Fehler + +### 1. Blueprint nicht registriert +**Problem:** API-Blueprint wird nicht registriert, führt zu 404-Fehlern +**Lösung:** Blueprint in app.py importieren und registrieren: +```python +from blueprints.api import api_bp +app.register_blueprint(api_bp) +``` + +### 2. Fehlende CSRF-Token bei POST-Requests +**Problem:** CSRF-Validierung schlägt fehl +**Lösung:** CSRF-Token in Templates einbinden oder API-Routen von CSRF befreien + +### 3. Database Session nicht geschlossen +**Problem:** Database connections leak +**Lösung:** Immer try/finally verwenden: +```python +db_session = get_db_session() +try: + # Database operations + pass +finally: + db_session.close() +``` + +### 4. Fehlende Authentifizierung +**Problem:** @login_required decorator fehlt +**Lösung:** Alle geschützten Routen mit @login_required versehen + +### 5. Falsche JSON-Response-Struktur +**Problem:** Frontend erwartet andere Datenstruktur +**Lösung:** Konsistente API-Response-Struktur verwenden: +```python +return jsonify({ + "success": True/False, + "data": {...}, + "error": "error message" # nur bei Fehlern +}) +``` + +## Datenbankfehler + +### 1. Relationship not loaded +**Problem:** Lazy loading von Relationships +**Lösung:** Eager loading verwenden: +```python +from sqlalchemy.orm import joinedload +jobs = session.query(Job).options(joinedload(Job.user)).all() +``` + +### 2. Session closed before accessing relationships +**Problem:** Zugriff auf Relationships nach Session.close() +**Lösung:** Alle benötigten Daten vor Session.close() laden + +## Logging-Fehler + +### 1. Logger nicht initialisiert +**Problem:** Logging funktioniert nicht +**Lösung:** Logger korrekt initialisieren: +```python +from utils.logging_config import get_logger +logger = get_logger("component_name") +``` + +## File-Upload-Fehler + +### 1. Upload-Ordner existiert nicht +**Problem:** FileNotFoundError beim Upload +**Lösung:** Ordner erstellen: +```python +os.makedirs(upload_folder, exist_ok=True) +``` + +### 2. Unsichere Dateinamen +**Problem:** Path traversal vulnerability +**Lösung:** secure_filename() verwenden: +```python +from werkzeug.utils import secure_filename +filename = secure_filename(file.filename) +``` + +## Frontend-Integration-Fehler + +### 1. CORS-Probleme +**Problem:** Cross-Origin-Requests werden blockiert +**Lösung:** CORS-Headers setzen oder Flask-CORS verwenden + +### 2. Inkonsistente API-Endpunkte +**Problem:** Frontend ruft nicht existierende Endpunkte auf +**Lösung:** Systematische Überprüfung aller Frontend-API-Calls + +### 3. Fehlende Error-Handling +**Problem:** Frontend kann Fehler nicht verarbeiten +**Lösung:** Konsistente Error-Response-Struktur implementieren + +### 4. Admin-Dashboard-Fehler +**Problem:** Admin-Dashboard API-Routen fehlen +**Lösung:** Vollständige Admin-API implementieren: +```python +@app.route("/api/admin/users/create", methods=["POST"]) +@login_required +def api_admin_create_user(): + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + # Implementation... +``` + +### 5. Fehlende Berechtigungsprüfung +**Problem:** Admin-Routen ohne Berechtigungsprüfung +**Lösung:** Immer Admin-Check einbauen: +```python +if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 +``` + +## Performance-Probleme + +### 1. N+1 Query Problem +**Problem:** Zu viele Datenbankabfragen +**Lösung:** Eager loading oder batch loading verwenden + +### 2. Fehlende Indizes +**Problem:** Langsame Datenbankabfragen +**Lösung:** Indizes auf häufig abgefragte Spalten erstellen + +### 3. Große Response-Größen +**Problem:** Langsame API-Responses +**Lösung:** Pagination und Feldfilterung implementieren \ No newline at end of file diff --git a/backend/ROADMAP.md b/backend/ROADMAP.md index f6103402..26814de6 100644 --- a/backend/ROADMAP.md +++ b/backend/ROADMAP.md @@ -29,6 +29,29 @@ MYP V2 ist ein 3D-Drucker-Management-System mit automatischer Smart Plug-Steueru - **Benutzer-Management**: Admin-Funktionen - **Statistiken**: Systemmetriken und Berichte - **Scheduler-Steuerung**: Start/Stop/Status des Job-Monitors +- **✅ API-Blueprint-Registrierung**: API-Blueprint wurde in app.py registriert +- **✅ Fehlende Frontend-API-Routen**: Alle vom Frontend benötigten API-Endpunkte implementiert + - `/api/dashboard` - Dashboard-Daten + - `/api/jobs/recent` - Letzte Jobs + - `/api/files/upload` - Datei-Upload + - `/api/files/download` - Datei-Download + - `/api/stats/*` - Verschiedene Statistik-Endpunkte + - `/api/user/*` - Benutzer-spezifische API-Endpunkte + - `/api/job/{id}/remaining-time` - Verbleibende Zeit für Jobs + - `/api/test` - Debug-Server Test-Endpunkt + - `/api/status` - System-Status-Überwachung + - `/api/schedule` - Scheduler-Informationen +- **✅ Admin-Dashboard-API-Routen**: Vollständige API-Integration für Admin-Panel + - `/api/admin/users/create` - Benutzer erstellen + - `/api/admin/users/{id}/edit` - Benutzer bearbeiten + - `/api/admin/users/{id}/toggle` - Benutzer aktivieren/deaktivieren + - `/api/admin/printers/create` - Drucker erstellen + - `/api/admin/printers/{id}/edit` - Drucker bearbeiten + - `/api/admin/printers/{id}/toggle` - Drucker aktivieren/deaktivieren + - `/api/admin/jobs/cancel/{id}` - Jobs abbrechen + - `/api/admin/system/info` - Erweiterte System-Informationen + - `/api/admin/logs/download` - Log-Download als ZIP + - `/api/admin/maintenance/run` - Wartungsroutinen ausführen #### Smart Plug-Integration - **TP-Link Tapo P110** Steuerung über PyP100 diff --git a/backend/app/app.py b/backend/app/app.py index e9bb802c..f72f8364 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -33,6 +33,7 @@ from utils.job_scheduler import scheduler from utils.template_helpers import register_template_helpers from blueprints.auth import auth_bp from blueprints.user import user_bp +from blueprints.api import api_bp # Flask-App initialisieren app = Flask(__name__) @@ -93,6 +94,14 @@ print("Auth-Blueprint erfolgreich geladen") app.register_blueprint(user_bp) print("User-Blueprint erfolgreich geladen") +# API-Blueprint registrieren +try: + from blueprints.api import api_bp + app.register_blueprint(api_bp) + print("API-Blueprint erfolgreich geladen") +except ImportError as e: + print(f"API-Blueprint konnte nicht geladen werden: {e}") + # Logging initialisieren setup_logging() log_startup_info() @@ -2418,4 +2427,918 @@ def admin_settings(): flash("Sie haben keine Berechtigung für den Admin-Bereich.", "error") return redirect(url_for("index")) - return render_template("admin_settings.html") \ No newline at end of file + return render_template("admin_settings.html") + +@app.route("/api/dashboard", methods=["GET"]) +@login_required +def api_dashboard(): + """Dashboard-Daten für API-Aufrufe""" + try: + db_session = get_db_session() + + # Grundlegende Statistiken sammeln + total_jobs = db_session.query(Job).count() + active_jobs = db_session.query(Job).filter(Job.status.in_(['running', 'pending'])).count() + total_printers = db_session.query(Printer).count() + available_printers = db_session.query(Printer).filter(Printer.status == 'available').count() + + dashboard_data = { + "total_jobs": total_jobs, + "active_jobs": active_jobs, + "total_printers": total_printers, + "available_printers": available_printers, + "timestamp": datetime.now().isoformat() + } + + db_session.close() + return jsonify(dashboard_data) + + except Exception as e: + app_logger.error(f"Fehler beim Laden der Dashboard-Daten: {str(e)}") + return jsonify({"error": "Fehler beim Laden der Dashboard-Daten"}), 500 + +@app.route("/api/jobs/recent", methods=["GET"]) +@login_required +def api_jobs_recent(): + """Letzte Jobs für Dashboard""" + try: + db_session = get_db_session() + limit = request.args.get('limit', 10, type=int) + + recent_jobs = db_session.query(Job).order_by(Job.created_at.desc()).limit(limit).all() + + jobs_data = [] + for job in recent_jobs: + jobs_data.append({ + "id": job.id, + "name": job.name, + "status": job.status, + "user_name": job.user.name if job.user else "Unbekannt", + "printer_name": job.printer.name if job.printer else "Kein Drucker", + "created_at": job.created_at.isoformat() if job.created_at else None + }) + + db_session.close() + return jsonify(jobs_data) + + except Exception as e: + app_logger.error(f"Fehler beim Laden der letzten Jobs: {str(e)}") + return jsonify([]), 500 + +@app.route("/api/files/upload", methods=["POST"]) +@login_required +def api_files_upload(): + """Datei-Upload für Druckaufträge""" + try: + if 'file' not in request.files: + return jsonify({"error": "Keine Datei hochgeladen"}), 400 + + file = request.files['file'] + if file.filename == '': + return jsonify({"error": "Keine Datei ausgewählt"}), 400 + + # Sicherer Dateiname erstellen + filename = secure_filename(file.filename) + + # Upload-Ordner erstellen falls nicht vorhanden + upload_folder = os.path.join(app.root_path, 'uploads') + os.makedirs(upload_folder, exist_ok=True) + + # Eindeutigen Dateinamen erstellen + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + unique_filename = f"{timestamp}_{filename}" + file_path = os.path.join(upload_folder, unique_filename) + + # Datei speichern + file.save(file_path) + + # Relative Pfad für Datenbank + relative_path = os.path.join('uploads', unique_filename) + + jobs_logger.info(f"Datei hochgeladen: {unique_filename} von Benutzer {current_user.username}") + + return jsonify({ + "success": True, + "file_path": relative_path, + "filename": unique_filename, + "original_filename": filename + }) + + except Exception as e: + jobs_logger.error(f"Fehler beim Datei-Upload: {str(e)}") + return jsonify({"error": f"Fehler beim Datei-Upload: {str(e)}"}), 500 + +@app.route("/api/files/download", methods=["GET"]) +@login_required +def api_files_download(): + """Datei-Download""" + try: + file_path = request.args.get('path') + if not file_path: + return jsonify({"error": "Kein Dateipfad angegeben"}), 400 + + # Sicherheitscheck: Nur Dateien im uploads-Ordner erlauben + if not file_path.startswith('uploads/'): + return jsonify({"error": "Ungültiger Dateipfad"}), 403 + + full_path = os.path.join(app.root_path, file_path) + + if not os.path.exists(full_path): + return jsonify({"error": "Datei nicht gefunden"}), 404 + + return send_file(full_path, as_attachment=True) + + except Exception as e: + jobs_logger.error(f"Fehler beim Datei-Download: {str(e)}") + return jsonify({"error": f"Fehler beim Datei-Download: {str(e)}"}), 500 + +@app.route("/api/stats/total-jobs", methods=["GET"]) +@login_required +def api_stats_total_jobs(): + """Gesamtzahl der Jobs""" + try: + db_session = get_db_session() + total = db_session.query(Job).count() + db_session.close() + return jsonify({"value": total}) + except Exception as e: + return jsonify({"value": 0}) + +@app.route("/api/stats/completed-jobs", methods=["GET"]) +@login_required +def api_stats_completed_jobs(): + """Anzahl abgeschlossener Jobs""" + try: + db_session = get_db_session() + completed = db_session.query(Job).filter(Job.status == 'completed').count() + db_session.close() + return jsonify({"value": completed}) + except Exception as e: + return jsonify({"value": 0}) + +@app.route("/api/stats/active-printers", methods=["GET"]) +@login_required +def api_stats_active_printers(): + """Anzahl aktiver Drucker""" + try: + db_session = get_db_session() + active = db_session.query(Printer).filter(Printer.status.in_(['available', 'busy'])).count() + db_session.close() + return jsonify({"value": active}) + except Exception as e: + return jsonify({"value": 0}) + +@app.route("/api/stats/job-status", methods=["GET"]) +@login_required +def api_stats_job_status(): + """Job-Status-Verteilung für Charts""" + try: + db_session = get_db_session() + from sqlalchemy import func + + status_counts = db_session.query( + Job.status, + func.count(Job.id).label('count') + ).group_by(Job.status).all() + + data = [{"status": status, "count": count} for status, count in status_counts] + db_session.close() + return jsonify(data) + except Exception as e: + return jsonify([]) + +@app.route("/api/stats/printer-usage", methods=["GET"]) +@login_required +def api_stats_printer_usage(): + """Drucker-Nutzungsstatistiken für Charts""" + try: + db_session = get_db_session() + from sqlalchemy import func + + usage_stats = db_session.query( + Printer.name, + func.count(Job.id).label('job_count') + ).join(Job, Printer.id == Job.printer_id, isouter=True)\ + .group_by(Printer.name).all() + + data = [{"printer": name, "jobs": count} for name, count in usage_stats] + db_session.close() + return jsonify(data) + except Exception as e: + return jsonify([]) + +@app.route("/api/stats/activity", methods=["GET"]) +@login_required +def api_stats_activity(): + """Aktivitätsliste für Statistiken""" + try: + db_session = get_db_session() + limit = request.args.get('limit', 10, type=int) + + recent_activity = db_session.query(Job)\ + .order_by(Job.updated_at.desc())\ + .limit(limit).all() + + activity_data = [] + for job in recent_activity: + activity_data.append({ + "id": job.id, + "name": job.name, + "action": f"Status: {job.status}", + "user": job.user.name if job.user else "Unbekannt", + "timestamp": job.updated_at.isoformat() if job.updated_at else None + }) + + db_session.close() + return jsonify(activity_data) + except Exception as e: + return jsonify([]) + +@app.route("/api/stats/job-duration", methods=["GET"]) +@login_required +def api_stats_job_duration(): + """Durchschnittliche Job-Dauer""" + try: + db_session = get_db_session() + from sqlalchemy import func + + avg_duration = db_session.query( + func.avg(Job.print_time) + ).filter(Job.status == 'completed').scalar() + + db_session.close() + return jsonify({"value": round(avg_duration or 0, 2)}) + except Exception as e: + return jsonify({"value": 0}) + +@app.route("/api/user/password", methods=["POST"]) +@login_required +def api_user_password(): + """Passwort ändern über API""" + try: + data = request.get_json() + current_password = data.get('current_password') + new_password = data.get('new_password') + + if not current_password or not new_password: + return jsonify({"error": "Aktuelles und neues Passwort erforderlich"}), 400 + + db_session = get_db_session() + user = db_session.query(User).filter(User.id == current_user.id).first() + + if not user or not user.check_password(current_password): + db_session.close() + return jsonify({"error": "Aktuelles Passwort ist falsch"}), 401 + + user.set_password(new_password) + user.updated_at = datetime.now() + db_session.commit() + db_session.close() + + user_logger.info(f"Benutzer {current_user.username} hat sein Passwort geändert") + return jsonify({"success": True, "message": "Passwort erfolgreich geändert"}) + + except Exception as e: + user_logger.error(f"Fehler beim Passwort ändern: {str(e)}") + return jsonify({"error": "Fehler beim Passwort ändern"}), 500 + +@app.route("/api/user/stats", methods=["GET"]) +@login_required +def api_user_stats(): + """Benutzerstatistiken""" + try: + db_session = get_db_session() + + user_jobs = db_session.query(Job).filter(Job.user_id == current_user.id).count() + completed_jobs = db_session.query(Job).filter( + Job.user_id == current_user.id, + Job.status == 'completed' + ).count() + + stats = { + "total_jobs": user_jobs, + "completed_jobs": completed_jobs, + "success_rate": round((completed_jobs / user_jobs * 100) if user_jobs > 0 else 0, 1) + } + + db_session.close() + return jsonify(stats) + + except Exception as e: + return jsonify({"total_jobs": 0, "completed_jobs": 0, "success_rate": 0}) + +@app.route("/api/system/stats", methods=["GET"]) +@login_required +def api_system_stats(): + """System-Statistiken für Admin-Panel""" + try: + # Basis-Systemstatistiken + uptime_days = get_system_uptime_days() + + db_session = get_db_session() + total_users = db_session.query(User).count() + total_printers = db_session.query(Printer).count() + total_jobs = db_session.query(Job).count() + db_session.close() + + stats = { + "uptime_days": uptime_days, + "total_users": total_users, + "total_printers": total_printers, + "total_jobs": total_jobs, + "timestamp": datetime.now().isoformat() + } + + return jsonify(stats) + + except Exception as e: + app_logger.error(f"Fehler beim Laden der System-Statistiken: {str(e)}") + return jsonify({"error": "Fehler beim Laden der System-Statistiken"}), 500 + +@app.route("/api/job//remaining-time", methods=["GET"]) +@login_required +@job_owner_required +def api_job_remaining_time(job_id): + """Verbleibende Zeit für einen Job berechnen""" + try: + db_session = get_db_session() + job = db_session.query(Job).filter(Job.id == job_id).first() + + if not job: + db_session.close() + return jsonify({"error": "Job nicht gefunden"}), 404 + + # Verbleibende Zeit berechnen + if job.status == 'running' and job.start_time: + # Berechne geschätzte Endzeit + estimated_end_time = job.start_time + timedelta(minutes=job.estimated_time) + now = datetime.now() + + if now < estimated_end_time: + remaining_seconds = int((estimated_end_time - now).total_seconds()) + remaining_time = max(0, remaining_seconds * 1000) # Frontend erwartet Millisekunden + else: + remaining_time = 0 + else: + remaining_time = 0 + + db_session.close() + return jsonify({ + "id": job_id, + "remainingTime": remaining_time, + "status": job.status + }) + + except Exception as e: + jobs_logger.error(f"Fehler beim Berechnen der verbleibenden Zeit für Job {job_id}: {str(e)}") + return jsonify({"error": "Fehler beim Berechnen der verbleibenden Zeit"}), 500 + +# Debug-Server API-Routen (nur für Entwicklung) +@app.route("/api/test", methods=["GET"]) +def api_test(): + """Test-Endpunkt für Debug-Server""" + return jsonify({ + "status": "ok", + "message": "Flask Backend ist erreichbar", + "timestamp": datetime.now().isoformat() + }) + +@app.route("/api/schedule", methods=["GET"]) +@login_required +def api_schedule(): + """Scheduler-Informationen""" + try: + from utils.job_scheduler import scheduler + + schedule_info = { + "enabled": SCHEDULER_ENABLED, + "interval": SCHEDULER_INTERVAL, + "is_running": scheduler.is_running() if hasattr(scheduler, 'is_running') else False, + "next_run": None # Könnte erweitert werden + } + + return jsonify(schedule_info) + + except Exception as e: + app_logger.error(f"Fehler beim Laden der Scheduler-Informationen: {str(e)}") + return jsonify({"error": "Fehler beim Laden der Scheduler-Informationen"}), 500 + +@app.route("/api/status", methods=["GET"]) +def api_status(): + """System-Status für externe Überwachung""" + try: + db_session = get_db_session() + + # Grundlegende Gesundheitsprüfung der Datenbank + try: + db_session.execute("SELECT 1") + db_healthy = True + except Exception: + db_healthy = False + finally: + db_session.close() + + status = { + "status": "healthy" if db_healthy else "unhealthy", + "database": "connected" if db_healthy else "disconnected", + "timestamp": datetime.now().isoformat(), + "version": "1.0.0" # Könnte aus einer Konfiguration gelesen werden + } + + return jsonify(status), 200 if db_healthy else 503 + + except Exception as e: + app_logger.error(f"Fehler bei der Status-Prüfung: {str(e)}") + return jsonify({ + "status": "error", + "error": str(e), + "timestamp": datetime.now().isoformat() + }), 500 + +# Admin-Dashboard API-Routen für Frontend-Integration +@app.route("/api/admin/users/create", methods=["POST"]) +@login_required +def api_admin_create_user(): + """API-Endpunkt zum Erstellen eines neuen Benutzers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + data = request.get_json() + + # Pflichtfelder prüfen + required_fields = ["email", "password", "username"] + for field in required_fields: + if field not in data or not data[field]: + return jsonify({"error": f"Feld '{field}' ist erforderlich"}), 400 + + db_session = get_db_session() + + # Prüfen, ob Benutzer bereits existiert + existing_user = db_session.query(User).filter( + sqlalchemy.or_(User.email == data["email"], User.username == data["username"]) + ).first() + + if existing_user: + db_session.close() + return jsonify({"error": "Benutzer mit dieser E-Mail oder diesem Benutzernamen existiert bereits"}), 400 + + # Neuen Benutzer erstellen + new_user = User( + email=data["email"], + username=data["username"], + name=data.get("name", ""), + role=data.get("role", "user"), + active=data.get("active", True), + created_at=datetime.now() + ) + + new_user.set_password(data["password"]) + + db_session.add(new_user) + db_session.commit() + + user_dict = new_user.to_dict() + db_session.close() + + auth_logger.info(f"Neuer Benutzer {data['email']} wurde von Admin {current_user.username} erstellt") + return jsonify({"success": True, "user": user_dict}), 201 + + except Exception as e: + auth_logger.error(f"Fehler beim Erstellen des Benutzers: {str(e)}") + return jsonify({"error": f"Fehler beim Erstellen des Benutzers: {str(e)}"}), 500 + +@app.route("/api/admin/users//edit", methods=["PUT"]) +@login_required +def api_admin_edit_user(user_id): + """API-Endpunkt zum Bearbeiten eines Benutzers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + data = request.get_json() + + db_session = get_db_session() + user = db_session.query(User).filter(User.id == user_id).first() + + if not user: + db_session.close() + return jsonify({"error": "Benutzer nicht gefunden"}), 404 + + # Benutzerdaten aktualisieren + if "email" in data: + user.email = data["email"] + if "username" in data: + user.username = data["username"] + if "name" in data: + user.name = data["name"] + if "role" in data: + user.role = data["role"] + if "active" in data: + user.active = data["active"] + if "password" in data and data["password"]: + user.set_password(data["password"]) + + user.updated_at = datetime.now() + + db_session.commit() + + user_dict = user.to_dict() + db_session.close() + + auth_logger.info(f"Benutzer {user.email} wurde von Admin {current_user.username} bearbeitet") + return jsonify({"success": True, "user": user_dict}) + + except Exception as e: + auth_logger.error(f"Fehler beim Bearbeiten des Benutzers {user_id}: {str(e)}") + return jsonify({"error": f"Fehler beim Bearbeiten des Benutzers: {str(e)}"}), 500 + +@app.route("/api/admin/users//toggle", methods=["POST"]) +@login_required +def api_admin_toggle_user(user_id): + """API-Endpunkt zum Aktivieren/Deaktivieren eines Benutzers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + # Verhindern, dass Admin sich selbst deaktiviert + if user_id == current_user.id: + return jsonify({"error": "Sie können sich nicht selbst deaktivieren"}), 400 + + try: + db_session = get_db_session() + user = db_session.query(User).filter(User.id == user_id).first() + + if not user: + db_session.close() + return jsonify({"error": "Benutzer nicht gefunden"}), 404 + + # Status umschalten + user.active = not user.active + user.updated_at = datetime.now() + + db_session.commit() + + user_dict = user.to_dict() + db_session.close() + + status = "aktiviert" if user.active else "deaktiviert" + auth_logger.info(f"Benutzer {user.email} wurde von Admin {current_user.username} {status}") + + return jsonify({"success": True, "user": user_dict, "message": f"Benutzer wurde {status}"}) + + except Exception as e: + auth_logger.error(f"Fehler beim Umschalten des Benutzerstatus {user_id}: {str(e)}") + return jsonify({"error": f"Fehler beim Umschalten des Benutzerstatus: {str(e)}"}), 500 + +@app.route("/api/admin/printers/create", methods=["POST"]) +@login_required +def api_admin_create_printer(): + """API-Endpunkt zum Erstellen eines neuen Druckers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + data = request.get_json() + + # Pflichtfelder prüfen + required_fields = ["name", "model", "location", "ip_address"] + for field in required_fields: + if field not in data or not data[field]: + return jsonify({"error": f"Feld '{field}' ist erforderlich"}), 400 + + db_session = get_db_session() + + # Prüfen, ob Drucker bereits existiert + existing_printer = db_session.query(Printer).filter( + sqlalchemy.or_(Printer.name == data["name"], Printer.ip_address == data["ip_address"]) + ).first() + + if existing_printer: + db_session.close() + return jsonify({"error": "Drucker mit diesem Namen oder dieser IP-Adresse existiert bereits"}), 400 + + # Neuen Drucker erstellen + new_printer = Printer( + name=data["name"], + model=data["model"], + location=data["location"], + ip_address=data["ip_address"], + plug_ip=data.get("plug_ip"), + plug_username=data.get("plug_username"), + plug_password=data.get("plug_password"), + mac_address=data.get("mac_address"), + status="offline", + active=data.get("active", True), + created_at=datetime.now() + ) + + db_session.add(new_printer) + db_session.commit() + + printer_dict = new_printer.to_dict() + db_session.close() + + printers_logger.info(f"Neuer Drucker {data['name']} wurde von Admin {current_user.username} erstellt") + return jsonify({"success": True, "printer": printer_dict}), 201 + + except Exception as e: + printers_logger.error(f"Fehler beim Erstellen des Druckers: {str(e)}") + return jsonify({"error": f"Fehler beim Erstellen des Druckers: {str(e)}"}), 500 + +@app.route("/api/admin/printers//edit", methods=["PUT"]) +@login_required +def api_admin_edit_printer(printer_id): + """API-Endpunkt zum Bearbeiten eines Druckers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + data = request.get_json() + + db_session = get_db_session() + printer = db_session.query(Printer).filter(Printer.id == printer_id).first() + + if not printer: + db_session.close() + return jsonify({"error": "Drucker nicht gefunden"}), 404 + + # Druckerdaten aktualisieren + if "name" in data: + printer.name = data["name"] + if "model" in data: + printer.model = data["model"] + if "location" in data: + printer.location = data["location"] + if "ip_address" in data: + printer.ip_address = data["ip_address"] + if "plug_ip" in data: + printer.plug_ip = data["plug_ip"] + if "plug_username" in data: + printer.plug_username = data["plug_username"] + if "plug_password" in data: + printer.plug_password = data["plug_password"] + if "mac_address" in data: + printer.mac_address = data["mac_address"] + if "active" in data: + printer.active = data["active"] + + printer.updated_at = datetime.now() + + db_session.commit() + + printer_dict = printer.to_dict() + db_session.close() + + printers_logger.info(f"Drucker {printer.name} wurde von Admin {current_user.username} bearbeitet") + return jsonify({"success": True, "printer": printer_dict}) + + except Exception as e: + printers_logger.error(f"Fehler beim Bearbeiten des Druckers {printer_id}: {str(e)}") + return jsonify({"error": f"Fehler beim Bearbeiten des Druckers: {str(e)}"}), 500 + +@app.route("/api/admin/printers//toggle", methods=["POST"]) +@login_required +def api_admin_toggle_printer(printer_id): + """API-Endpunkt zum Aktivieren/Deaktivieren eines Druckers (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + db_session = get_db_session() + printer = db_session.query(Printer).filter(Printer.id == printer_id).first() + + if not printer: + db_session.close() + return jsonify({"error": "Drucker nicht gefunden"}), 404 + + # Status umschalten + printer.active = not printer.active + printer.updated_at = datetime.now() + + db_session.commit() + + printer_dict = printer.to_dict() + db_session.close() + + status = "aktiviert" if printer.active else "deaktiviert" + printers_logger.info(f"Drucker {printer.name} wurde von Admin {current_user.username} {status}") + + return jsonify({"success": True, "printer": printer_dict, "message": f"Drucker wurde {status}"}) + + except Exception as e: + printers_logger.error(f"Fehler beim Umschalten des Druckerstatus {printer_id}: {str(e)}") + return jsonify({"error": f"Fehler beim Umschalten des Druckerstatus: {str(e)}"}), 500 + +@app.route("/api/admin/jobs/cancel/", methods=["POST"]) +@login_required +def api_admin_cancel_job(job_id): + """API-Endpunkt zum Abbrechen eines Jobs (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + db_session = get_db_session() + job = db_session.query(Job).filter(Job.id == job_id).first() + + if not job: + db_session.close() + return jsonify({"error": "Job nicht gefunden"}), 404 + + # Nur laufende oder geplante Jobs können abgebrochen werden + if job.status not in ["scheduled", "running"]: + db_session.close() + return jsonify({"error": f"Job im Status '{job.status}' kann nicht abgebrochen werden"}), 400 + + # Job abbrechen + job.status = "cancelled" + job.actual_end_time = datetime.now() + + # Steckdose ausschalten falls Job lief + if job.status == "running": + from utils.job_scheduler import toggle_plug + toggle_plug(job.printer_id, False) + + db_session.commit() + + job_dict = job.to_dict() + db_session.close() + + jobs_logger.info(f"Job {job_id} wurde von Admin {current_user.username} abgebrochen") + return jsonify({"success": True, "job": job_dict, "message": "Job wurde abgebrochen"}) + + except Exception as e: + jobs_logger.error(f"Fehler beim Abbrechen des Jobs {job_id}: {str(e)}") + return jsonify({"error": f"Fehler beim Abbrechen des Jobs: {str(e)}"}), 500 + +@app.route("/api/admin/system/info", methods=["GET"]) +@login_required +def api_admin_system_info(): + """Erweiterte System-Informationen für Admin-Dashboard""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + import psutil + import platform + + # System-Grundinformationen + system_info = { + "platform": platform.platform(), + "python_version": platform.python_version(), + "architecture": platform.architecture()[0], + "processor": platform.processor(), + "hostname": platform.node(), + "uptime": get_system_uptime_days() + } + + # Hardware-Informationen + hardware_info = { + "cpu_count": psutil.cpu_count(), + "cpu_freq": psutil.cpu_freq()._asdict() if psutil.cpu_freq() else None, + "memory_total": psutil.virtual_memory().total, + "memory_available": psutil.virtual_memory().available, + "disk_total": psutil.disk_usage('/').total, + "disk_free": psutil.disk_usage('/').free + } + + # Datenbank-Informationen + db_session = get_db_session() + db_info = { + "users_count": db_session.query(User).count(), + "printers_count": db_session.query(Printer).count(), + "jobs_count": db_session.query(Job).count(), + "active_jobs": db_session.query(Job).filter(Job.status.in_(["scheduled", "running"])).count() + } + db_session.close() + + # Anwendungs-Informationen + app_info = { + "version": "3.0.0", # Könnte aus einer Konfiguration gelesen werden + "debug_mode": app.debug, + "flask_env": os.environ.get('FLASK_ENV', 'production'), + "scheduler_enabled": SCHEDULER_ENABLED, + "ssl_enabled": SSL_ENABLED + } + + return jsonify({ + "system": system_info, + "hardware": hardware_info, + "database": db_info, + "application": app_info, + "timestamp": datetime.now().isoformat() + }) + + except Exception as e: + app_logger.error(f"Fehler beim Abrufen der System-Informationen: {str(e)}") + return jsonify({"error": f"Fehler beim Abrufen der System-Informationen: {str(e)}"}), 500 + +@app.route("/api/admin/logs/download", methods=["GET"]) +@login_required +def api_admin_download_logs(): + """Download der System-Logs als ZIP-Datei (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + import zipfile + import tempfile + + # Temporäre ZIP-Datei erstellen + temp_zip = tempfile.NamedTemporaryFile(delete=False, suffix='.zip') + + with zipfile.ZipFile(temp_zip.name, 'w', zipfile.ZIP_DEFLATED) as zipf: + log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs') + + # Alle Log-Dateien zur ZIP hinzufügen + for root, dirs, files in os.walk(log_dir): + for file in files: + if file.endswith('.log'): + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, log_dir) + zipf.write(file_path, arcname) + + # ZIP-Datei als Download senden + return send_file( + temp_zip.name, + as_attachment=True, + download_name=f"system_logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip", + mimetype='application/zip' + ) + + except Exception as e: + app_logger.error(f"Fehler beim Download der Logs: {str(e)}") + return jsonify({"error": f"Fehler beim Download der Logs: {str(e)}"}), 500 + +@app.route("/api/admin/maintenance/run", methods=["POST"]) +@login_required +def api_admin_run_maintenance(): + """Führt Wartungsroutinen aus (Admin only)""" + if not current_user.is_admin: + return jsonify({"error": "Keine Berechtigung"}), 403 + + try: + maintenance_results = [] + + # Cache leeren + try: + import shutil + import tempfile + + cache_dir = os.path.join(tempfile.gettempdir(), 'flask_cache') + if os.path.exists(cache_dir): + shutil.rmtree(cache_dir) + maintenance_results.append("Cache geleert") + except Exception as e: + maintenance_results.append(f"Cache-Fehler: {str(e)}") + + # Datenbank optimieren + try: + db_session = get_db_session() + db_session.execute(sqlalchemy.text("VACUUM")) + db_session.execute(sqlalchemy.text("ANALYZE")) + db_session.commit() + db_session.close() + maintenance_results.append("Datenbank optimiert") + except Exception as e: + maintenance_results.append(f"DB-Fehler: {str(e)}") + + # Alte Log-Dateien komprimieren (älter als 7 Tage) + try: + import gzip + log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs') + seven_days_ago = datetime.now() - timedelta(days=7) + + compressed_files = 0 + for root, dirs, files in os.walk(log_dir): + for file in files: + if file.endswith('.log'): + file_path = os.path.join(root, file) + file_stat = os.stat(file_path) + file_age = datetime.fromtimestamp(file_stat.st_mtime) + + if file_age < seven_days_ago: + # Datei komprimieren + with open(file_path, 'rb') as f_in: + with gzip.open(f"{file_path}.gz", 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + os.remove(file_path) + compressed_files += 1 + + if compressed_files > 0: + maintenance_results.append(f"{compressed_files} Log-Dateien komprimiert") + else: + maintenance_results.append("Keine Log-Dateien zu komprimieren") + except Exception as e: + maintenance_results.append(f"Log-Komprimierung-Fehler: {str(e)}") + + app_logger.info(f"Wartung wurde von Admin {current_user.username} durchgeführt: {', '.join(maintenance_results)}") + + return jsonify({ + "success": True, + "message": "Wartung erfolgreich durchgeführt", + "results": maintenance_results + }) + + except Exception as e: + app_logger.error(f"Fehler bei der Wartung: {str(e)}") + return jsonify({"error": f"Fehler bei der Wartung: {str(e)}"}), 500 \ No newline at end of file diff --git a/backend/app/blueprints/auth.py b/backend/app/blueprints/auth.py index 3af0f8a4..920df2f6 100644 --- a/backend/app/blueprints/auth.py +++ b/backend/app/blueprints/auth.py @@ -85,4 +85,68 @@ def logout(): if request.is_json or request.headers.get('Content-Type') == 'application/json': return jsonify({"success": True, "redirect_url": url_for("auth.login")}) else: - return redirect(url_for("auth.login")) \ No newline at end of file + return redirect(url_for("auth.login")) + +@auth_bp.route("/api/login", methods=["POST"]) +def api_login(): + """API-Login-Endpunkt für Frontend""" + try: + data = request.get_json() + if not data: + return jsonify({"error": "Keine Daten erhalten"}), 400 + + username = data.get("username") + password = data.get("password") + remember_me = data.get("remember_me", False) + + if not username or not password: + return jsonify({"error": "Benutzername und Passwort müssen angegeben werden"}), 400 + + db_session = get_db_session() + user = db_session.query(User).filter( + (User.username == username) | (User.email == username) + ).first() + + if user and user.check_password(password): + login_user(user, remember=remember_me) + auth_logger.info(f"API-Login erfolgreich für Benutzer {username}") + + user_data = { + "id": user.id, + "username": user.username, + "name": user.name, + "email": user.email, + "is_admin": user.is_admin + } + + db_session.close() + return jsonify({ + "success": True, + "user": user_data, + "redirect_url": url_for("index") + }) + else: + auth_logger.warning(f"Fehlgeschlagener API-Login für Benutzer {username}") + db_session.close() + return jsonify({"error": "Ungültiger Benutzername oder Passwort"}), 401 + + except Exception as e: + auth_logger.error(f"Fehler beim API-Login: {str(e)}") + return jsonify({"error": "Anmeldefehler. Bitte versuchen Sie es später erneut"}), 500 + +@auth_bp.route("/api/callback", methods=["GET", "POST"]) +def api_callback(): + """OAuth-Callback-Endpunkt für externe Authentifizierung""" + try: + # Dieser Endpunkt würde für OAuth-Integration verwendet werden + # Hier könnte GitHub/OAuth-Code verarbeitet werden + + # Placeholder für OAuth-Integration + return jsonify({ + "message": "OAuth-Callback noch nicht implementiert", + "redirect_url": url_for("auth.login") + }) + + except Exception as e: + auth_logger.error(f"Fehler im OAuth-Callback: {str(e)}") + return jsonify({"error": "OAuth-Callback-Fehler"}), 500 \ No newline at end of file diff --git a/backend/setup_raspberry_pi.sh b/backend/setup_raspberry_pi.sh index e6e51a3d..03f9182c 100644 --- a/backend/setup_raspberry_pi.sh +++ b/backend/setup_raspberry_pi.sh @@ -111,7 +111,7 @@ mkdir -p "$APP_DIR/logs/jobs" mkdir -p "$APP_DIR/logs/printers" mkdir -p "$APP_DIR/logs/scheduler" mkdir -p "$APP_DIR/logs/errors" -mkdir -p "$BACKEND_DIR/certs" +mkdir -p "$APP_DIR/certs" mkdir -p "$PROJECT_DIR/frontend/ssl" # Berechtigungen setzen @@ -119,7 +119,7 @@ log "8. Berechtigungen setzen..." chown -R $USER:$USER "$PROJECT_DIR" chmod -R 755 "$PROJECT_DIR" chmod -R 700 "$APP_DIR/logs" -chmod -R 700 "$BACKEND_DIR/certs" +chmod -R 700 "$APP_DIR/certs" # Datenbank initialisieren log "9. Datenbank initialisieren..." @@ -196,8 +196,8 @@ server { server_name raspberrypi localhost; # SSL-Konfiguration - ssl_certificate $BACKEND_DIR/certs/myp.crt; - ssl_certificate_key $BACKEND_DIR/certs/myp.key; + ssl_certificate $APP_DIR/certs/myp.crt; + ssl_certificate_key $APP_DIR/certs/myp.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; @@ -313,7 +313,7 @@ log "📁 Wichtige Verzeichnisse:" log " • Anwendung: $APP_DIR" log " • Logs: $APP_DIR/logs" log " • Datenbank: $APP_DIR/database/myp.db" -log " • SSL-Zertifikate: $BACKEND_DIR/certs" +log " • SSL-Zertifikate: $APP_DIR/certs" log "" log "⚠️ Hinweise:" log " • Das SSL-Zertifikat ist selbstsigniert" diff --git a/SSL_KONFIGURATION.md b/docs/SSL_KONFIGURATION.md similarity index 100% rename from SSL_KONFIGURATION.md rename to docs/SSL_KONFIGURATION.md diff --git a/SUMMARY.md b/docs/SUMMARY.md similarity index 100% rename from SUMMARY.md rename to docs/SUMMARY.md diff --git a/myp_documentation.md b/docs/myp_documentation.md similarity index 100% rename from myp_documentation.md rename to docs/myp_documentation.md diff --git a/frontend/docker/deploy.sh b/frontend/docker/deploy.sh index 09b73d6c..540602ce 100644 --- a/frontend/docker/deploy.sh +++ b/frontend/docker/deploy.sh @@ -23,7 +23,7 @@ done # Execute docker compose echo "Running docker compose..." -docker compose -f "docker/compose.yml" up -d +docker compose -f "compose.yml" up -d # Check if the operation was successful if [ $? -eq 0 ]; then diff --git a/myp_installer.ps1 b/myp_installer.ps1 index 3105f87a..33a01fef 100644 --- a/myp_installer.ps1 +++ b/myp_installer.ps1 @@ -476,8 +476,8 @@ function Show-SSLStatus { Show-Header "SSL-Zertifikat-Status" $certPaths = @( - "backend\instance\ssl\myp.crt", - "backend\instance\ssl\myp.key", + "backend\app\certs\myp.crt", + "backend\app\certs\myp.key", "frontend\ssl\myp.crt", "frontend\ssl\myp.key" ) @@ -610,7 +610,7 @@ function Create-SSLCertificates { Show-Header "SSL-Zertifikat-Generator" # Parameter definieren - $certDir = "./backend/instance/ssl" + $certDir = "./backend/app/certs" $backendCertFile = "$certDir/myp.crt" $backendKeyFile = "$certDir/myp.key" $frontendCertFile = "$certDir/frontend.crt" diff --git a/myp_installer.sh b/myp_installer.sh index 41872505..1401f554 100644 --- a/myp_installer.sh +++ b/myp_installer.sh @@ -1,7 +1,7 @@ #!/bin/bash # MYP Installer Control Center - Vollständige Linux/Unix-Installation # Zentrale Installationskonsole für die MYP-Plattform -# Version 3.0 - Konsolidiert alle Setup-Funktionen +# Version 4.0 - Granularer Installer mit Frontend/Backend-Trennung # Farbdefinitionen RED='\033[0;31m' @@ -12,6 +12,13 @@ CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' # No Color +# Globale Variablen +PROJECT_DIR="$(pwd)" +BACKEND_DIR="$PROJECT_DIR/backend" +FRONTEND_DIR="$PROJECT_DIR/frontend" +APP_DIR="$BACKEND_DIR/app" +VENV_DIR="$BACKEND_DIR/venv" + # Überprüfen, ob das Skript als Root ausgeführt wird is_root=0 if [ "$EUID" -eq 0 ]; then @@ -24,7 +31,8 @@ show_header() { clear echo -e "${CYAN}=============================================================${NC}" echo -e "${CYAN} MYP INSTALLER CONTROL CENTER ${NC}" - echo -e "${CYAN} Version 3.0 ${NC}" + echo -e "${CYAN} Version 4.0 ${NC}" + echo -e "${CYAN} Granularer Installer mit Trennung ${NC}" echo -e "${CYAN}=============================================================${NC}" echo -e "${CYAN} $title${NC}" echo -e "${CYAN}=============================================================${NC}" @@ -45,6 +53,7 @@ check_command() { exec_command() { local cmd="$1" local description="$2" + local allow_fail="$3" echo -e "${BLUE}> $description...${NC}" @@ -54,8 +63,13 @@ exec_command() { echo -e "${GREEN}✓ Erfolgreich abgeschlossen!${NC}" return 0 else - echo -e "${RED}✗ Fehler beim Ausführen des Befehls. Exit-Code: $?${NC}" - return 1 + if [ "$allow_fail" = "true" ]; then + echo -e "${YELLOW}⚠ Warnung: $description fehlgeschlagen, wird übersprungen.${NC}" + return 0 + else + echo -e "${RED}✗ Fehler beim Ausführen des Befehls. Exit-Code: $?${NC}" + return 1 + fi fi } @@ -67,6 +81,625 @@ get_local_ip() { echo "$ip" } +# System-Abhängigkeiten installieren +install_system_dependencies() { + show_header "System-Abhängigkeiten installieren" + + if [ $is_root -eq 0 ]; then + echo -e "${RED}Diese Funktion erfordert Root-Rechte.${NC}" + echo -e "${YELLOW}Bitte starten Sie das Skript mit sudo oder führen Sie folgende Befehle manuell aus:${NC}" + echo "" + echo -e "${WHITE}# Debian/Ubuntu/Raspberry Pi OS:${NC}" + echo -e "${WHITE}sudo apt update${NC}" + echo -e "${WHITE}sudo apt install -y python3 python3-pip python3-venv nodejs npm git curl wget sqlite3 openssl${NC}" + echo "" + echo -e "${WHITE}# RHEL/CentOS/Fedora:${NC}" + echo -e "${WHITE}sudo dnf install -y python3 python3-pip nodejs npm git curl wget sqlite openssl${NC}" + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + echo -e "${BLUE}Erkenne Betriebssystem...${NC}" + + if [ -f /etc/debian_version ]; then + echo -e "${GREEN}Debian/Ubuntu/Raspberry Pi OS erkannt${NC}" + + echo -e "${BLUE}Aktualisiere Paketlisten...${NC}" + apt update + + echo -e "${BLUE}Installiere System-Pakete...${NC}" + apt install -y \ + python3 \ + python3-pip \ + python3-venv \ + python3-dev \ + build-essential \ + libssl-dev \ + libffi-dev \ + libsqlite3-dev \ + nodejs \ + npm \ + git \ + curl \ + wget \ + sqlite3 \ + openssl \ + ca-certificates \ + nginx \ + supervisor \ + ufw \ + net-tools \ + htop \ + vim \ + nano \ + chromium-browser + + elif [ -f /etc/redhat-release ]; then + echo -e "${GREEN}RHEL/CentOS/Fedora erkannt${NC}" + + echo -e "${BLUE}Installiere System-Pakete...${NC}" + if check_command dnf; then + dnf install -y \ + python3 \ + python3-pip \ + python3-devel \ + gcc \ + openssl-devel \ + libffi-devel \ + sqlite-devel \ + nodejs \ + npm \ + git \ + curl \ + wget \ + sqlite \ + openssl \ + ca-certificates \ + nginx \ + supervisor \ + chromium + else + yum install -y \ + python3 \ + python3-pip \ + python3-devel \ + gcc \ + openssl-devel \ + libffi-devel \ + sqlite-devel \ + nodejs \ + npm \ + git \ + curl \ + wget \ + sqlite \ + openssl \ + ca-certificates \ + nginx \ + supervisor \ + chromium + fi + + else + echo -e "${YELLOW}Unbekanntes Betriebssystem. Bitte installieren Sie manuell:${NC}" + echo -e "${WHITE}- Python 3.8+${NC}" + echo -e "${WHITE}- Node.js 16+${NC}" + echo -e "${WHITE}- Git, curl, wget, sqlite3, openssl${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + echo -e "${GREEN}✓ System-Abhängigkeiten erfolgreich installiert!${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +# Python & Node.js Umgebung einrichten +setup_python_node_environment() { + show_header "Python & Node.js Umgebung einrichten" + + echo -e "${BLUE}1. Python-Umgebung prüfen...${NC}" + + # Python prüfen + python_cmd="" + if check_command python3; then + python_cmd="python3" + python_version=$(python3 --version 2>&1) + echo -e "${GREEN}✓ $python_version${NC}" + elif check_command python; then + python_cmd="python" + python_version=$(python --version 2>&1) + echo -e "${GREEN}✓ $python_version${NC}" + else + echo -e "${RED}✗ Python nicht gefunden${NC}" + return 1 + fi + + # Pip prüfen + if check_command pip3; then + echo -e "${GREEN}✓ pip3 gefunden${NC}" + elif check_command pip; then + echo -e "${GREEN}✓ pip gefunden${NC}" + else + echo -e "${RED}✗ pip nicht gefunden${NC}" + return 1 + fi + + echo -e "${BLUE}2. Node.js-Umgebung prüfen...${NC}" + + # Node.js prüfen + if check_command node; then + node_version=$(node --version) + echo -e "${GREEN}✓ Node.js $node_version${NC}" + else + echo -e "${RED}✗ Node.js nicht gefunden${NC}" + return 1 + fi + + # npm prüfen + if check_command npm; then + npm_version=$(npm --version) + echo -e "${GREEN}✓ npm $npm_version${NC}" + else + echo -e "${RED}✗ npm nicht gefunden${NC}" + return 1 + fi + + echo -e "${BLUE}3. Globale npm-Pakete aktualisieren...${NC}" + exec_command "npm update -g" "npm global update" true + + echo -e "${GREEN}✓ Python & Node.js Umgebung bereit!${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +# Backend installieren +install_backend() { + show_header "Backend Installation" + + echo -e "${BLUE}MYP Backend (Flask API) installieren${NC}" + echo "" + + # Python prüfen + python_cmd="" + if check_command python3; then + python_cmd="python3" + echo -e "${GREEN}✓ Python 3 gefunden${NC}" + elif check_command python; then + python_cmd="python" + echo -e "${GREEN}✓ Python gefunden${NC}" + else + echo -e "${RED}✗ Python nicht gefunden. Bitte installieren Sie Python 3.8+${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + # Pip prüfen + pip_cmd="" + if check_command pip3; then + pip_cmd="pip3" + elif check_command pip; then + pip_cmd="pip" + else + echo -e "${RED}✗ pip nicht gefunden. Bitte installieren Sie pip${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + # Virtual Environment erstellen + echo -e "${BLUE}1. Virtual Environment erstellen...${NC}" + if [ ! -d "$VENV_DIR" ]; then + exec_command "$python_cmd -m venv $VENV_DIR" "Erstelle Virtual Environment" + else + echo -e "${YELLOW}Virtual Environment existiert bereits${NC}" + fi + + # Virtual Environment aktivieren + echo -e "${BLUE}2. Virtual Environment aktivieren...${NC}" + source "$VENV_DIR/bin/activate" + + # Pip upgraden + echo -e "${BLUE}3. Pip upgraden...${NC}" + exec_command "pip install --upgrade pip setuptools wheel" "Pip upgraden" + + # Requirements installieren + echo -e "${BLUE}4. Backend-Abhängigkeiten installieren...${NC}" + if [ -f "$BACKEND_DIR/requirements.txt" ]; then + exec_command "pip install -r $BACKEND_DIR/requirements.txt" "Backend-Abhängigkeiten installieren" + else + echo -e "${RED}✗ requirements.txt nicht gefunden in $BACKEND_DIR${NC}" + return 1 + fi + + # Verzeichnisse erstellen + echo -e "${BLUE}5. Backend-Verzeichnisse erstellen...${NC}" + mkdir -p "$APP_DIR/database" + mkdir -p "$APP_DIR/logs/app" + mkdir -p "$APP_DIR/logs/auth" + mkdir -p "$APP_DIR/logs/jobs" + mkdir -p "$APP_DIR/logs/printers" + mkdir -p "$APP_DIR/logs/scheduler" + mkdir -p "$APP_DIR/logs/errors" + mkdir -p "$APP_DIR/certs" + + echo -e "${GREEN}✓ Backend-Verzeichnisse erstellt${NC}" + + # Datenbank initialisieren + echo -e "${BLUE}6. Datenbank initialisieren...${NC}" + cd "$APP_DIR" + if [ ! -f "database/myp.db" ]; then + exec_command "$python_cmd -c 'from models import init_database, create_initial_admin; init_database(); create_initial_admin()'" "Datenbank initialisieren" + else + echo -e "${YELLOW}Datenbank existiert bereits${NC}" + fi + + # SSL-Zertifikate erstellen + echo -e "${BLUE}7. SSL-Zertifikate erstellen...${NC}" + read -p "Möchten Sie SSL-Zertifikate erstellen? (j/n, Standard: j): " create_ssl + if [ "$create_ssl" != "n" ]; then + create_ssl_certificates + fi + + echo "" + echo -e "${GREEN}✓ Backend-Installation abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}Backend starten:${NC}" + echo -e "${WHITE}cd $APP_DIR && $python_cmd app.py${NC}" + echo "" + echo -e "${BLUE}Backend-URLs:${NC}" + echo -e "${WHITE}- HTTPS: https://localhost:443${NC}" + echo -e "${WHITE}- HTTP: http://localhost:5000${NC}" + + deactivate + cd "$PROJECT_DIR" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +# Frontend installieren +install_frontend() { + show_header "Frontend Installation" + + echo -e "${BLUE}MYP Frontend (Next.js React App) installieren${NC}" + echo "" + + # Node.js prüfen + if ! check_command node; then + echo -e "${RED}✗ Node.js nicht gefunden. Bitte installieren Sie Node.js 16+${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + node_version=$(node --version) + echo -e "${GREEN}✓ Node.js gefunden: $node_version${NC}" + + # npm prüfen + if ! check_command npm; then + echo -e "${RED}✗ npm nicht gefunden. Bitte installieren Sie npm${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + npm_version=$(npm --version) + echo -e "${GREEN}✓ npm gefunden: $npm_version${NC}" + + # Frontend-Verzeichnis prüfen + if [ ! -d "$FRONTEND_DIR" ]; then + echo -e "${RED}✗ Frontend-Verzeichnis nicht gefunden: $FRONTEND_DIR${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + cd "$FRONTEND_DIR" + + # Dependencies installieren + echo -e "${BLUE}1. Frontend-Abhängigkeiten installieren...${NC}" + exec_command "npm install" "Frontend-Abhängigkeiten installieren" + + # SSL-Verzeichnis erstellen + echo -e "${BLUE}2. SSL-Verzeichnis erstellen...${NC}" + mkdir -p "$FRONTEND_DIR/ssl" + + # .env.local konfigurieren + echo -e "${BLUE}3. Frontend-Konfiguration erstellen...${NC}" + setup_frontend_config + + # Build erstellen (optional) + read -p "Möchten Sie das Frontend für Produktion builden? (j/n, Standard: n): " build_frontend + if [ "$build_frontend" = "j" ]; then + echo -e "${BLUE}4. Frontend für Produktion builden...${NC}" + exec_command "npm run build" "Frontend builden" + fi + + echo "" + echo -e "${GREEN}✓ Frontend-Installation abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}Frontend starten:${NC}" + echo -e "${WHITE}cd $FRONTEND_DIR && npm run dev${NC}" + echo "" + echo -e "${BLUE}Frontend-URLs:${NC}" + echo -e "${WHITE}- Development: http://localhost:3000${NC}" + echo -e "${WHITE}- Production: http://localhost:3000${NC}" + + cd "$PROJECT_DIR" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +# Kiosk-Modus installieren +install_kiosk_mode() { + show_header "Kiosk-Modus Installation" + + echo -e "${BLUE}MYP Kiosk-Modus (Backend Web Interface) installieren${NC}" + echo "" + echo -e "${YELLOW}Der Kiosk-Modus nutzt das Backend Flask Web Interface${NC}" + echo -e "${YELLOW}als Ersatz für das separate Frontend.${NC}" + echo "" + + # Backend muss installiert sein + if [ ! -d "$VENV_DIR" ]; then + echo -e "${RED}✗ Backend ist nicht installiert. Bitte installieren Sie zuerst das Backend.${NC}" + read -p "Drücken Sie ENTER, um fortzufahren..." + return 1 + fi + + # Kiosk-spezifische Konfiguration + echo -e "${BLUE}1. Kiosk-Konfiguration erstellen...${NC}" + + # Virtual Environment aktivieren + source "$VENV_DIR/bin/activate" + + cd "$APP_DIR" + + # Kiosk-Konfiguration in settings.py setzen + if [ -f "config/settings.py" ]; then + # Backup erstellen + cp "config/settings.py" "config/settings.py.backup" + + # Kiosk-Modus aktivieren + cat >> "config/settings.py" << 'EOF' + +# Kiosk-Modus Konfiguration +KIOSK_MODE = True +KIOSK_AUTO_LOGIN = True +KIOSK_FULLSCREEN = True +KIOSK_HIDE_NAVIGATION = False +KIOSK_DEFAULT_USER = "kiosk@mercedes-benz.com" +EOF + + echo -e "${GREEN}✓ Kiosk-Konfiguration hinzugefügt${NC}" + fi + + # Systemd Service für Kiosk erstellen (falls Root) + if [ $is_root -eq 1 ]; then + echo -e "${BLUE}2. Kiosk-Service erstellen...${NC}" + + cat > "/etc/systemd/system/myp-kiosk.service" << EOF +[Unit] +Description=MYP Kiosk Mode - 3D Printer Management Kiosk +After=network.target graphical-session.target + +[Service] +Type=simple +User=$USER +Group=$USER +WorkingDirectory=$APP_DIR +Environment=PATH=$VENV_DIR/bin +Environment=DISPLAY=:0 +ExecStart=$VENV_DIR/bin/python app.py --kiosk +Restart=always +RestartSec=10 + +[Install] +WantedBy=graphical-session.target +EOF + + systemctl daemon-reload + systemctl enable myp-kiosk.service + + echo -e "${GREEN}✓ Kiosk-Service erstellt und aktiviert${NC}" + fi + + # Browser-Autostart für Kiosk (Raspberry Pi) + echo -e "${BLUE}3. Browser-Autostart konfigurieren...${NC}" + + autostart_dir="$HOME/.config/autostart" + mkdir -p "$autostart_dir" + + cat > "$autostart_dir/myp-kiosk.desktop" << EOF +[Desktop Entry] +Type=Application +Name=MYP Kiosk +Comment=MYP 3D Printer Management Kiosk +Exec=chromium-browser --kiosk --disable-infobars --disable-session-crashed-bubble --disable-translate --no-first-run https://localhost:443 +X-GNOME-Autostart-enabled=true +Hidden=false +NoDisplay=false +EOF + + echo -e "${GREEN}✓ Browser-Autostart konfiguriert${NC}" + + deactivate + cd "$PROJECT_DIR" + + echo "" + echo -e "${GREEN}✓ Kiosk-Modus Installation abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}Kiosk-Modus starten:${NC}" + echo -e "${WHITE}sudo systemctl start myp-kiosk${NC}" + echo "" + echo -e "${BLUE}Kiosk-URLs:${NC}" + echo -e "${WHITE}- Vollbild: https://localhost:443${NC}" + echo -e "${WHITE}- Normal: https://localhost:443${NC}" + echo "" + echo -e "${YELLOW}Hinweise:${NC}" + echo -e "${WHITE}- Der Kiosk-Modus startet automatisch beim Boot${NC}" + echo -e "${WHITE}- Browser öffnet im Vollbildmodus${NC}" + echo -e "${WHITE}- Nutzt das Backend Web Interface${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +# Frontend-Konfiguration erstellen +setup_frontend_config() { + echo -e "${BLUE}Backend-URL für Frontend konfigurieren:${NC}" + echo -e "${WHITE}1. Lokale Entwicklung (https://localhost:443)${NC}" + echo -e "${WHITE}2. Raspberry Pi (https://raspberrypi:443)${NC}" + echo -e "${WHITE}3. Benutzerdefinierte URL${NC}" + + read -p "Wählen Sie eine Option (1-3, Standard: 1): " choice + + backend_url="https://localhost:443" + + case $choice in + 2) + backend_url="https://raspberrypi:443" + ;; + 3) + read -p "Backend-URL eingeben: " backend_url + ;; + *) + backend_url="https://localhost:443" + ;; + esac + + # .env.local erstellen + cat > "$FRONTEND_DIR/.env.local" << EOF +# Backend API Konfiguration +NEXT_PUBLIC_API_URL=$backend_url + +# Frontend-URL +NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000 + +# OAuth Konfiguration +NEXT_PUBLIC_OAUTH_CALLBACK_URL=http://localhost:3000/auth/login/callback + +# GitHub OAuth +GITHUB_CLIENT_ID=7c5d8bef1a5519ec1fdc +GITHUB_CLIENT_SECRET=5f1e586204358fbd53cf5fb7d418b3f06ccab8fd + +# Entwicklungsumgebung +NODE_ENV=development +DEBUG=true +NEXT_DEBUG=true + +# Backend Host +NEXT_PUBLIC_BACKEND_HOST=$(echo "$backend_url" | sed -E 's|https?://([^:/]+).*|\1|') +NEXT_PUBLIC_BACKEND_PROTOCOL=$(echo "$backend_url" | sed -E 's|^(https?)://.*|\1|') +EOF + + echo -e "${GREEN}✓ Frontend-Konfiguration erstellt: $backend_url${NC}" +} + +# Vollinstallationen +install_everything() { + show_header "Vollständige Installation (Backend + Frontend)" + + echo -e "${BLUE}Installiere komplette MYP-Platform...${NC}" + echo "" + + # System-Abhängigkeiten (falls Root) + if [ $is_root -eq 1 ]; then + install_system_dependencies + else + echo -e "${YELLOW}System-Abhängigkeiten übersprungen (keine Root-Rechte)${NC}" + fi + + # Backend installieren + install_backend + + # Frontend installieren + install_frontend + + echo "" + echo -e "${GREEN}✓ Vollständige Installation abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}Nächste Schritte:${NC}" + echo -e "${WHITE}1. Backend starten: cd $APP_DIR && python app.py${NC}" + echo -e "${WHITE}2. Frontend starten: cd $FRONTEND_DIR && npm run dev${NC}" + echo -e "${WHITE}3. Anwendung öffnen: https://localhost:443 (Backend) + http://localhost:3000 (Frontend)${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +install_production_setup() { + show_header "Produktions-Setup (Backend + Kiosk)" + + echo -e "${BLUE}Installiere Produktions-Setup für Raspberry Pi...${NC}" + echo "" + + # System-Abhängigkeiten (falls Root) + if [ $is_root -eq 1 ]; then + install_system_dependencies + else + echo -e "${YELLOW}System-Abhängigkeiten übersprungen (keine Root-Rechte)${NC}" + fi + + # Backend installieren + install_backend + + # Kiosk-Modus installieren + install_kiosk_mode + + echo "" + echo -e "${GREEN}✓ Produktions-Setup abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}System für Produktion konfiguriert:${NC}" + echo -e "${WHITE}- Backend läuft als Service${NC}" + echo -e "${WHITE}- Kiosk-Modus aktiviert${NC}" + echo -e "${WHITE}- Browser startet automatisch${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + +install_development_setup() { + show_header "Entwicklungs-Setup (Backend + Frontend + Tools)" + + echo -e "${BLUE}Installiere Entwicklungs-Setup...${NC}" + echo "" + + # System-Abhängigkeiten (falls Root) + if [ $is_root -eq 1 ]; then + install_system_dependencies + else + echo -e "${YELLOW}System-Abhängigkeiten übersprungen (keine Root-Rechte)${NC}" + fi + + # Backend installieren + install_backend + + # Frontend installieren + install_frontend + + # Entwicklungstools konfigurieren + echo -e "${BLUE}Entwicklungstools konfigurieren...${NC}" + + # Git Hooks (optional) + if [ -d ".git" ]; then + echo -e "${BLUE}Git-Repository erkannt${NC}" + # Hier könnten Git-Hooks konfiguriert werden + fi + + echo "" + echo -e "${GREEN}✓ Entwicklungs-Setup abgeschlossen!${NC}" + echo "" + echo -e "${BLUE}Entwicklungsumgebung bereit:${NC}" + echo -e "${WHITE}- Backend mit Debug-Modus${NC}" + echo -e "${WHITE}- Frontend mit Hot-Reload${NC}" + echo -e "${WHITE}- SSL-Zertifikate für HTTPS${NC}" + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + test_dependencies() { show_header "Systemvoraussetzungen prüfen" @@ -98,24 +731,6 @@ test_dependencies() { all_installed=0 fi - # Docker - if check_command docker; then - echo -e "${GREEN}✓ Docker gefunden${NC}" - docker_version=$(docker --version 2>&1) - echo -e "${WHITE} Version: $docker_version${NC}" - else - echo -e "${RED}✗ Docker nicht gefunden${NC}" - all_installed=0 - fi - - # Docker Compose - if check_command docker-compose; then - echo -e "${GREEN}✓ Docker Compose gefunden${NC}" - else - echo -e "${RED}✗ Docker Compose nicht gefunden${NC}" - all_installed=0 - fi - # Node.js if check_command node; then echo -e "${GREEN}✓ Node.js gefunden${NC}" @@ -134,14 +749,6 @@ test_dependencies() { all_installed=0 fi - # OpenSSL - if check_command openssl; then - echo -e "${GREEN}✓ OpenSSL gefunden${NC}" - else - echo -e "${RED}✗ OpenSSL nicht gefunden${NC}" - all_installed=0 - fi - # Git if check_command git; then echo -e "${GREEN}✓ Git gefunden${NC}" @@ -150,11 +757,19 @@ test_dependencies() { all_installed=0 fi - # curl - if check_command curl; then - echo -e "${GREEN}✓ cURL gefunden${NC}" + # OpenSSL + if check_command openssl; then + echo -e "${GREEN}✓ OpenSSL gefunden${NC}" else - echo -e "${RED}✗ cURL nicht gefunden${NC}" + echo -e "${RED}✗ OpenSSL nicht gefunden${NC}" + all_installed=0 + fi + + # SQLite + if check_command sqlite3; then + echo -e "${GREEN}✓ SQLite3 gefunden${NC}" + else + echo -e "${RED}✗ SQLite3 nicht gefunden${NC}" all_installed=0 fi @@ -162,7 +777,7 @@ test_dependencies() { if [ $all_installed -eq 1 ]; then echo -e "${GREEN}✓ Alle Abhängigkeiten sind installiert!${NC}" else - echo -e "${YELLOW}⚠ Einige Abhängigkeiten fehlen. Bitte installieren Sie diese vor der Verwendung.${NC}" + echo -e "${YELLOW}⚠ Einige Abhängigkeiten fehlen. Nutzen Sie 'System-Abhängigkeiten installieren'.${NC}" fi echo "" @@ -226,7 +841,7 @@ test_backend_connection() { echo -e "${BLUE}Welches Backend möchten Sie testen?${NC}" echo -e "${WHITE}1. Lokales Backend (localhost:443)${NC}" - echo -e "${WHITE}2. Raspberry Pi Backend (192.168.0.105:5000)${NC}" + echo -e "${WHITE}2. Raspberry Pi Backend (raspberrypi:443)${NC}" echo -e "${WHITE}3. Benutzerdefinierte URL${NC}" read -p "Wählen Sie eine Option (1-3, Standard: 1): " choice @@ -236,8 +851,8 @@ test_backend_connection() { case $choice in 2) - backend_url="http://192.168.0.105:5000" - backend_host="192.168.0.105" + backend_url="https://raspberrypi:443" + backend_host="raspberrypi" ;; 3) read -p "Backend-URL eingeben (z.B. https://raspberrypi:443): " backend_url @@ -272,38 +887,6 @@ test_backend_connection() { echo -e "${RED}✗ Backend-Service nicht erreichbar${NC}" fi - # 3. API-Endpunkte prüfen - echo -e "${BLUE}3. Prüfe Backend-API-Endpunkte...${NC}" - for endpoint in "printers" "jobs" "users"; do - api_url="$backend_url/api/$endpoint" - if curl -f --connect-timeout 5 "$api_url" >/dev/null 2>&1; then - echo -e "${GREEN}✓ API-Endpunkt /$endpoint erreichbar${NC}" - else - echo -e "${YELLOW}⚠ API-Endpunkt /$endpoint nicht erreichbar${NC}" - fi - done - - # 4. Frontend-Konfiguration prüfen - echo -e "${BLUE}4. Prüfe Frontend-Konfigurationsdateien...${NC}" - - env_local_path="frontend/.env.local" - if [ -f "$env_local_path" ]; then - if grep -q "NEXT_PUBLIC_API_URL" "$env_local_path"; then - echo -e "${GREEN}✓ .env.local gefunden und konfiguriert${NC}" - else - echo -e "${YELLOW}⚠ .env.local existiert, aber Backend-URL fehlt${NC}" - fi - else - echo -e "${YELLOW}⚠ .env.local nicht gefunden${NC}" - fi - - echo "" - read -p "Möchten Sie die Frontend-Konfiguration für dieses Backend aktualisieren? (j/n): " update_config - - if [ "$update_config" = "j" ]; then - setup_backend_url "$backend_url" - fi - echo "" read -p "Drücken Sie ENTER, um fortzufahren..." } @@ -316,14 +899,14 @@ setup_backend_url() { if [ -z "$backend_url" ]; then echo -e "${BLUE}Verfügbare Backend-Konfigurationen:${NC}" echo -e "${WHITE}1. Lokale Entwicklung (https://localhost:443)${NC}" - echo -e "${WHITE}2. Raspberry Pi (http://192.168.0.105:5000)${NC}" + echo -e "${WHITE}2. Raspberry Pi (https://raspberrypi:443)${NC}" echo -e "${WHITE}3. Benutzerdefinierte URL${NC}" read -p "Wählen Sie eine Option (1-3, Standard: 1): " choice case $choice in 2) - backend_url="http://192.168.0.105:5000" + backend_url="https://raspberrypi:443" ;; 3) read -p "Backend-URL eingeben (z.B. https://raspberrypi:443): " backend_url @@ -380,80 +963,130 @@ EOF read -p "Drücken Sie ENTER, um fortzufahren..." } +start_application() { + show_header "Anwendung starten" + + echo -e "${BLUE}Welche Komponente möchten Sie starten?${NC}" + echo -e "${WHITE}1. Backend-Server starten (Flask API)${NC}" + echo -e "${WHITE}2. Frontend-Server starten (Next.js)${NC}" + echo -e "${WHITE}3. Kiosk-Modus starten (Backend Web Interface)${NC}" + echo -e "${WHITE}4. Beide Server starten (Backend + Frontend)${NC}" + echo -e "${WHITE}5. Debug-Server starten${NC}" + echo -e "${WHITE}6. Zurück zum Hauptmenü${NC}" + + read -p "Wählen Sie eine Option (1-6): " choice + + case $choice in + 1) + echo -e "${BLUE}Starte Backend-Server...${NC}" + if [ -d "$VENV_DIR" ]; then + cd "$APP_DIR" + source "$VENV_DIR/bin/activate" + python app.py & + echo -e "${GREEN}Backend-Server gestartet: https://localhost:443${NC}" + deactivate + cd "$PROJECT_DIR" + else + echo -e "${RED}Backend nicht installiert. Bitte installieren Sie zuerst das Backend.${NC}" + fi + ;; + 2) + echo -e "${BLUE}Starte Frontend-Server...${NC}" + if [ -d "$FRONTEND_DIR/node_modules" ]; then + cd "$FRONTEND_DIR" + npm run dev & + echo -e "${GREEN}Frontend-Server gestartet: http://localhost:3000${NC}" + cd "$PROJECT_DIR" + else + echo -e "${RED}Frontend nicht installiert. Bitte installieren Sie zuerst das Frontend.${NC}" + fi + ;; + 3) + echo -e "${BLUE}Starte Kiosk-Modus...${NC}" + if [ $is_root -eq 1 ]; then + systemctl start myp-kiosk + echo -e "${GREEN}Kiosk-Modus gestartet${NC}" + else + echo -e "${RED}Root-Rechte erforderlich für Kiosk-Service${NC}" + fi + ;; + 4) + echo -e "${BLUE}Starte Backend und Frontend...${NC}" + # Backend starten + if [ -d "$VENV_DIR" ]; then + cd "$APP_DIR" + source "$VENV_DIR/bin/activate" + python app.py & + echo -e "${GREEN}Backend gestartet: https://localhost:443${NC}" + deactivate + cd "$PROJECT_DIR" + fi + # Frontend starten + if [ -d "$FRONTEND_DIR/node_modules" ]; then + cd "$FRONTEND_DIR" + npm run dev & + echo -e "${GREEN}Frontend gestartet: http://localhost:3000${NC}" + cd "$PROJECT_DIR" + fi + ;; + 5) + start_debug_server + ;; + 6) + return + ;; + *) + echo -e "${RED}Ungültige Option.${NC}" + ;; + esac + + echo "" + read -p "Drücken Sie ENTER, um fortzufahren..." +} + start_debug_server() { show_header "Debug-Server starten" echo -e "${BLUE}Welchen Debug-Server möchten Sie starten?${NC}" - echo -e "${WHITE}1. Frontend Debug-Server (Next.js)${NC}" - echo -e "${WHITE}2. Backend Debug-Server (Flask)${NC}" - echo -e "${WHITE}3. Beide Debug-Server${NC}" - echo -e "${WHITE}4. Frontend Debug-Server (einfacher HTTP-Server)${NC}" + echo -e "${WHITE}1. Frontend Debug-Server (Next.js Development)${NC}" + echo -e "${WHITE}2. Backend Debug-Server (Flask Debug Mode)${NC}" + echo -e "${WHITE}3. Einfacher HTTP-Server (Debug-Server Verzeichnis)${NC}" - read -p "Wählen Sie eine Option (1-4, Standard: 1): " choice + read -p "Wählen Sie eine Option (1-3): " choice case $choice in 1) - echo -e "${BLUE}Starte Frontend Debug-Server...${NC}" - if [ -d "frontend" ] && check_command npm; then - (cd frontend && npm run dev) & - echo -e "${GREEN}✓ Frontend Debug-Server gestartet${NC}" + if [ -d "$FRONTEND_DIR" ]; then + cd "$FRONTEND_DIR" + npm run dev + cd "$PROJECT_DIR" else - echo -e "${RED}✗ Frontend-Verzeichnis oder npm nicht gefunden${NC}" + echo -e "${RED}Frontend-Verzeichnis nicht gefunden${NC}" fi ;; 2) - echo -e "${BLUE}Starte Backend Debug-Server...${NC}" - if [ -f "backend/app/app.py" ]; then - python_cmd="" - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" - fi - - if [ -n "$python_cmd" ]; then - $python_cmd backend/app/app.py --debug & - echo -e "${GREEN}✓ Backend Debug-Server gestartet${NC}" - else - echo -e "${RED}✗ Python nicht gefunden${NC}" - fi + if [ -d "$VENV_DIR" ]; then + cd "$APP_DIR" + source "$VENV_DIR/bin/activate" + python app.py --debug + deactivate + cd "$PROJECT_DIR" else - echo -e "${RED}✗ Backend-Anwendung nicht gefunden${NC}" + echo -e "${RED}Backend nicht installiert${NC}" fi ;; 3) - echo -e "${BLUE}Starte beide Debug-Server...${NC}" - - # Backend starten - if [ -f "backend/app/app.py" ]; then - python_cmd="" - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" + debug_dir="$FRONTEND_DIR/debug-server" + if [ -d "$debug_dir" ]; then + cd "$debug_dir" + if check_command node; then + node src/app.js + else + echo -e "${RED}Node.js nicht gefunden${NC}" fi - - if [ -n "$python_cmd" ]; then - $python_cmd backend/app/app.py --debug & - echo -e "${GREEN}✓ Backend Debug-Server gestartet${NC}" - fi - fi - - # Frontend starten - if [ -d "frontend" ] && check_command npm; then - (cd frontend && npm run dev) & - echo -e "${GREEN}✓ Frontend Debug-Server gestartet${NC}" - fi - ;; - 4) - echo -e "${BLUE}Starte einfachen HTTP Debug-Server...${NC}" - debug_server_dir="frontend/debug-server" - - if [ -d "$debug_server_dir" ] && check_command node; then - node "$debug_server_dir/src/app.js" & - echo -e "${GREEN}✓ Einfacher Debug-Server gestartet${NC}" + cd "$PROJECT_DIR" else - echo -e "${RED}✗ Debug-Server-Verzeichnis oder Node.js nicht gefunden${NC}" + echo -e "${RED}Debug-Server-Verzeichnis nicht gefunden${NC}" fi ;; *) @@ -461,12 +1094,6 @@ start_debug_server() { ;; esac - echo "" - echo -e "${BLUE}Debug-Server-URLs:${NC}" - echo -e "${WHITE}- Frontend: http://localhost:3000${NC}" - echo -e "${WHITE}- Backend: https://localhost:443${NC}" - echo -e "${WHITE}- Debug-Server: http://localhost:8080${NC}" - echo "" read -p "Drücken Sie ENTER, um fortzufahren..." } @@ -475,8 +1102,8 @@ show_ssl_status() { show_header "SSL-Zertifikat-Status" cert_paths=( - "backend/instance/ssl/myp.crt" - "backend/instance/ssl/myp.key" + "backend/app/certs/myp.crt" + "backend/app/certs/myp.key" "frontend/ssl/myp.crt" "frontend/ssl/myp.key" ) @@ -517,97 +1144,11 @@ show_ssl_status() { read -p "Drücken Sie ENTER, um fortzufahren..." } -install_myp_complete() { - show_header "Vollständige MYP-Installation" - - echo -e "${BLUE}Diese Funktion führt eine vollständige MYP-Installation durch:${NC}" - echo -e "${WHITE}1. Systemvoraussetzungen prüfen${NC}" - echo -e "${WHITE}2. Python-Abhängigkeiten installieren${NC}" - echo -e "${WHITE}3. Node.js-Abhängigkeiten installieren${NC}" - echo -e "${WHITE}4. SSL-Zertifikate erstellen${NC}" - echo -e "${WHITE}5. Datenbank initialisieren${NC}" - echo -e "${WHITE}6. Konfigurationsdateien erstellen${NC}" - echo "" - - read -p "Möchten Sie fortfahren? (j/n, Standard: j): " confirm - if [ "$confirm" = "n" ]; then - return - fi - - # 1. Systemvoraussetzungen prüfen - echo -e "${BLUE}1. Prüfe Systemvoraussetzungen...${NC}" - python_cmd="" - pip_cmd="" - - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" - else - echo -e "${RED}✗ Python nicht gefunden. Bitte installieren Sie Python 3.6+.${NC}" - return 1 - fi - - if check_command pip3; then - pip_cmd="pip3" - elif check_command pip; then - pip_cmd="pip" - else - echo -e "${RED}✗ pip nicht gefunden. Bitte installieren Sie pip.${NC}" - return 1 - fi - - # 2. Python-Abhängigkeiten installieren - echo -e "${BLUE}2. Installiere Python-Abhängigkeiten...${NC}" - if [ -f "backend/requirements.txt" ]; then - exec_command "$pip_cmd install -r backend/requirements.txt" "Installiere Backend-Abhängigkeiten" - else - echo -e "${YELLOW}⚠ requirements.txt nicht gefunden${NC}" - fi - - # 3. Node.js-Abhängigkeiten installieren - if check_command node && check_command npm; then - echo -e "${BLUE}3. Installiere Node.js-Abhängigkeiten...${NC}" - if [ -f "frontend/package.json" ]; then - exec_command "cd frontend && npm install" "Installiere Frontend-Abhängigkeiten" - else - echo -e "${YELLOW}⚠ package.json nicht gefunden${NC}" - fi - else - echo -e "${YELLOW}3. Überspringe Node.js-Abhängigkeiten (Node.js/npm nicht gefunden)${NC}" - fi - - # 4. SSL-Zertifikate erstellen - echo -e "${BLUE}4. Erstelle SSL-Zertifikate...${NC}" - create_ssl_certificates - - # 5. Datenbank initialisieren - echo -e "${BLUE}5. Initialisiere Datenbank...${NC}" - if [ -f "backend/app/models.py" ]; then - exec_command "cd backend && $python_cmd -c 'from app.models import init_db, create_initial_admin; init_db(); create_initial_admin()'" "Initialisiere Datenbank" - fi - - # 6. Konfigurationsdateien erstellen - echo -e "${BLUE}6. Erstelle Konfigurationsdateien...${NC}" - setup_backend_url "https://localhost:443" - - echo "" - echo -e "${GREEN}✓ Vollständige MYP-Installation abgeschlossen!${NC}" - echo "" - echo -e "${BLUE}Nächste Schritte:${NC}" - echo -e "${WHITE}1. Backend starten: python backend/app/app.py${NC}" - echo -e "${WHITE}2. Frontend starten: cd frontend && npm run dev${NC}" - echo -e "${WHITE}3. Anwendung öffnen: https://localhost:443${NC}" - - echo "" - read -p "Drücken Sie ENTER, um fortzufahren..." -} - create_ssl_certificates() { show_header "SSL-Zertifikat-Generator" # Parameter definieren - cert_dir="./backend/instance/ssl" + cert_dir="./backend/app/certs" backend_cert_file="$cert_dir/myp.crt" backend_key_file="$cert_dir/myp.key" frontend_cert_file="$cert_dir/frontend.crt" @@ -665,22 +1206,6 @@ create_ssl_certificates() { return 1 fi - # Überprüfen, ob cryptography installiert ist - cryptography_installed=$($python_cmd -c "try: import cryptography; print('True'); except ImportError: print('False')" 2>/dev/null) - - if [ "$cryptography_installed" != "True" ]; then - echo -e "${YELLOW}Installiere Python-Abhängigkeit 'cryptography'...${NC}" - if check_command pip3; then - exec_command "pip3 install cryptography" "Installiere cryptography-Paket" - elif check_command pip; then - exec_command "pip install cryptography" "Installiere cryptography-Paket" - else - echo -e "${RED}pip nicht gefunden. Kann cryptography nicht installieren.${NC}" - read -p "Drücken Sie ENTER, um fortzufahren..." - return 1 - fi - fi - # Python-Skript zur Zertifikatserstellung erstellen cat > temp_cert_script.py << EOL #!/usr/bin/env python3 @@ -779,21 +1304,26 @@ def create_self_signed_cert(cert_path, key_path, hostname="localhost"): with open(cert_path, "wb") as cert_file: cert_file.write(cert.public_bytes(Encoding.PEM)) - print(f"Selbstsigniertes SSL-Zertifikat für '{hostname}' erstellt:") + print(f"SSL-Zertifikat für '{hostname}' erstellt:") print(f"Zertifikat: {cert_path}") print(f"Schlüssel: {key_path}") - print(f"Gültig für 10 Jahre.") # Backend-Zertifikat erstellen create_self_signed_cert('$backend_cert_file', '$backend_key_file', '$backend_hostname') -# Frontend-Zertifikat erstellen -create_self_signed_cert('$frontend_cert_file', '$frontend_key_file', '$frontend_hostname') +# Frontend SSL-Verzeichnis +frontend_ssl_dir = './frontend/ssl' +os.makedirs(frontend_ssl_dir, exist_ok=True) + +# Frontend-Zertifikat erstellen (Kopie des Backend-Zertifikats) +import shutil +shutil.copy('$backend_cert_file', './frontend/ssl/myp.crt') +shutil.copy('$backend_key_file', './frontend/ssl/myp.key') + +print("SSL-Zertifikate erfolgreich erstellt!") EOL - # Python-Skript ausführbar machen und ausführen - chmod +x temp_cert_script.py - + # Python-Skript ausführen if $python_cmd temp_cert_script.py; then echo -e "${GREEN}SSL-Zertifikate erfolgreich erstellt!${NC}" else @@ -803,264 +1333,10 @@ EOL # Temporäres Skript löschen rm -f temp_cert_script.py - # Zertifikate im System installieren (optional) - if [ $is_root -eq 1 ]; then - read -p "Möchten Sie die Zertifikate im System installieren? (j/n, Standard: n): " install_certs - - if [ "$install_certs" = "j" ]; then - if [ -f "$backend_cert_file" ]; then - echo -e "${BLUE}Installiere Backend-Zertifikat im System...${NC}" - - # Debian/Ubuntu/Raspberry Pi OS - if [ -d "/usr/local/share/ca-certificates" ]; then - cp "$backend_cert_file" /usr/local/share/ca-certificates/myp-backend.crt - update-ca-certificates - # RHEL/CentOS/Fedora - elif [ -d "/etc/pki/ca-trust/source/anchors" ]; then - cp "$backend_cert_file" /etc/pki/ca-trust/source/anchors/myp-backend.crt - update-ca-trust extract - fi - fi - - if [ -f "$frontend_cert_file" ]; then - echo -e "${BLUE}Installiere Frontend-Zertifikat im System...${NC}" - - # Debian/Ubuntu/Raspberry Pi OS - if [ -d "/usr/local/share/ca-certificates" ]; then - cp "$frontend_cert_file" /usr/local/share/ca-certificates/myp-frontend.crt - update-ca-certificates - # RHEL/CentOS/Fedora - elif [ -d "/etc/pki/ca-trust/source/anchors" ]; then - cp "$frontend_cert_file" /etc/pki/ca-trust/source/anchors/myp-frontend.crt - update-ca-trust extract - fi - fi - fi - else - echo -e "${YELLOW}Hinweis: Um die Zertifikate im System zu installieren, starten Sie das Skript als Root.${NC}" - fi - - # Frontend für HTTPS konfigurieren echo "" - read -p "Möchten Sie das Frontend für HTTPS konfigurieren? (j/n, Standard: j): " configure_frontend - - if [ "$configure_frontend" != "n" ]; then - echo -e "${BLUE}Konfiguriere Frontend für HTTPS...${NC}" - - # Kopiere Zertifikate ins Frontend-Verzeichnis - frontend_ssl_dir="./frontend/ssl" - mkdir -p "$frontend_ssl_dir" - - if [ -f "$backend_cert_file" ]; then - cp "$backend_cert_file" "$frontend_ssl_dir/myp.crt" - fi - - if [ -f "$backend_key_file" ]; then - cp "$backend_key_file" "$frontend_ssl_dir/myp.key" - fi - - echo -e "${GREEN}Zertifikate ins Frontend-Verzeichnis kopiert.${NC}" - - # .env.local aktualisieren - env_local_path="./frontend/.env.local" - - if [ -f "$env_local_path" ]; then - env_content=$(cat "$env_local_path") - else - env_content="# MYP Frontend Umgebungsvariablen" - fi - - # SSL-Konfigurationen - ssl_configs=( - "NODE_TLS_REJECT_UNAUTHORIZED=0" - "HTTPS=true" - "SSL_CRT_FILE=./ssl/myp.crt" - "SSL_KEY_FILE=./ssl/myp.key" - "NEXT_PUBLIC_API_URL=https://$backend_hostname" - "NEXT_PUBLIC_BACKEND_HOST=$backend_hostname" - "NEXT_PUBLIC_BACKEND_PROTOCOL=https" - ) - - # Existierende Konfigurationen aktualisieren - for config in "${ssl_configs[@]}"; do - key=$(echo "$config" | cut -d'=' -f1) - - if echo "$env_content" | grep -q "^$key="; then - # Update existierende Konfiguration - env_content=$(echo "$env_content" | sed "s/^$key=.*/$config/") - else - # Neue Konfiguration hinzufügen - env_content="$env_content\n$config" - fi - done - - # Speichern der aktualisierten Umgebungsvariablen - echo -e "$env_content" > "$env_local_path" - echo -e "${GREEN}.env.local Datei mit SSL-Konfigurationen aktualisiert.${NC}" - fi - - echo "" - echo -e "${BLUE}SSL-Zertifikate wurden in folgenden Pfaden gespeichert:${NC}" - echo -e "${WHITE}Backend: $backend_cert_file${NC}" - echo -e "${WHITE}Frontend: $frontend_cert_file${NC}" - - echo "" - read -p "Drücken Sie ENTER, um fortzufahren..." -} - -setup_environment() { - show_header "Umgebungs-Setup" - - # Prüfen, ob Python und pip installiert sind - python_cmd="" - pip_cmd="" - - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" - else - echo -e "${RED}Python ist nicht installiert. Bitte installieren Sie Python 3.6+ und versuchen Sie es erneut.${NC}" - read -p "Drücken Sie ENTER, um fortzufahren..." - return 1 - fi - - if check_command pip3; then - pip_cmd="pip3" - elif check_command pip; then - pip_cmd="pip" - else - echo -e "${RED}pip ist nicht installiert. Bitte installieren Sie pip und versuchen Sie es erneut.${NC}" - read -p "Drücken Sie ENTER, um fortzufahren..." - return 1 - fi - - # Python-Abhängigkeiten installieren - echo -e "${BLUE}Installiere Backend-Abhängigkeiten...${NC}" - exec_command "$pip_cmd install -r backend/requirements.txt" "Installiere Python-Abhängigkeiten" - - # Prüfen, ob Node.js und npm installiert sind - if check_command node && check_command npm; then - # Frontend-Abhängigkeiten installieren - echo -e "${BLUE}Installiere Frontend-Abhängigkeiten...${NC}" - exec_command "cd frontend && npm install" "Installiere Node.js-Abhängigkeiten" - else - echo -e "${YELLOW}Node.js oder npm ist nicht installiert. Frontend-Abhängigkeiten werden übersprungen.${NC}" - fi - - # Docker-Compose Datei aktualisieren - docker_compose_file="docker-compose.yml" - if [ -f "$docker_compose_file" ]; then - if ! grep -q -- "--dual-protocol" "$docker_compose_file"; then - # Backup erstellen - cp "$docker_compose_file" "${docker_compose_file}.bak" - - # Konfiguration aktualisieren - sed -i 's/command: python -m app\.app/command: python -m app.app --dual-protocol/g' "$docker_compose_file" - - echo -e "${GREEN}Docker-Compose-Datei wurde aktualisiert, um den dual-protocol-Modus zu aktivieren.${NC}" - else - echo -e "${GREEN}Docker-Compose-Datei ist bereits korrekt konfiguriert.${NC}" - fi - fi - - # Datenbank initialisieren - echo -e "${BLUE}Initialisiere Datenbank...${NC}" - if [ -f "backend/app/models.py" ]; then - exec_command "cd backend && $python_cmd -c 'from app.models import init_db, create_initial_admin; init_db(); create_initial_admin()'" "Initialisiere Datenbank und Admin-Benutzer" - fi - - echo "" - echo -e "${GREEN}Umgebungs-Setup abgeschlossen!${NC}" - - echo "" - read -p "Drücken Sie ENTER, um fortzufahren..." -} - -start_application() { - show_header "Anwendung starten" - - echo -e "${BLUE}Wie möchten Sie die Anwendung starten?${NC}" - echo -e "${WHITE}1. Backend-Server starten (Python)${NC}" - echo -e "${WHITE}2. Frontend-Server starten (Node.js)${NC}" - echo -e "${WHITE}3. Beide Server starten (in separaten Terminals)${NC}" - echo -e "${WHITE}4. Mit Docker Compose starten${NC}" - echo -e "${WHITE}5. Vollständige Installation und Start${NC}" - echo -e "${WHITE}6. Debug-Server starten${NC}" - echo -e "${WHITE}7. Zurück zum Hauptmenü${NC}" - - read -p "Wählen Sie eine Option (1-7): " choice - - case $choice in - 1) - echo -e "${BLUE}Starte Backend-Server...${NC}" - python_cmd="" - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" - fi - - if [ -n "$python_cmd" ]; then - $python_cmd backend/app/app.py & - echo -e "${GREEN}Backend-Server läuft jetzt im Hintergrund.${NC}" - else - echo -e "${RED}Python nicht gefunden.${NC}" - fi - ;; - 2) - echo -e "${BLUE}Starte Frontend-Server...${NC}" - if check_command npm; then - (cd frontend && npm run dev) & - echo -e "${GREEN}Frontend-Server läuft jetzt im Hintergrund.${NC}" - else - echo -e "${RED}npm nicht gefunden.${NC}" - fi - ;; - 3) - echo -e "${BLUE}Starte Backend-Server...${NC}" - python_cmd="" - if check_command python3; then - python_cmd="python3" - elif check_command python; then - python_cmd="python" - fi - - if [ -n "$python_cmd" ]; then - $python_cmd backend/app/app.py & - echo -e "${GREEN}Backend-Server gestartet.${NC}" - fi - - echo -e "${BLUE}Starte Frontend-Server...${NC}" - if check_command npm; then - (cd frontend && npm run dev) & - echo -e "${GREEN}Frontend-Server gestartet.${NC}" - fi - - echo -e "${GREEN}Beide Server laufen jetzt im Hintergrund.${NC}" - ;; - 4) - if check_command docker && check_command docker-compose; then - echo -e "${BLUE}Starte Anwendung mit Docker Compose...${NC}" - exec_command "docker-compose up -d" "Starte Docker Container" - echo -e "${GREEN}Docker Container wurden gestartet.${NC}" - else - echo -e "${RED}Docker oder Docker Compose ist nicht installiert.${NC}" - fi - ;; - 5) - install_myp_complete - ;; - 6) - start_debug_server - ;; - 7) - return - ;; - *) - echo -e "${RED}Ungültige Option.${NC}" - ;; - esac + echo -e "${BLUE}SSL-Zertifikate wurden erstellt:${NC}" + echo -e "${WHITE}- Backend: $backend_cert_file${NC}" + echo -e "${WHITE}- Frontend: frontend/ssl/myp.crt${NC}" echo "" read -p "Drücken Sie ENTER, um fortzufahren..." @@ -1070,7 +1346,7 @@ show_project_info() { show_header "Projekt-Informationen" echo -e "${CYAN}MYP (Mercedes-Benz Yard Printing) Platform${NC}" - echo -e "${BLUE}Version 3.0${NC}" + echo -e "${BLUE}Version 4.0${NC}" echo "" echo -e "${BLUE}Beschreibung:${NC}" echo -e "${WHITE}Eine vollständige 3D-Drucker-Management-Plattform für Mercedes-Benz Werk 040 Berlin.${NC}" @@ -1078,6 +1354,7 @@ show_project_info() { echo -e "${BLUE}Komponenten:${NC}" echo -e "${WHITE}- Backend: Flask-basierte REST API${NC}" echo -e "${WHITE}- Frontend: Next.js React-Anwendung${NC}" + echo -e "${WHITE}- Kiosk-Modus: Backend Web Interface${NC}" echo -e "${WHITE}- Datenbank: SQLite${NC}" echo -e "${WHITE}- Authentifizierung: GitHub OAuth + lokale Benutzer${NC}" echo -e "${WHITE}- SSL/TLS: Selbstsignierte Zertifikate${NC}" @@ -1085,13 +1362,16 @@ show_project_info() { echo -e "${BLUE}Standard-Zugangsdaten:${NC}" echo -e "${WHITE}- Admin E-Mail: admin@mercedes-benz.com${NC}" echo -e "${WHITE}- Admin Passwort: 744563017196A${NC}" - echo -e "${WHITE}- Router Passwort: vT6Vsd^p${NC}" echo "" echo -e "${BLUE}URLs:${NC}" - echo -e "${WHITE}- Backend: https://localhost:443 oder https://raspberrypi:443${NC}" - echo -e "${WHITE}- Frontend: https://localhost:3000${NC}" + echo -e "${WHITE}- Backend API: https://localhost:443${NC}" + echo -e "${WHITE}- Frontend: http://localhost:3000${NC}" + echo -e "${WHITE}- Kiosk-Modus: https://localhost:443 (Vollbild)${NC}" echo "" - echo -e "${YELLOW}Weitere Informationen finden Sie in der CREDENTIALS.md Datei.${NC}" + echo -e "${BLUE}Deployment-Modi:${NC}" + echo -e "${WHITE}- Entwicklung: Backend + Frontend getrennt${NC}" + echo -e "${WHITE}- Produktion: Backend + Kiosk-Modus${NC}" + echo -e "${WHITE}- Vollständig: Alle Komponenten${NC}" echo "" read -p "Drücken Sie ENTER, um fortzufahren..." @@ -1137,72 +1417,181 @@ clean_old_files() { read -p "Drücken Sie ENTER, um fortzufahren..." } +# Installationsmenü anzeigen +show_installation_menu() { + show_header "Installations-Menü" + + echo -e "${WHITE}📦 SYSTEM-KOMPONENTEN:${NC}" + echo -e "${WHITE}1. System-Abhängigkeiten installieren${NC}" + echo -e "${WHITE}2. Python & Node.js Umgebung einrichten${NC}" + echo "" + echo -e "${WHITE}🔧 HAUPT-KOMPONENTEN:${NC}" + echo -e "${WHITE}3. Backend installieren (Flask API)${NC}" + echo -e "${WHITE}4. Frontend installieren (Next.js React)${NC}" + echo -e "${WHITE}5. Kiosk-Modus installieren (Backend Web Interface)${NC}" + echo "" + echo -e "${WHITE}🎯 VOLLINSTALLATIONEN:${NC}" + echo -e "${WHITE}6. Alles installieren (Backend + Frontend)${NC}" + echo -e "${WHITE}7. Produktions-Setup (Backend + Kiosk)${NC}" + echo -e "${WHITE}8. Entwicklungs-Setup (Backend + Frontend + Tools)${NC}" + echo "" + echo -e "${WHITE}⚙️ KONFIGURATION:${NC}" + echo -e "${WHITE}9. SSL-Zertifikate erstellen${NC}" + echo -e "${WHITE}10. Host-Konfiguration einrichten${NC}" + echo -e "${WHITE}11. Backend-URL konfigurieren${NC}" + echo "" + echo -e "${WHITE}🔍 SYSTEM & TESTS:${NC}" + echo -e "${WHITE}12. Systemvoraussetzungen prüfen${NC}" + echo -e "${WHITE}13. Backend-Verbindung testen${NC}" + echo -e "${WHITE}14. SSL-Status anzeigen${NC}" + echo "" + echo -e "${WHITE}🚀 ANWENDUNG STARTEN:${NC}" + echo -e "${WHITE}15. Server starten (Backend/Frontend/Kiosk)${NC}" + echo "" + echo -e "${WHITE}ℹ️ SONSTIGES:${NC}" + echo -e "${WHITE}16. Projekt-Informationen${NC}" + echo -e "${WHITE}17. Alte Dateien bereinigen${NC}" + echo -e "${WHITE}18. Zurück zum Hauptmenü${NC}" + echo -e "${WHITE}0. Beenden${NC}" + echo "" + + read -p "Wählen Sie eine Option (0-18): " choice + + case $choice in + 1) + install_system_dependencies + show_installation_menu + ;; + 2) + setup_python_node_environment + show_installation_menu + ;; + 3) + install_backend + show_installation_menu + ;; + 4) + install_frontend + show_installation_menu + ;; + 5) + install_kiosk_mode + show_installation_menu + ;; + 6) + install_everything + show_installation_menu + ;; + 7) + install_production_setup + show_installation_menu + ;; + 8) + install_development_setup + show_installation_menu + ;; + 9) + create_ssl_certificates + show_installation_menu + ;; + 10) + setup_hosts + show_installation_menu + ;; + 11) + setup_backend_url + show_installation_menu + ;; + 12) + test_dependencies + show_installation_menu + ;; + 13) + test_backend_connection + show_installation_menu + ;; + 14) + show_ssl_status + show_installation_menu + ;; + 15) + start_application + show_installation_menu + ;; + 16) + show_project_info + show_installation_menu + ;; + 17) + clean_old_files + show_installation_menu + ;; + 18) + show_main_menu + ;; + 0) + echo -e "${GREEN}Auf Wiedersehen!${NC}" + exit 0 + ;; + *) + echo -e "${RED}Ungültige Option. Bitte versuchen Sie es erneut.${NC}" + sleep 2 + show_installation_menu + ;; + esac +} + # Hauptmenü anzeigen show_main_menu() { show_header "Hauptmenü" - echo -e "${WHITE}1. Systemvoraussetzungen prüfen${NC}" - echo -e "${WHITE}2. Host-Konfiguration einrichten${NC}" - echo -e "${WHITE}3. SSL-Zertifikate erstellen${NC}" - echo -e "${WHITE}4. Umgebung einrichten (Abhängigkeiten installieren)${NC}" - echo -e "${WHITE}5. Anwendung starten${NC}" - echo -e "${WHITE}6. Backend-Verbindung testen${NC}" - echo -e "${WHITE}7. Backend-URL konfigurieren${NC}" - echo -e "${WHITE}8. SSL-Zertifikat-Status anzeigen${NC}" - echo -e "${WHITE}9. Vollständige MYP-Installation${NC}" - echo -e "${WHITE}10. Projekt-Informationen anzeigen${NC}" - echo -e "${WHITE}11. Alte Dateien bereinigen${NC}" - echo -e "${WHITE}12. Beenden${NC}" + echo -e "${WHITE}🚀 SCHNELLSTART:${NC}" + echo -e "${WHITE}1. Vollständige Installation (Alle Komponenten)${NC}" + echo -e "${WHITE}2. Backend-Only Installation (Kiosk-ready)${NC}" + echo -e "${WHITE}3. Entwicklungs-Setup (Backend + Frontend)${NC}" + echo "" + echo -e "${WHITE}⚙️ GRANULARE INSTALLATION:${NC}" + echo -e "${WHITE}4. Granulare Installation & Konfiguration${NC}" + echo "" + echo -e "${WHITE}🔧 SYSTEM & WARTUNG:${NC}" + echo -e "${WHITE}5. Systemvoraussetzungen prüfen${NC}" + echo -e "${WHITE}6. Anwendung starten${NC}" + echo -e "${WHITE}7. Projekt-Informationen${NC}" + echo "" + echo -e "${WHITE}0. Beenden${NC}" echo "" - read -p "Wählen Sie eine Option (1-12): " choice + read -p "Wählen Sie eine Option (0-7): " choice case $choice in 1) - test_dependencies + install_everything show_main_menu ;; 2) - setup_hosts + install_production_setup show_main_menu ;; 3) - create_ssl_certificates + install_development_setup show_main_menu ;; 4) - setup_environment - show_main_menu + show_installation_menu ;; 5) - start_application + test_dependencies show_main_menu ;; 6) - test_backend_connection + start_application show_main_menu ;; 7) - setup_backend_url - show_main_menu - ;; - 8) - show_ssl_status - show_main_menu - ;; - 9) - install_myp_complete - show_main_menu - ;; - 10) show_project_info show_main_menu ;; - 11) - clean_old_files - show_main_menu - ;; - 12) + 0) echo -e "${GREEN}Auf Wiedersehen!${NC}" exit 0 ;;