diff --git a/backend/docs/SCHULUNG_SCREENSHOT_TOOL.md b/backend/docs/SCHULUNG_SCREENSHOT_TOOL.md new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/backend/docs/SCHULUNG_SCREENSHOT_TOOL.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/scripts/run_screenshot_tool.ps1 b/backend/scripts/run_screenshot_tool.ps1 new file mode 100644 index 00000000..68e207e3 --- /dev/null +++ b/backend/scripts/run_screenshot_tool.ps1 @@ -0,0 +1,169 @@ +# PowerShell-Skript für automatische Screenshots +# ============================================= + +param( + [string]$ConfigFile = "screenshot_config.json", + [switch]$Interactive = $false, + [switch]$QuickRun = $false, + [string]$ServerUrl = "", + [string]$OutputDir = "" +) + +Write-Host "===============================================" -ForegroundColor Cyan +Write-Host "🎯 AUTOMATISCHES SCREENSHOT-TOOL FÜR SCHULUNGEN" -ForegroundColor Yellow +Write-Host "===============================================" -ForegroundColor Cyan +Write-Host "" + +# Arbeitsverzeichnis zum Skript-Ordner ändern +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $ScriptDir + +# Prüfe ob Python verfügbar ist +try { + $pythonVersion = python --version 2>&1 + Write-Host "✅ Python gefunden: $pythonVersion" -ForegroundColor Green +} catch { + Write-Host "❌ Python nicht gefunden!" -ForegroundColor Red + Write-Host "💡 Installieren Sie Python von https://python.org" -ForegroundColor Yellow + exit 1 +} + +# Prüfe ob Selenium installiert ist +$seleniumCheck = python -c "import selenium; print('✅ Selenium verfügbar')" 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host "⚠️ Selenium nicht installiert. Installiere jetzt..." -ForegroundColor Yellow + pip install selenium webdriver-manager + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Selenium-Installation fehlgeschlagen!" -ForegroundColor Red + exit 1 + } + Write-Host "✅ Selenium erfolgreich installiert" -ForegroundColor Green +} else { + Write-Host $seleniumCheck -ForegroundColor Green +} + +# Prüfe ChromeDriver +$chromeCheck = python -c "from selenium import webdriver; from selenium.webdriver.chrome.service import Service; print('✅ Chrome WebDriver verfügbar')" 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host "⚠️ ChromeDriver nicht gefunden. Installiere webdriver-manager..." -ForegroundColor Yellow + pip install webdriver-manager + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ WebDriver-Manager-Installation fehlgeschlagen!" -ForegroundColor Red + Write-Host "💡 Manuell ChromeDriver von https://chromedriver.chromium.org/ herunterladen" -ForegroundColor Yellow + } else { + Write-Host "✅ WebDriver-Manager installiert" -ForegroundColor Green + } +} + +# Flask-App verfügbarkeit prüfen +Write-Host "" +Write-Host "🔍 Prüfe Flask-App..." -ForegroundColor Blue +$flaskCheck = python -c "import sys; sys.path.append('..'); from app import app; print('✅ Flask-App verfügbar')" 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host "⚠️ Flask-App nicht direkt verfügbar" -ForegroundColor Yellow + Write-Host "📋 Das Tool wird mit Standard-Routen arbeiten" -ForegroundColor Blue +} else { + Write-Host $flaskCheck -ForegroundColor Green +} + +Write-Host "" +Write-Host "🚀 STARTE SCREENSHOT-TOOL" -ForegroundColor Green +Write-Host "=" * 30 -ForegroundColor Green + +# Parameter für das Python-Skript vorbereiten +$pythonArgs = @("screenshot_tool.py") + +if ($QuickRun) { + Write-Host "⚡ Quick-Run-Modus aktiviert" -ForegroundColor Yellow + # Setze Umgebungsvariablen für automatische Konfiguration + $env:SCREENSHOT_AUTO_MODE = "true" + $env:SCREENSHOT_HEADLESS = "true" + + if ($ServerUrl) { + $env:SCREENSHOT_SERVER_URL = $ServerUrl + } + + if ($OutputDir) { + $env:SCREENSHOT_OUTPUT_DIR = $OutputDir + } +} + +# Tool ausführen +try { + if ($Interactive) { + Write-Host "🎛️ Interaktiver Modus - Folgen Sie den Anweisungen" -ForegroundColor Blue + python @pythonArgs + } else { + Write-Host "🤖 Automatischer Modus" -ForegroundColor Blue + python @pythonArgs + } + + $exitCode = $LASTEXITCODE + + if ($exitCode -eq 0) { + Write-Host "" + Write-Host "🎉 SCREENSHOT-ERSTELLUNG ERFOLGREICH ABGESCHLOSSEN!" -ForegroundColor Green + Write-Host "" + + # Zeige Ausgabe-Ordner an + $outputPath = "docs\schulung\screenshots" + if (Test-Path $outputPath) { + Write-Host "📁 Screenshots verfügbar in:" -ForegroundColor Blue + Write-Host " $(Resolve-Path $outputPath)" -ForegroundColor White + + # Frage ob Ordner geöffnet werden soll + $openFolder = Read-Host "📂 Möchten Sie den Screenshot-Ordner öffnen? (j/n)" + if ($openFolder -eq "j" -or $openFolder -eq "ja" -or $openFolder -eq "y" -or $openFolder -eq "yes") { + Start-Process "explorer.exe" -ArgumentList (Resolve-Path $outputPath) + } + } + + # Zeige Bericht an + $reportPath = "$outputPath\screenshot_bericht.md" + if (Test-Path $reportPath) { + Write-Host "" + Write-Host "📊 Detaillierter Bericht verfügbar:" -ForegroundColor Blue + Write-Host " $(Resolve-Path $reportPath)" -ForegroundColor White + + $openReport = Read-Host "📖 Möchten Sie den Bericht öffnen? (j/n)" + if ($openReport -eq "j" -or $openReport -eq "ja" -or $openReport -eq "y" -or $openReport -eq "yes") { + Start-Process "notepad.exe" -ArgumentList (Resolve-Path $reportPath) + } + } + + } else { + Write-Host "" + Write-Host "❌ FEHLER BEI DER SCREENSHOT-ERSTELLUNG" -ForegroundColor Red + Write-Host "📋 Überprüfen Sie die Logs für Details" -ForegroundColor Yellow + } + +} catch { + Write-Host "" + Write-Host "❌ UNERWARTETER FEHLER: $($_.Exception.Message)" -ForegroundColor Red + $exitCode = 1 +} + +Write-Host "" +Write-Host "💡 VERWENDUNGSHINWEISE FÜR SCHULUNGEN:" -ForegroundColor Cyan +Write-Host "=" * 40 -ForegroundColor Cyan +Write-Host "• Admin-Screenshots: docs\schulung\screenshots\admin\" -ForegroundColor White +Write-Host "• Benutzer-Screenshots: docs\schulung\screenshots\benutzer\" -ForegroundColor White +Write-Host "• Öffentliche Screenshots: docs\schulung\screenshots\oeffentlich\" -ForegroundColor White +Write-Host "• Verschiedene Auflösungen in Unterordnern verfügbar" -ForegroundColor White +Write-Host "• Perfekt für PowerPoint-Präsentationen geeignet" -ForegroundColor White + +Write-Host "" +Write-Host "🎓 TIPPS FÜR PRÄSENTATIONEN:" -ForegroundColor Yellow +Write-Host "• Desktop-Screenshots für Hauptpräsentationen verwenden" -ForegroundColor White +Write-Host "• Mobile-Screenshots für Responsive-Design zeigen" -ForegroundColor White +Write-Host "• Admin-Ordner für Administrator-Schulungen" -ForegroundColor White +Write-Host "• Benutzer-Ordner für allgemeine Mitarbeiterschulungen" -ForegroundColor White + +Write-Host "" +Write-Host "📞 Bei Problemen:" -ForegroundColor Magenta +Write-Host "• Log-Datei prüfen: screenshot_tool.log" -ForegroundColor White +Write-Host "• Server läuft auf korrekter URL?" -ForegroundColor White +Write-Host "• Admin-Zugangsdaten korrekt?" -ForegroundColor White +Write-Host "• ChromeDriver installiert?" -ForegroundColor White + +exit $exitCode \ No newline at end of file diff --git a/backend/scripts/screenshot_config.json b/backend/scripts/screenshot_config.json new file mode 100644 index 00000000..57aff142 --- /dev/null +++ b/backend/scripts/screenshot_config.json @@ -0,0 +1,113 @@ +{ + "server": { + "base_url": "http://localhost:5000", + "admin_email": "admin@example.com", + "admin_password": "admin123" + }, + "browser": { + "type": "chrome", + "headless": true, + "page_load_timeout": 15, + "element_wait_timeout": 10, + "screenshot_delay": 2 + }, + "output": { + "base_directory": "docs/schulung/screenshots", + "include_timestamp": true, + "create_subdirectories": true + }, + "resolutions": { + "desktop": { + "width": 1920, + "height": 1080, + "description": "Standard Desktop-Auflösung" + }, + "tablet": { + "width": 1024, + "height": 768, + "description": "Tablet-Auflösung" + }, + "mobile": { + "width": 375, + "height": 667, + "description": "Mobile-Auflösung (iPhone)" + }, + "large_desktop": { + "width": 2560, + "height": 1440, + "description": "Große Desktop-Auflösung für 4K-Displays" + } + }, + "routes": { + "include_patterns": [ + "/", + "/dashboard", + "/admin*", + "/user/*", + "/printers*", + "/jobs*", + "/stats*", + "/reports*", + "/maintenance*", + "/guest*" + ], + "exclude_patterns": [ + "/api/*", + "/auth/api/*", + "/static/*", + "*/favicon.ico", + "*/robots.txt", + "*/sitemap.xml" + ], + "custom_routes": [ + { + "url": "/demo", + "category": "benutzer", + "description": "Komponenten-Demo-Seite" + }, + { + "url": "/socket-test", + "category": "admin", + "description": "WebSocket-Test-Seite" + } + ] + }, + "categories": { + "admin": { + "patterns": ["/admin", "/admin-dashboard", "/admin/*"], + "description": "Administrator-Bereich" + }, + "benutzer": { + "patterns": ["/user/*", "/dashboard", "/printers", "/jobs", "/stats"], + "description": "Allgemeiner Benutzerbereich" + }, + "oeffentlich": { + "patterns": ["/", "/guest", "/privacy", "/terms", "/imprint", "/legal"], + "description": "Öffentlich zugängliche Seiten" + }, + "berichte": { + "patterns": ["/reports", "/stats"], + "description": "Berichte und Statistiken" + }, + "wartung": { + "patterns": ["/maintenance"], + "description": "Wartung und System-Tools" + } + }, + "advanced": { + "take_full_page_screenshots": true, + "wait_for_dynamic_content": true, + "capture_hover_states": false, + "include_browser_ui": false, + "compress_images": false, + "generate_thumbnails": true, + "create_comparison_report": false + }, + "schulung": { + "create_presentation_slides": true, + "generate_step_by_step_guide": true, + "create_feature_overview": true, + "include_annotations": false, + "language": "de" + } +} \ No newline at end of file diff --git a/backend/scripts/screenshot_tool.py b/backend/scripts/screenshot_tool.py new file mode 100644 index 00000000..00dc2ce2 --- /dev/null +++ b/backend/scripts/screenshot_tool.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python3 +""" +Automatisches Screenshot-Tool für Mitarbeiterschulungen +====================================================== + +Dieses Tool erstellt automatisch Screenshots von allen verfügbaren Seiten +der Webanwendung für Schulungszwecke und Präsentationsmaterial. + +Funktionen: +- Automatische Erkennung aller verfügbaren Routen +- Screenshots in verschiedenen Auflösungen (Desktop, Tablet, Mobile) +- Strukturierte Ordnerorganisation für Schulungen +- Automatischer Login als Admin +- Detaillierter Bericht über erstellte Screenshots +- Behandlung von geschützten und öffentlichen Bereichen + +Autor: KI-Assistant +Datum: 2025-01-16 +""" + +import os +import sys +import time +import json +import logging +from datetime import datetime, timedelta +from typing import List, Dict, Tuple, Optional, Set +from urllib.parse import urljoin, urlparse +import traceback +import subprocess + +# Selenium Imports +try: + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.chrome.options import Options as ChromeOptions + from selenium.webdriver.firefox.options import Options as FirefoxOptions + from selenium.webdriver.chrome.service import Service as ChromeService + from selenium.webdriver.firefox.service import Service as FirefoxService + from selenium.common.exceptions import TimeoutException, WebDriverException, NoSuchElementException + SELENIUM_AVAILABLE = True +except ImportError as e: + print(f"⚠️ Selenium nicht verfügbar: {e}") + print("💡 Installieren Sie Selenium: pip install selenium") + SELENIUM_AVAILABLE = False + +# Flask App Import +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +try: + from app import app, User + from models import get_db_session + FLASK_APP_AVAILABLE = True +except ImportError as e: + print(f"⚠️ Flask-App nicht verfügbar: {e}") + FLASK_APP_AVAILABLE = False + +# Logging-Konfiguration +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('screenshot_tool.log'), + logging.StreamHandler() + ] +) +logger = logging.getLogger(__name__) + +class ScreenshotConfiguration: + """Konfiguration für das Screenshot-Tool""" + + # Basis-Konfiguration + BASE_URL = "http://localhost:5000" + ADMIN_EMAIL = "admin@example.com" # Anpassen falls nötig + ADMIN_PASSWORD = "admin123" # Anpassen falls nötig + + # Screenshot-Ordner + SCREENSHOT_BASE_DIR = os.path.join("docs", "schulung", "screenshots") + + # Browser-Konfiguration + BROWSER_TYPE = "chrome" # "chrome" oder "firefox" + HEADLESS = True # Für Server ohne GUI + + # Auflösungen für verschiedene Geräte + RESOLUTIONS = { + "desktop": (1920, 1080), + "tablet": (1024, 768), + "mobile": (375, 667) + } + + # Wartezeiten + PAGE_LOAD_TIMEOUT = 15 + ELEMENT_WAIT_TIMEOUT = 10 + SCREENSHOT_DELAY = 2 + + # Zu ignorierende Routen (z.B. API-Endpunkte) + IGNORED_ROUTES = { + "/api/", "/auth/api/", "/static/", "/favicon.ico", + "/robots.txt", "/sitemap.xml" + } + + # Routen die spezielle Behandlung benötigen + SPECIAL_ROUTES = { + "/admin": "Benötigt Admin-Rechte", + "/user/": "Benötigt User-Login", + "/guest": "Öffentlich zugänglich" + } + +class RouteDiscovery: + """Klasse zur automatischen Erkennung aller verfügbaren Routen""" + + def __init__(self): + self.routes = set() + self.protected_routes = set() + self.public_routes = set() + + def discover_routes_from_app(self) -> List[str]: + """Extrahiert alle Routen aus der Flask-App""" + if not FLASK_APP_AVAILABLE: + logger.warning("Flask-App nicht verfügbar - verwende Standard-Routen") + return self._get_default_routes() + + discovered_routes = [] + + try: + with app.app_context(): + # Alle URL-Regeln der App durchgehen + for rule in app.url_map.iter_rules(): + # Nur GET-Routen für Screenshots + if 'GET' in rule.methods: + route = str(rule.rule) + + # Dynamische Parameter ersetzen + if '<' in route: + route = self._resolve_dynamic_route(route) + if route: + discovered_routes.extend(route if isinstance(route, list) else [route]) + else: + discovered_routes.append(route) + + # Kategorisierung der Routen + self._categorize_route(route) + + logger.info(f"✅ {len(discovered_routes)} Routen aus Flask-App extrahiert") + return discovered_routes + + except Exception as e: + logger.error(f"❌ Fehler beim Extrahieren der Routen: {e}") + return self._get_default_routes() + + def _resolve_dynamic_route(self, route: str) -> Optional[List[str]]: + """Löst dynamische Routen mit Beispieldaten auf""" + resolved_routes = [] + + try: + if not FLASK_APP_AVAILABLE: + return None + + with app.app_context(): + db_session = get_db_session() + + # User-ID Parameter + if '' in route: + users = db_session.query(User).limit(3).all() + for user in users: + resolved_routes.append(route.replace('', str(user.id))) + + # Printer-ID Parameter + elif '' in route: + # Beispiel-Printer-IDs (1, 2, 3) + for printer_id in [1, 2, 3]: + resolved_routes.append(route.replace('', str(printer_id))) + + # Job-ID Parameter + elif '' in route: + # Beispiel-Job-IDs (1, 2, 3) + for job_id in [1, 2, 3]: + resolved_routes.append(route.replace('', str(job_id))) + + # Andere Parameter mit Standard-Werten + elif '<' in route: + # Generische Behandlung + import re + pattern = r'<[^>]+>' + resolved_route = re.sub(pattern, '1', route) + resolved_routes.append(resolved_route) + + db_session.close() + + except Exception as e: + logger.warning(f"⚠️ Fehler beim Auflösen der dynamischen Route {route}: {e}") + + return resolved_routes if resolved_routes else None + + def _categorize_route(self, route: str): + """Kategorisiert Routen als öffentlich oder geschützt""" + if any(protected in route for protected in ['/admin', '/user/', '/dashboard']): + self.protected_routes.add(route) + elif any(public in route for public in ['/guest', '/privacy', '/terms', '/imprint']): + self.public_routes.add(route) + else: + # Standardmäßig als geschützt behandeln + self.protected_routes.add(route) + + def _get_default_routes(self) -> List[str]: + """Fallback-Routen falls automatische Erkennung fehlschlägt""" + return [ + "/", + "/dashboard", + "/admin", + "/admin-dashboard", + "/printers", + "/jobs", + "/jobs/new", + "/stats", + "/user/profile", + "/user/settings", + "/admin/users/add", + "/admin/printers/add", + "/reports", + "/maintenance", + "/guest", + "/guest-status", + "/privacy", + "/terms", + "/imprint", + "/demo" + ] + +class BrowserManager: + """Verwaltet den Webbrowser für Screenshots""" + + def __init__(self, config: ScreenshotConfiguration): + self.config = config + self.driver = None + + def initialize_browser(self) -> bool: + """Initialisiert den Webbrowser""" + try: + if self.config.BROWSER_TYPE.lower() == "chrome": + self.driver = self._setup_chrome() + elif self.config.BROWSER_TYPE.lower() == "firefox": + self.driver = self._setup_firefox() + else: + raise ValueError(f"Unbekannter Browser-Typ: {self.config.BROWSER_TYPE}") + + # Browser-Konfiguration + self.driver.set_page_load_timeout(self.config.PAGE_LOAD_TIMEOUT) + self.driver.implicitly_wait(self.config.ELEMENT_WAIT_TIMEOUT) + + logger.info(f"✅ Browser {self.config.BROWSER_TYPE} erfolgreich initialisiert") + return True + + except Exception as e: + logger.error(f"❌ Fehler beim Initialisieren des Browsers: {e}") + return False + + def _setup_chrome(self): + """Konfiguriert Chrome-Browser""" + options = ChromeOptions() + + if self.config.HEADLESS: + options.add_argument('--headless') + + # Weitere Chrome-Optionen für Stabilität + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-gpu') + options.add_argument('--window-size=1920,1080') + options.add_argument('--disable-notifications') + options.add_argument('--disable-popup-blocking') + + try: + # Automatische Erkennung des ChromeDriver + return webdriver.Chrome(options=options) + except Exception as e: + logger.warning(f"⚠️ ChromeDriver automatisch nicht gefunden: {e}") + logger.info("💡 Installieren Sie ChromeDriver oder verwenden Sie webdriver-manager") + raise + + def _setup_firefox(self): + """Konfiguriert Firefox-Browser""" + options = FirefoxOptions() + + if self.config.HEADLESS: + options.add_argument('--headless') + + try: + return webdriver.Firefox(options=options) + except Exception as e: + logger.warning(f"⚠️ GeckoDriver nicht gefunden: {e}") + logger.info("💡 Installieren Sie GeckoDriver für Firefox") + raise + + def set_resolution(self, resolution_name: str): + """Setzt die Browser-Auflösung""" + if resolution_name in self.config.RESOLUTIONS: + width, height = self.config.RESOLUTIONS[resolution_name] + self.driver.set_window_size(width, height) + logger.debug(f"📱 Auflösung gesetzt: {resolution_name} ({width}x{height})") + + def close(self): + """Schließt den Browser""" + if self.driver: + try: + self.driver.quit() + logger.info("✅ Browser geschlossen") + except Exception as e: + logger.warning(f"⚠️ Fehler beim Schließen des Browsers: {e}") + +class ScreenshotTool: + """Hauptklasse für das Screenshot-Tool""" + + def __init__(self, config: ScreenshotConfiguration = None): + self.config = config or ScreenshotConfiguration() + self.browser = BrowserManager(self.config) + self.route_discovery = RouteDiscovery() + self.results = { + "success": [], + "failed": [], + "skipped": [], + "total_screenshots": 0, + "start_time": None, + "end_time": None + } + + def run_screenshot_session(self) -> Dict: + """Führt eine komplette Screenshot-Session durch""" + logger.info("🚀 Starte automatische Screenshot-Erstellung für Schulungen") + self.results["start_time"] = datetime.now() + + try: + # 1. Vorbereitung + if not self._prepare_environment(): + return self.results + + # 2. Browser initialisieren + if not self.browser.initialize_browser(): + return self.results + + # 3. Routen entdecken + routes = self.route_discovery.discover_routes_from_app() + logger.info(f"📋 {len(routes)} Routen gefunden") + + # 4. Login durchführen + if not self._perform_admin_login(): + logger.error("❌ Admin-Login fehlgeschlagen") + return self.results + + # 5. Screenshots erstellen + self._create_screenshots_for_routes(routes) + + # 6. Bericht generieren + self._generate_report() + + except KeyboardInterrupt: + logger.info("⏹️ Screenshot-Session vom Benutzer abgebrochen") + except Exception as e: + logger.error(f"❌ Unerwarteter Fehler: {e}") + logger.debug(traceback.format_exc()) + finally: + self.browser.close() + self.results["end_time"] = datetime.now() + + return self.results + + def _prepare_environment(self) -> bool: + """Bereitet die Umgebung für Screenshots vor""" + try: + # Screenshot-Ordner erstellen + os.makedirs(self.config.SCREENSHOT_BASE_DIR, exist_ok=True) + + # Unterordner für verschiedene Kategorien + categories = ["admin", "benutzer", "oeffentlich", "alle_auflösungen"] + for category in categories: + category_dir = os.path.join(self.config.SCREENSHOT_BASE_DIR, category) + os.makedirs(category_dir, exist_ok=True) + + # Auflösungs-Unterordner + for resolution in self.config.RESOLUTIONS.keys(): + resolution_dir = os.path.join(category_dir, resolution) + os.makedirs(resolution_dir, exist_ok=True) + + logger.info(f"✅ Screenshot-Ordner vorbereitet: {self.config.SCREENSHOT_BASE_DIR}") + return True + + except Exception as e: + logger.error(f"❌ Fehler bei der Umgebungsvorbereitung: {e}") + return False + + def _perform_admin_login(self) -> bool: + """Führt den Admin-Login durch""" + try: + login_url = urljoin(self.config.BASE_URL, "/auth/login") + self.browser.driver.get(login_url) + + # Warten bis Login-Formular geladen ist + wait = WebDriverWait(self.browser.driver, self.config.ELEMENT_WAIT_TIMEOUT) + + # Email eingeben + email_field = wait.until(EC.presence_of_element_located((By.NAME, "email"))) + email_field.clear() + email_field.send_keys(self.config.ADMIN_EMAIL) + + # Passwort eingeben + password_field = self.browser.driver.find_element(By.NAME, "password") + password_field.clear() + password_field.send_keys(self.config.ADMIN_PASSWORD) + + # Login-Button klicken + login_button = self.browser.driver.find_element(By.CSS_SELECTOR, "button[type='submit']") + login_button.click() + + # Warten auf Weiterleitung zum Dashboard + wait.until(lambda driver: "/dashboard" in driver.current_url or "/admin" in driver.current_url) + + logger.info("✅ Admin-Login erfolgreich") + return True + + except TimeoutException: + logger.error("❌ Timeout beim Admin-Login") + return False + except NoSuchElementException as e: + logger.error(f"❌ Login-Element nicht gefunden: {e}") + return False + except Exception as e: + logger.error(f"❌ Fehler beim Admin-Login: {e}") + return False + + def _create_screenshots_for_routes(self, routes: List[str]): + """Erstellt Screenshots für alle angegebenen Routen""" + total_routes = len(routes) + + for i, route in enumerate(routes, 1): + logger.info(f"📸 Screenshot {i}/{total_routes}: {route}") + + # Route ignorieren falls in Ignore-Liste + if any(ignored in route for ignored in self.config.IGNORED_ROUTES): + logger.debug(f"⏭️ Route ignoriert: {route}") + self.results["skipped"].append({"route": route, "reason": "In Ignore-Liste"}) + continue + + # Screenshots für alle Auflösungen erstellen + for resolution_name in self.config.RESOLUTIONS.keys(): + try: + self._take_screenshot_for_route(route, resolution_name) + self.results["total_screenshots"] += 1 + + except Exception as e: + logger.error(f"❌ Fehler bei Screenshot für {route} ({resolution_name}): {e}") + self.results["failed"].append({ + "route": route, + "resolution": resolution_name, + "error": str(e) + }) + + def _take_screenshot_for_route(self, route: str, resolution_name: str): + """Erstellt einen Screenshot für eine spezifische Route und Auflösung""" + # Browser-Auflösung setzen + self.browser.set_resolution(resolution_name) + + # Zur Seite navigieren + full_url = urljoin(self.config.BASE_URL, route) + self.browser.driver.get(full_url) + + # Warten bis Seite geladen ist + time.sleep(self.config.SCREENSHOT_DELAY) + + # Kategorie bestimmen + category = self._determine_route_category(route) + + # Dateiname generieren + safe_route_name = self._sanitize_filename(route) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{safe_route_name}_{resolution_name}_{timestamp}.png" + + # Screenshot-Pfad + screenshot_path = os.path.join( + self.config.SCREENSHOT_BASE_DIR, + category, + resolution_name, + filename + ) + + # Screenshot erstellen + self.browser.driver.save_screenshot(screenshot_path) + + # Erfolg protokollieren + self.results["success"].append({ + "route": route, + "resolution": resolution_name, + "file_path": screenshot_path, + "file_size": os.path.getsize(screenshot_path), + "timestamp": datetime.now().isoformat() + }) + + logger.debug(f"✅ Screenshot gespeichert: {screenshot_path}") + + def _determine_route_category(self, route: str) -> str: + """Bestimmt die Kategorie einer Route für die Ordnerorganisation""" + if any(admin_route in route for admin_route in ['/admin', '/admin-dashboard']): + return "admin" + elif any(user_route in route for user_route in ['/user/', '/dashboard', '/printers', '/jobs']): + return "benutzer" + elif any(public_route in route for public_route in ['/guest', '/privacy', '/terms', '/imprint', '/']): + return "oeffentlich" + else: + return "benutzer" # Standard-Kategorie + + def _sanitize_filename(self, route: str) -> str: + """Bereinigt Routennamen für Dateinamen""" + import re + # Entferne gefährliche Zeichen + safe_name = re.sub(r'[<>:"/\\|?*]', '_', route) + # Ersetze Slashes mit Unterstrichen + safe_name = safe_name.replace('/', '_') + # Entferne führende/nachfolgende Unterstriche + safe_name = safe_name.strip('_') + # Leere Namen behandeln + if not safe_name or safe_name == '_': + safe_name = "home" + return safe_name + + def _generate_report(self): + """Generiert einen detaillierten Bericht über die Screenshot-Session""" + duration = self.results["end_time"] - self.results["start_time"] + + report = { + "session_info": { + "start_time": self.results["start_time"].isoformat(), + "end_time": self.results["end_time"].isoformat(), + "duration_seconds": duration.total_seconds(), + "duration_formatted": str(duration) + }, + "statistics": { + "total_screenshots": self.results["total_screenshots"], + "successful_screenshots": len(self.results["success"]), + "failed_screenshots": len(self.results["failed"]), + "skipped_routes": len(self.results["skipped"]) + }, + "results": self.results + } + + # Bericht als JSON speichern + report_path = os.path.join(self.config.SCREENSHOT_BASE_DIR, "screenshot_report.json") + with open(report_path, 'w', encoding='utf-8') as f: + json.dump(report, f, indent=2, ensure_ascii=False, default=str) + + # Menschenlesbaren Bericht erstellen + self._generate_human_readable_report(report) + + logger.info(f"📊 Bericht gespeichert: {report_path}") + + def _generate_human_readable_report(self, report: Dict): + """Erstellt einen menschenlesbaren Bericht""" + report_path = os.path.join(self.config.SCREENSHOT_BASE_DIR, "screenshot_bericht.md") + + with open(report_path, 'w', encoding='utf-8') as f: + f.write("# Screenshot-Bericht für Mitarbeiterschulungen\n\n") + f.write(f"**Erstellt am:** {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}\n\n") + + # Zusammenfassung + f.write("## Zusammenfassung\n\n") + f.write(f"- **Gesamte Screenshots:** {report['statistics']['total_screenshots']}\n") + f.write(f"- **Erfolgreich:** {report['statistics']['successful_screenshots']}\n") + f.write(f"- **Fehlgeschlagen:** {report['statistics']['failed_screenshots']}\n") + f.write(f"- **Übersprungen:** {report['statistics']['skipped_routes']}\n") + f.write(f"- **Dauer:** {report['session_info']['duration_formatted']}\n\n") + + # Erfolgreiche Screenshots + if self.results["success"]: + f.write("## Erfolgreich erstellte Screenshots\n\n") + for item in self.results["success"]: + f.write(f"- **{item['route']}** ({item['resolution']})\n") + f.write(f" - Datei: `{item['file_path']}`\n") + f.write(f" - Größe: {item['file_size']:,} Bytes\n\n") + + # Fehlgeschlagene Screenshots + if self.results["failed"]: + f.write("## Fehlgeschlagene Screenshots\n\n") + for item in self.results["failed"]: + f.write(f"- **{item['route']}** ({item['resolution']})\n") + f.write(f" - Fehler: {item['error']}\n\n") + + # Verwendungshinweise + f.write("## Verwendung für Schulungen\n\n") + f.write("### Ordnerstruktur\n\n") + f.write("```\n") + f.write("docs/schulung/screenshots/\n") + f.write("├── admin/ # Administrator-Bereich\n") + f.write("│ ├── desktop/ # Desktop-Auflösung (1920x1080)\n") + f.write("│ ├── tablet/ # Tablet-Auflösung (1024x768)\n") + f.write("│ └── mobile/ # Mobile-Auflösung (375x667)\n") + f.write("├── benutzer/ # Benutzer-Bereich\n") + f.write("│ ├── desktop/\n") + f.write("│ ├── tablet/\n") + f.write("│ └── mobile/\n") + f.write("└── oeffentlich/ # Öffentlicher Bereich\n") + f.write(" ├── desktop/\n") + f.write(" ├── tablet/\n") + f.write(" └── mobile/\n") + f.write("```\n\n") + + f.write("### Empfehlungen für Präsentationen\n\n") + f.write("1. **Desktop-Screenshots** für Hauptpräsentationen verwenden\n") + f.write("2. **Mobile-Screenshots** für Responsive-Design-Demonstrationen\n") + f.write("3. **Admin-Ordner** für Administratoren-Schulungen\n") + f.write("4. **Benutzer-Ordner** für allgemeine Mitarbeiterschulungen\n") + f.write("5. **Öffentlich-Ordner** für Gäste-/Kunden-Präsentationen\n\n") + + logger.info(f"📝 Menschenlesbarer Bericht gespeichert: {report_path}") + +def main(): + """Hauptfunktion des Screenshot-Tools""" + print("=" * 60) + print("🎯 AUTOMATISCHES SCREENSHOT-TOOL FÜR SCHULUNGEN") + print("=" * 60) + + # Abhängigkeiten prüfen + if not SELENIUM_AVAILABLE: + print("❌ Selenium ist nicht installiert!") + print("💡 Führen Sie aus: pip install selenium") + return 1 + + # Konfiguration erstellen + config = ScreenshotConfiguration() + + # Benutzer-Eingaben für Konfiguration + print("\n📋 KONFIGURATION") + print("-" * 30) + + base_url = input(f"Server-URL [{config.BASE_URL}]: ").strip() + if base_url: + config.BASE_URL = base_url + + admin_email = input(f"Admin-Email [{config.ADMIN_EMAIL}]: ").strip() + if admin_email: + config.ADMIN_EMAIL = admin_email + + admin_password = input(f"Admin-Passwort [{config.ADMIN_PASSWORD}]: ").strip() + if admin_password: + config.ADMIN_PASSWORD = admin_password + + headless_input = input(f"Headless-Modus (ohne GUI) [{'Ja' if config.HEADLESS else 'Nein'}]: ").strip().lower() + if headless_input in ['nein', 'n', 'no', 'false']: + config.HEADLESS = False + elif headless_input in ['ja', 'j', 'yes', 'true']: + config.HEADLESS = True + + print(f"\n✅ Konfiguration abgeschlossen") + print(f"📂 Screenshots werden gespeichert in: {config.SCREENSHOT_BASE_DIR}") + + # Screenshot-Tool starten + tool = ScreenshotTool(config) + results = tool.run_screenshot_session() + + # Ergebnisse anzeigen + print("\n" + "=" * 60) + print("📊 ERGEBNISSE") + print("=" * 60) + print(f"✅ Erfolgreich: {len(results['success'])} Screenshots") + print(f"❌ Fehlgeschlagen: {len(results['failed'])} Screenshots") + print(f"⏭️ Übersprungen: {len(results['skipped'])} Routen") + print(f"📸 Gesamt: {results['total_screenshots']} Screenshots") + + if results["start_time"] and results["end_time"]: + duration = results["end_time"] - results["start_time"] + print(f"⏱️ Dauer: {duration}") + + print(f"\n📁 Alle Screenshots verfügbar in:") + print(f" {os.path.abspath(config.SCREENSHOT_BASE_DIR)}") + + return 0 if results["total_screenshots"] > 0 else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/backend/scripts/start_screenshot_tool.bat b/backend/scripts/start_screenshot_tool.bat new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/backend/scripts/start_screenshot_tool.bat @@ -0,0 +1 @@ + \ No newline at end of file