📝 Commit Details:

This commit is contained in:
Till Tomczak 2025-05-31 22:40:29 +02:00
parent 91b1886dde
commit df8fb197c0
14061 changed files with 997277 additions and 103548 deletions

2
backend/.npmrc Normal file
View File

@ -0,0 +1,2 @@
fund=false
audit-level=moderate

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,595 +0,0 @@
# Häufige Fehler und Lösungen
Dieses Dokument enthält häufig auftretende Fehler und deren Lösungen für das MYP-3D-Druck-Management-System.
## Drucker-Status-Check mit 7-Sekunden-Timeout
### Implementierung (2024-12-19)
**Problem:** Drucker-Status wurde nur basierend auf hardkodierten Konfigurationen bestimmt, ohne echte Netzwerk-Überprüfung.
**Lösung:** Implementierung eines echten Ping-basierten Status-Checks mit 7-Sekunden-Timeout:
#### Backend-Änderungen:
1. **Neue Funktionen in `app.py`:**
- `check_printer_status(ip_address, timeout=7)`: Überprüft einzelnen Drucker via Ping
- `check_multiple_printers_status(printers, timeout=7)`: Parallel-Check mehrerer Drucker
2. **Aktualisierte API-Endpunkte:**
- `/api/printers`: Verwendet jetzt echten Status-Check
- `/api/printers/status`: Spezieller Endpunkt für Status-Überprüfung
3. **Features:**
- 7-Sekunden-Timeout pro Drucker
- Parallel-Ausführung mit ThreadPoolExecutor
- Windows- und Unix-kompatible Ping-Befehle
- Detailliertes Logging der Status-Checks
- Automatische Datenbank-Aktualisierung
#### Frontend-Änderungen:
1. **Erweiterte Drucker-Seite (`templates/printers.html`):**
- Funktionsfähiger "Aktualisieren"-Button
- Loading-States mit Timeout-Information
- Status-Nachrichten mit Erfolgs-/Fehler-Feedback
- Zeitstempel der letzten Überprüfung
2. **Neue JavaScript-Funktionen:**
- `refreshPrinters()`: Vollständiger Status-Check mit UI-Feedback
- `loadPrintersWithStatusCheck()`: Erweiterte Lade-Funktion
- `showStatusMessage()`: Toast-Nachrichten für Benutzer-Feedback
- `formatTime()`: Relative Zeitanzeige für Status-Checks
#### Benutzer-Erfahrung:
- **Vor dem Update:** Drucker-Status basierte nur auf Konfiguration
- **Nach dem Update:**
- Echter Ping-Test mit 7-Sekunden-Timeout
- Visuelles Feedback während der Überprüfung
- Erfolgs-/Fehler-Nachrichten
- Zeitstempel der letzten Überprüfung
- Button-Deaktivierung während des Checks
#### Technische Details:
- **Timeout:** 7 Sekunden pro Drucker (konfigurierbar)
- **Parallel-Verarbeitung:** Bis zu 10 gleichzeitige Checks
- **Plattform-Unterstützung:** Windows (`ping -n 1 -w 7000`) und Unix (`ping -c 1 -W 7`)
- **Fehlerbehandlung:** Graceful Fallback auf "offline" bei Timeouts oder Fehlern
- **Logging:** Detaillierte Protokollierung aller Status-Checks
#### Konfiguration:
```python
# Timeout kann in den API-Aufrufen angepasst werden
status_results = check_multiple_printers_status(printer_data, timeout=7)
```
#### Fehlerbehebung:
1. **Timeout-Probleme:** Timeout kann in der Funktion angepasst werden
2. **Netzwerk-Probleme:** Logs in `logs/printers/printers.log` prüfen
3. **Performance:** Bei vielen Druckern ThreadPool-Größe anpassen
## Job-Scheduler und Steckdosensteuerung
### Steckdose kann nicht eingeschaltet werden
**Problem:**
Log-Eintrag: `Fehler beim Starten von Job X: Steckdose konnte nicht eingeschaltet werden`
**Mögliche Ursachen und Lösungen:**
1. **Netzwerkverbindung zu Steckdose unterbrochen**
- Prüfen Sie, ob die Steckdose mit dem Netzwerk verbunden ist
- Ping-Test: `ping [IP-Adresse der Steckdose]`
- Steckdose neu starten (physischer Reset-Knopf)
2. **Falsche Zugangsdaten für Steckdose**
- Überprüfen Sie in der Datenbank, ob die korrekten Zugangsdaten (Benutzername/Passwort) für die Steckdose hinterlegt sind
- Admin-Bereich → Drucker → Steckdosenkonfiguration bearbeiten
3. **PyP110-Bibliothek veraltet**
- Bibliothek aktualisieren: `pip install PyP100 --upgrade`
### Job wird nicht gestartet oder beendet
**Problem:**
Ein geplanter Job startet nicht oder ein laufender Job wird nicht beendet
**Mögliche Ursachen und Lösungen:**
1. **Scheduler-Thread läuft nicht**
- Prüfen Sie den Status des Schedulers im Admin-Bereich
- Wenn nicht aktiv: Scheduler starten
- Bei wiederholtem Problem: Server neu starten
2. **Datenbankprobleme**
- Überprüfen Sie die Datenbank auf Konsistenz
- Backup einspielen, falls notwendig
### Job-Verlängerung funktioniert nicht
**Problem:**
Nach dem Verlängern eines Jobs wird er trotzdem zum ursprünglichen Zeitpunkt beendet
**Lösung:**
1. Prüfen Sie die Datenbankeinträge, ob die `end_at`-Zeit korrekt aktualisiert wurde
2. Prüfen Sie die Log-Dateien auf Fehler beim Aktualisieren des Jobs
3. Verlängern Sie den Job erneut mit größerem Zeitpuffer
## Sicherheitshinweise
### Sicherheitsmaßnahmen bei Stromausfällen
**Wichtig:**
- Bei Stromausfällen kehren die Steckdosen in ihren Standardzustand zurück (meist AUS)
- Nach Wiederherstellung der Stromversorgung wird der Scheduler automatisch Jobs wieder aufnehmen
- Laufende Jobs werden jedoch **nicht** automatisch fortgesetzt, um Schäden zu vermeiden
- Administrator muss laufende Jobs manuell neu starten
### 5-Minuten Sicherheitspuffer
Das System verwendet einen 5-Minuten Sicherheitspuffer, bevor es einen Job beendet. Dies verhindert, dass ein Druck zu früh beendet wird. Die tatsächliche Ausschaltzeit ist also immer 5 Minuten nach der im System angezeigten Endzeit.
## API-Endpunkte für Fehlerbehebung
| Endpunkt | Beschreibung |
|----------|--------------|
| `GET /api/scheduler/status` | Status des Job-Schedulers abfragen |
| `POST /api/scheduler/start` | Scheduler manuell starten (Admin) |
| `POST /api/scheduler/stop` | Scheduler manuell stoppen (Admin) |
| `GET /api/jobs/active` | Alle aktiven Jobs anzeigen |
| `POST /api/jobs/<id>/extend` | Job-Laufzeit verlängern |
| `POST /api/jobs/<id>/finish` | Job manuell beenden (Admin) |
## Flask-Login Fehler
### AttributeError: 'User' object has no attribute 'is_authenticated'
**Problem:**
```
AttributeError: 'User' object has no attribute 'is_authenticated'
```
**Ursache:** Die User-Klasse erbt nicht von `UserMixin` oder implementiert nicht die erforderlichen Flask-Login Attribute.
**Lösungen:**
1. Stellen Sie sicher, dass die User-Klasse von `UserMixin` erbt:
```python
from flask_login import UserMixin
class User(UserMixin, Base):
# ... Rest der Klasse
```
2. Implementieren Sie die erforderlichen Methoden manuell:
```python
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return self.active
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
```
3. Führen Sie die Datenbankmigration aus:
```bash
python3.11 migrate_db.py
```
### Fehlende username-Spalte
**Problem:** Die Anwendung versucht auf `username` zuzugreifen, aber die Spalte existiert nicht in der Datenbank.
**Lösungen:**
1. Führen Sie das Migrationsskript aus:
```bash
python3.11 migrate_db.py
```
2. Falls die Migration fehlschlägt, initialisieren Sie die Datenbank neu:
```bash
python3.11 init_db.py
```
## Datenbank-Fehler
### SQLite Database is locked
**Problem:**
```
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked
```
**Ursache:** Dieser Fehler tritt auf, wenn mehrere Prozesse gleichzeitig auf die Datenbank zugreifen.
**Lösungen:**
1. Stellen Sie sicher, dass Sie alle Datenbankverbindungen mit `db_session.close()` schließen
2. Verwenden Sie `with get_db_session() as session:` um Sessions automatisch zu schließen
3. Prüfen Sie, ob der Scheduler und die Hauptanwendung gleichzeitig auf die Datenbank zugreifen
### Detached Instance Error
**Problem:**
```
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Job at 0x...> is not bound to a Session
```
**Ursache:** Zugriff auf ein SQLAlchemy-Objekt nach dem Schließen der Session.
**Lösungen:**
1. Verwenden Sie `joinedload` für eager loading: `db_session.query(Job).options(joinedload(Job.user))`
2. Konvertieren Sie Objekte zu Dictionaries, bevor Sie die Session schließen: `job_dict = job.to_dict()`
3. Verwenden Sie `session.expunge_all()` gefolgt von `session.close()`, wenn Sie Objekte weiter verwenden müssen
## Tapo Smart Plug Fehler
### Verbindungsfehler
**Problem:**
```
PyP100.PyP100.P100Exception: Could not connect to device
```
**Ursache:** Smart Plug ist nicht erreichbar oder Authentifizierungsprobleme.
**Lösungen:**
1. Überprüfen Sie die IP-Adresse des Smart Plugs
2. Stellen Sie sicher, dass der Smart Plug eingeschaltet und mit dem WLAN verbunden ist
3. Überprüfen Sie die Zugangsdaten in `config/settings.py`
4. Verwenden Sie einen Try-Except-Block zur Fehlerbehandlung:
```python
try:
p110 = PyP110.P110(ip, username, password)
p110.handshake()
p110.login()
p110.turnOn()
except Exception as e:
printers_logger.error(f"Smart Plug Fehler: {str(e)}")
```
## Flask-Anwendungsfehler
### Interner Server-Fehler (500)
**Problem:** Die Anwendung gibt einen 500-Fehler zurück.
**Ursache:** Verschiedene mögliche Ursachen, von Datenbank- bis hin zu Programmierfehlern.
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/app/app.log` und `logs/errors/errors.log`
2. Starten Sie die Anwendung im Debug-Modus: `FLASK_DEBUG=True python3.11 app.py`
3. Implementieren Sie bessere Fehlerbehandlung mit Try-Except-Blöcken
### Authentifizierungsfehler
**Problem:** Benutzer können sich nicht anmelden oder erhalten unbefugte Zugriffsfehler.
**Ursachen:**
- Ungültige Anmeldedaten
- Session-Probleme
- Cookie-Probleme
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/auth/auth.log`
2. Setzen Sie das Passwort zurück mit:
```python
from models import User, get_db_session
db_session = get_db_session()
user = db_session.query(User).filter(User.username == "admin").first()
user.set_password("neues_passwort")
db_session.commit()
db_session.close()
```
3. Löschen Sie Browser-Cookies und -Cache
## CSS und Frontend-Fehler
### Tailwind-Stile werden nicht angewendet
**Problem:** Die Tailwind-Klassen haben keine Wirkung auf das Styling.
**Lösungen:**
1. Stellen Sie sicher, dass die generierte CSS-Datei korrekt eingebunden ist:
```html
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind-dark-consolidated.min.css') }}">
```
2. Bauen Sie die CSS-Datei neu: `npm run build:css`
3. Überprüfen Sie in der Browser-Konsole, ob Fehler beim Laden der CSS-Datei auftreten
### Dark Mode funktioniert nicht
**Problem:** Die Dark-Mode-Stile werden nicht angewendet.
**Lösungen:**
1. Stellen Sie sicher, dass das HTML-Element die entsprechende Klasse hat:
```html
<html class="dark" data-theme="dark">
```
2. Überprüfen Sie, ob die Tailwind-Konfiguration Dark Mode aktiviert hat:
```js
// tailwind.config.js
module.exports = {
darkMode: 'class',
// ...
}
```
## Scheduler-Fehler
### Scheduler führt keine Jobs aus
**Problem:** Der Scheduler schaltet Drucker nicht ein/aus wie erwartet.
**Ursachen:**
- Scheduler ist nicht gestartet
- Fehler bei der Tapo-Verbindung
- Fehler in der Scheduler-Logik
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/scheduler/scheduler.log`
2. Stellen Sie sicher, dass `SCHEDULER_ENABLED = True` in `config/settings.py` gesetzt ist
3. Starten Sie die Anwendung neu
## Performance-Probleme
### Langsame Ladezeiten
**Problem:** Die Anwendung lädt langsam oder reagiert träge.
**Lösungen:**
1. Optimieren Sie Datenbankabfragen mit geeigneten Indizes
2. Reduzieren Sie die Anzahl der Datenbankabfragen durch Eager Loading
3. Implementieren Sie Caching für häufig abgerufene Daten
4. Überprüfen Sie die Logfiles auf übermäßige Logging-Aktivitäten
## Content Security Policy (CSP) Fehler
### Script-src-elem 'none' Fehler
**Problem:**
```
Refused to load the script because it violates the following Content Security Policy directive: "script-src-elem 'none'".
```
**Ursache:** Die Content Security Policy ist zu restriktiv eingestellt und blockiert das Laden von Skripten.
**Lösungen:**
1. Überprüfen Sie die CSP-Konfiguration in `config/security.py`:
```python
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; "
"script-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; "
# ... weitere Regeln
)
```
2. Stellen Sie sicher, dass die CSP in der `after_request`-Funktion richtig angewendet wird:
```python
@app.after_request
def add_security_headers(response):
from config.security import get_security_headers
security_headers = get_security_headers()
for header, value in security_headers.items():
response.headers[header] = value
# CSP-Report-Only Header entfernen
if 'Content-Security-Policy-Report-Only' in response.headers:
del response.headers['Content-Security-Policy-Report-Only']
return response
```
3. Vermeiden Sie mehrere konkurrierende Service Worker Registrierungen, die CSP-Konflikte verursachen können.
### Inline Script Fehler
**Problem:**
```
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src-elem 'none'"
```
**Lösungen:**
1. Fügen Sie 'unsafe-inline' zur script-src Direktive hinzu
2. Alternativ fügen Sie einen Hash oder Nonce für das Inline-Skript hinzu:
```html
<script nonce="{{ csp_nonce }}">
// Inline JavaScript
</script>
```
und in der CSP:
```
script-src 'nonce-{{ csp_nonce }}';
```
### Service Worker Registration Fehler
**Problem:**
```
Refused to create a worker from 'URL' because it violates the following Content Security Policy directive: "script-src 'none'"
```
**Lösungen:**
1. Stellen Sie sicher, dass die `worker-src` Direktive korrekt konfiguriert ist:
```
worker-src 'self' blob:;
```
2. Erstellen Sie die erforderlichen Service Worker Dateien im richtigen Verzeichnis
3. Verwenden Sie einen einheitlichen Ansatz zur Service Worker Registrierung, vorzugsweise im offline-app.js:
```javascript
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/static/js/sw.js')
.then(function(registration) {
console.log('Service Worker registriert:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker Registration fehlgeschlagen:', error);
// Fallback
});
});
}
```
### Icon 404 Fehler
**Problem:**
```
Failed to load resource: the server responded with a status of 404 (NOT FOUND)
Error while trying to use the following icon from the Manifest: URL (Download error or resource isn't a valid image)
```
**Lösungen:**
1. Erstellen Sie die fehlenden Icon-Dateien im Verzeichnis `static/icons/`
2. Aktualisieren Sie das Web App Manifest, um nur verfügbare Icons zu referenzieren:
```json
"icons": [
{
"src": "/static/icons/mercedes-logo.svg",
"sizes": "192x192",
"type": "image/svg+xml"
}
]
```
3. Verwenden Sie Tools wie Lighthouse, um fehlende PWA-Ressourcen zu identifizieren
## JavaScript-Referenzfehler
### Problem: Nicht definierte Funktionen in admin-panel.js
Fehlermeldungen wie "Uncaught ReferenceError: loadPrinters is not defined" können auftreten, wenn Funktionen in der JavaScript-Datei verwendet, aber nicht definiert werden.
**Lösung:**
- Implementiere die fehlenden Funktionen (loadPrinters, loadSchedulerStatus, loadSystemStats, loadLogs, loadUsers, etc.)
- Stelle sicher, dass alle aufgerufenen Funktionen im richtigen Scope definiert sind
- Überprüfe die Reihenfolge der Funktionsdefinitionen und -aufrufe
### Problem: Funktionen in anderen Dateien werden nicht gefunden
Fehler wie "Uncaught ReferenceError: refreshJobs is not defined" in jobs.html oder "exportStats is not defined" in stats.html.
**Lösung:**
- Stelle sicher, dass die Funktionen in der jeweiligen HTML-Datei oder in einer eingebundenen JavaScript-Datei definiert sind
- Überprüfe, ob die JavaScript-Dateien korrekt in der HTML-Datei eingebunden sind
- Nutze konsistente Namenskonventionen für Funktionen
## Service Worker Fehler
### Problem: Cache-API kann mit chrome-extension URLs nicht umgehen
Fehler: "Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported"
**Lösung:**
- Überprüfe die URL-Protokolle bevor du Cache-Operationen durchführst
- Füge eine spezielle Behandlung für chrome-extension-Protokolle hinzu
- Verwende try-catch-Blöcke, um Fehler bei der Cache-Handhabung abzufangen
```javascript
if (url.protocol === 'chrome-extension:') {
// Spezielle Behandlung für chrome-extension URLs
try {
return await fetch(request);
} catch (error) {
console.error('Failed to fetch from chrome-extension:', error);
return new Response(JSON.stringify({
error: 'Fehler beim Zugriff auf chrome-extension',
offline: true
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
}
```
## API-Endpunkt Fehler
### Problem: API-Ressourcen werden nicht gefunden (404)
Fehlermeldungen wie "Failed to load resource: the server responded with a status of 404 (NOT FOUND)"
**Lösung:**
- Überprüfe die API-Routen im Backend
- Stelle sicher, dass die API-Endpunkte korrekt implementiert sind
- Überprüfe die Anfrage-URLs in den Frontend-Skripten
- Füge Fehlerbehandlung für fehlende Ressourcen hinzu
## Frontend-Rendering Fehler
### Problem: Fehler beim Laden der Admin-Statistiken
"Error loading admin: admin-panel.js:484 Fehler beim Laden der Admin-Statistiken"
**Lösung:**
- Implementiere ordnungsgemäße Fehlerbehandlung in API-Anfragen
- Zeige benutzerfreundliche Fehlermeldungen an
- Füge Fallback-Werte für fehlende Daten hinzu
```javascript
try {
const response = await fetch('/api/admin/stats');
if (!response.ok) {
throw new Error('Fehler beim Laden der Admin-Statistiken');
}
const data = await response.json();
// Daten verarbeiten
} catch (error) {
console.error('Error loading admin stats:', error);
showNotification('Fehler beim Laden der Admin-Statistiken', 'error');
}
```
## Icon/Ressourcen Fehler
### Problem: Icon-Dateien werden nicht gefunden
"Error while trying to use the following icon from the Manifest: http://127.0.0.1:5000/static/icons/icon-144x144.png"
**Lösung:**
- Überprüfe, ob die Icon-Dateien im richtigen Verzeichnis vorhanden sind
- Stelle sicher, dass die Pfade in der Manifest-Datei korrekt sind
- Füge Fallback-Icons für den Fall hinzu, dass die primären Icons nicht geladen werden können
## Allgemeine Fehlerbehebung
1. **Browser-Konsole prüfen**: Die meisten JavaScript-Fehler werden in der Browser-Konsole angezeigt (F12 oder Rechtsklick -> Untersuchen -> Konsole)
2. **Netzwerk-Tab prüfen**: Im Netzwerk-Tab des Browsers können fehlgeschlagene API-Anfragen identifiziert werden
3. **Codestruktur überprüfen**: Stelle sicher, dass alle Funktionen in der richtigen Reihenfolge definiert werden
4. **Fehlerbehandlung verbessern**: Implementiere try-catch-Blöcke für alle asynchronen Funktionen
5. **Browser-Cache leeren**: Manchmal können Probleme durch veraltete gecachte Dateien verursacht werden
## 404 Fehler (Seite nicht gefunden)
### Fehlende Routen oder API-Endpunkte
**Problem:**
Die Anwendung zeigt 404-Fehler für bestimmte Routen wie `/privacy`, `/terms`, `/my/jobs`, `/api/user/export` oder `/api/user/profile`.
**Ursachen:**
- Die Route wurde nicht korrekt in `app.py` oder dem entsprechenden Blueprint registriert
- Bei API-Endpunkten: Die alte API-Route wurde verwendet, aber die Anwendung nutzt jetzt eine neue Route
- Möglicherweise wurden die Templates erstellt, aber die Routen dafür fehlen
**Lösungen:**
1. **Für öffentliche Seiten** (`/privacy`, `/terms`):
- Überprüfen Sie, ob die Routen in `app.py` definiert sind
- Stellen Sie sicher, dass der `@login_required` Decorator entfernt wurde, falls diese Seiten öffentlich sein sollen
- Prüfen Sie, ob die entsprechenden Template-Dateien unter `templates/` existieren
2. **Für Benutzer-spezifische Seiten** (`/my/jobs`):
- Fügen Sie eine Route hinzu, die zur Jobs-Seite mit vorgefiltertem Benutzer-Parameter weiterleitet
3. **Für API-Weiterleitungen** (`/api/user/export`, `/api/user/profile`):
- Erstellen Sie Weiterleitungsrouten, die zu den neuen Blueprint-Routen führen
- Beispiel: `/api/user/export` sollte zu `/user/export` weiterleiten
4. **Debugging von Routen:**
- Mit `flask routes` können Sie alle registrierten Routen auflisten
- Überprüfen Sie die Log-Dateien auf fehlgeschlagene Routenzugriffe
- Im Debug-Modus starten: `FLASK_DEBUG=True python3.11 app.py`
5. **Blueprint-Registrierung prüfen:**
- Stellen Sie sicher, dass alle Blueprints korrekt in `app.py` registriert sind:
```python
from blueprints.user import user_bp
app.register_blueprint(user_bp)
```
---
Dieses Dokument wird kontinuierlich aktualisiert, wenn neue Fehler auftreten und Lösungen gefunden werden.

View File

@ -1,173 +0,0 @@
# MYP Platform - Entwicklungs-Roadmap
Dieses Dokument beschreibt die geplanten Entwicklungsschritte und zukünftigen Features für das MYP 3D-Drucker Reservierungssystem.
## Aktuelle Version: 1.1
Die aktuelle Version umfasst die Grundfunktionalitäten:
- Benutzerauthentifizierung und -verwaltung
- Druckerverwaltung
- Job-Scheduling und -Überwachung
- Smart Plug Integration
- **✅ Vollständiges UI-Komponenten-System mit Tailwind CSS**
- **✅ Template-Helper für einfache UI-Entwicklung**
- **✅ JavaScript-Utilities für interaktive Komponenten**
- **✅ Dark Mode Support**
- **✅ Responsive Design**
- **✅ Umfassende UI-Dokumentation**
## Kürzlich Abgeschlossen (Version 1.2) ✅
### Sicherheits-Features ✅
- ✅ **Rate Limiting**: Schutz vor API-Missbrauch und DDoS-Attacken
- ✅ **Content Security Policy (CSP)**: Schutz vor XSS-Angriffen
- ✅ **Erweiterte Security Headers**: Comprehensive security headers für alle Responses
- ✅ **Verdächtige Aktivitäts-Erkennung**: Automatische Erkennung von SQL-Injection und anderen Bedrohungen
- ✅ **Client-Fingerprinting**: Erweiterte Sicherheit durch Client-Identifikation
### Erweiterte Berechtigungen ✅
- ✅ **Granulare Berechtigungen**: 7 detaillierte Rollen (Guest bis Super Admin)
- ✅ **Ressourcen-spezifische Zugriffskontrolle**: Job-, Drucker- und Benutzer-spezifische Berechtigungen
- ✅ **Temporäre Berechtigungen**: Zeitlich begrenzte Berechtigungsüberschreibungen
- ✅ **Permission Caching**: Performance-optimierte Berechtigungsprüfung
- ✅ **Template-Integration**: Template-Helper für berechtigungsbasierte UI-Anzeige
### Erweiterte UI-Komponenten ✅
- ✅ **Progress-Bars**: Animierte, konfigurabr progress indicators mit verschiedenen Styles
- ✅ **Advanced File-Upload**: Drag & Drop, Preview, Chunk-Upload, Validierung
- ✅ **DatePicker**: Deutscher Kalender mit Validierung und Custom Events
- ✅ **Auto-Initialisierung**: Data-Attribute-basierte Komponenten-Initialisierung
### Analytics & Statistiken ✅
- ✅ **Umfassende Analytics-Engine**: Drucker-, Job- und Benutzer-Statistiken
- ✅ **KPI-Dashboard**: Key Performance Indicators mit Trend-Analyse
- ✅ **Report-Generierung**: Verschiedene Report-Typen und Zeiträume
- ✅ **Interaktive Charts**: Chart.js-basierte Visualisierungen
- ✅ **Export-Funktionalität**: JSON, CSV, PDF, Excel-Export (Framework bereit)
## Geplante Features
### Version 1.3 (Kurzfristig)
- [ ] **E-Mail-Benachrichtigungen**: Bei Job-Status-Änderungen und System-Events
- [ ] **Erweiterte Formular-Validierung**: Client- und serverseitige Validierung mit UI-Feedback
- [ ] **Multi-Format-Export**: Vollständige PDF- und Excel-Report-Generierung
- [ ] **Zwei-Faktor-Authentifizierung**: TOTP-basierte 2FA-Implementierung
### Version 1.3 (Mittelfristig)
- [ ] Druckerprofile mit spezifischen Eigenschaften (Druckvolumen, Materialien, etc.)
- [ ] Materialverwaltung und -tracking
- [ ] Verbessertes Dashboard mit Echtzeit-Updates
- [ ] **HTMX-Integration**: Für bessere Interaktivität ohne JavaScript-Framework
- [ ] **Drag & Drop**: Für Job-Reihenfolge und Datei-Uploads
- [ ] **Erweiterte Tabellen**: Sortierung, Filterung, Pagination
### Version 2.0 (Langfristig)
- [ ] OctoPrint Integration für direkte Druckersteuerung
- [ ] Mobile App mit Push-Benachrichtigungen
- [ ] Wartungsplanung und -tracking
- [ ] Multi-Standort-Unterstützung
- [ ] **Progressive Web App (PWA)**: Offline-Funktionalität und App-Installation
- [ ] **Erweiterte Themes**: Anpassbare Farbschemata und Layouts
## Technische Verbesserungen
### Backend
- [ ] Refactoring für verbesserte Modularität
- [ ] REST API Dokumentation mit Swagger/OpenAPI
- [ ] Verbesserte Testabdeckung
- [ ] Migration zu SQLAlchemy 2.0
- [ ] **WebSocket-Integration**: Für Echtzeit-Updates
### Frontend
- [X] ~~Optimierung der Benutzeroberfläche~~ ✅ **Abgeschlossen**
- [X] ~~UI-Komponenten-System~~ ✅ **Abgeschlossen**
- [ ] **HTMX-Integration**: Für bessere Interaktivität ohne komplexe JavaScript-Frameworks
- [ ] **Progressive Web App (PWA)**: Funktionalität für App-ähnliche Erfahrung
- [ ] **Barrierefreiheit**: Nach WCAG-Richtlinien
- [ ] **Performance-Optimierung**: Lazy Loading, Code Splitting
### CSS/Styling
- [X] ~~Tailwind CSS Integration~~ ✅ **Abgeschlossen**
- [X] ~~PostCSS Build-Pipeline~~ ✅ **Abgeschlossen**
- [X] ~~Dark Mode Support~~ ✅ **Abgeschlossen**
- [X] ~~Responsive Design~~ ✅ **Abgeschlossen**
- [ ] **CSS-Optimierung**: Purging ungenutzter Styles, Critical CSS
- [ ] **Animation-System**: Micro-Interactions und Übergänge
## Leistung und Skalierung
- [ ] Optimierung der Datenbankabfragen
- [ ] Caching-Strategie implementieren
- [ ] Asynchrone Verarbeitung für zeitintensive Aufgaben
- [ ] Docker-Container für einfache Bereitstellung
- [ ] **CDN-Integration**: Für statische Assets
- [ ] **Service Worker**: Für Offline-Funktionalität
## Sicherheit
- [ ] Security Audit durchführen
- [ ] Implementierung von CSRF-Schutz
- [ ] Rate Limiting für API-Endpunkte
- [ ] Zwei-Faktor-Authentifizierung
- [ ] **Content Security Policy (CSP)**: Schutz vor XSS-Angriffen
## Entwickler-Erfahrung
### Dokumentation ✅
- [X] ~~UI-Komponenten-Dokumentation~~ ✅ **Abgeschlossen**
- [X] ~~Tailwind CSS Setup-Guide~~ ✅ **Abgeschlossen**
- [ ] API-Dokumentation mit Swagger
- [ ] Entwickler-Handbuch
- [ ] Deployment-Guide
### Tooling
- [X] ~~PostCSS Build-System~~ ✅ **Abgeschlossen**
- [X] ~~NPM Scripts für Development~~ ✅ **Abgeschlossen**
- [ ] **Hot Reload**: Für CSS und Templates
- [ ] **Linting**: ESLint, Prettier, Flake8
- [ ] **Testing**: Unit Tests, Integration Tests, E2E Tests
## Community und Beiträge
Wir freuen uns über Beiträge und Feedback zu dieser Roadmap. Wenn Sie Vorschläge haben oder an der Entwicklung teilnehmen möchten, erstellen Sie bitte einen Issue oder Pull Request im Repository.
### Aktuelle Prioritäten für Beiträge
1. **Testing**: Unit Tests für UI-Komponenten
2. **Accessibility**: WCAG-konforme Verbesserungen
3. **Performance**: Optimierung der CSS-Größe
4. **Dokumentation**: Übersetzungen und Beispiele
## Changelog
### Version 1.1 (Dezember 2024)
- ✅ Vollständiges UI-Komponenten-System implementiert
- ✅ Template-Helper für alle gängigen UI-Elemente
- ✅ JavaScript-Utilities für interaktive Funktionen
- ✅ PostCSS Build-Pipeline mit Tailwind CSS
- ✅ Umfassende Dokumentation erstellt
- ✅ Demo-Seite für alle Komponenten
### Version 1.0 (Juni 2023)
- ✅ Grundfunktionalitäten implementiert
- ✅ Basis-UI mit Tailwind CSS
- ✅ Dark Mode Support
---
*Zuletzt aktualisiert: Dezember 2024*

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
# MYP Platform - Python Dependencies
# Basierend auf tatsächlich verwendeten Imports in app.py
# Automatisch generiert am: 2025-05-29 19:41:49
# Installiere mit: pip install -r requirements.txt
# ===== CORE FLASK FRAMEWORK =====
# Direkt in app.py verwendet
Flask==3.1.1
Flask-Login==0.6.3
Flask-WTF==1.2.1
# ===== DATENBANK =====
# SQLAlchemy für Datenbankoperationen (models.py, app.py)
SQLAlchemy==2.0.36
# ===== SICHERHEIT UND AUTHENTIFIZIERUNG =====
# Werkzeug für Passwort-Hashing und Utilities (app.py)
bcrypt==4.2.1
cryptography==44.0.0
Werkzeug==3.1.3
# ===== SMART PLUG STEUERUNG =====
# PyP100 für TP-Link Tapo Smart Plugs (utils/job_scheduler.py)
PyP100
# ===== RATE LIMITING UND CACHING =====
# Redis für Rate Limiting (utils/rate_limiter.py) - optional
redis==5.2.1
# ===== HTTP REQUESTS =====
# Requests für HTTP-Anfragen (utils/queue_manager.py, utils/debug_drucker_erkennung.py)
requests==2.32.3
# ===== TEMPLATE ENGINE =====
# Jinja2 und MarkupSafe (automatisch mit Flask installiert, aber explizit für utils/template_helpers.py)
MarkupSafe==3.0.2
# ===== SYSTEM MONITORING =====
# psutil für System-Monitoring (utils/debug_utils.py, utils/debug_cli.py)
psutil==6.1.1
# ===== ZUSÄTZLICHE CORE ABHÄNGIGKEITEN =====
# Click für CLI-Kommandos (automatisch mit Flask)
# Keine Core-Requirements
# ===== WINDOWS-SPEZIFISCHE ABHÄNGIGKEITEN =====
# Nur für Windows-Systeme erforderlich
# Keine Windows-Requirements
# ===== OPTIONAL: ENTWICKLUNG UND TESTING =====
# Nur für Entwicklungsumgebung
pytest==8.3.4; extra == "dev"
pytest-cov==6.0.0; extra == "dev"
# ===== OPTIONAL: PRODUKTIONS-SERVER =====
# Gunicorn für Produktionsumgebung
gunicorn==23.0.0; extra == "prod"

View File

@ -1,994 +0,0 @@
#!/bin/bash
# ===================================================================
# MYP Druckerverwaltung - Raspberry Pi Schnellstart Optimierung
# Optimiert automatischen Start ohne Benutzeranmeldung
# Für bereits installierte Systeme
# ===================================================================
set -e
# =========================== KONFIGURATION ===========================
KIOSK_USER="kiosk"
APP_USER="myp"
APP_DIR="/opt/myp-druckerverwaltung"
INSTALL_LOG="/var/log/myp-schnellstart.log"
# Farben für Ausgabe
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# ========================== LOGGING-SYSTEM ==========================
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$INSTALL_LOG"
}
error() {
echo -e "${RED}[FEHLER] $1${NC}" | tee -a "$INSTALL_LOG"
exit 1
}
warning() {
echo -e "${YELLOW}[WARNUNG] $1${NC}" | tee -a "$INSTALL_LOG"
}
info() {
echo -e "${BLUE}[INFO] $1${NC}" | tee -a "$INSTALL_LOG"
}
progress() {
echo -e "${PURPLE}[FORTSCHRITT] $1${NC}" | tee -a "$INSTALL_LOG"
}
# ========================== SYSTEM-CHECKS ==========================
check_root() {
if [ "$EUID" -ne 0 ]; then
error "Dieses Skript muss als Root ausgeführt werden: sudo $0"
fi
}
check_system() {
log "=== RASPBERRY PI SCHNELLSTART-OPTIMIERUNG ==="
# Prüfe ob Kiosk-Benutzer existiert
if ! id "$KIOSK_USER" &>/dev/null; then
# Kiosk-Benutzer erstellen falls nicht vorhanden
progress "Erstelle Kiosk-Benutzer: $KIOSK_USER"
if ! useradd -m -s /bin/bash "$KIOSK_USER" 2>/dev/null; then
adduser --disabled-password --gecos "" "$KIOSK_USER" || error "Kann Kiosk-Benutzer nicht erstellen"
fi
# Kiosk-Benutzer zu Audio/Video-Gruppen hinzufügen
usermod -aG audio,video,input "$KIOSK_USER" 2>/dev/null || true
info "Kiosk-Benutzer erstellt"
else
info "Kiosk-Benutzer existiert bereits"
fi
# Prüfe ob App-Verzeichnis existiert
if [ ! -d "$APP_DIR" ]; then
# App-Verzeichnis erstellen falls nicht vorhanden
progress "Erstelle App-Verzeichnis: $APP_DIR"
mkdir -p "$APP_DIR"
chown -R "$APP_USER:$APP_USER" "$APP_DIR" 2>/dev/null || true
info "App-Verzeichnis erstellt"
else
info "App-Verzeichnis existiert bereits"
fi
# Prüfe ob App-Benutzer existiert
if ! id "$APP_USER" &>/dev/null; then
# App-Benutzer erstellen falls nicht vorhanden
progress "Erstelle App-Benutzer: $APP_USER"
if ! useradd -m -s /bin/bash "$APP_USER" 2>/dev/null; then
adduser --disabled-password --gecos "" "$APP_USER" || error "Kann App-Benutzer nicht erstellen"
fi
usermod -aG sudo "$APP_USER" 2>/dev/null || true
info "App-Benutzer erstellt"
else
info "App-Benutzer existiert bereits"
fi
info "System-Checks erfolgreich"
}
# ========================== ESSENZIELLE PAKETE INSTALLIEREN ==========================
install_essential_packages() {
log "=== ESSENZIELLE PAKETE INSTALLIEREN ==="
progress "Aktualisiere Paketlisten..."
apt-get update -y || warning "APT Update teilweise fehlgeschlagen"
progress "Installiere essenzielle Pakete..."
apt-get install -y \
curl wget git unzip \
python3 python3-pip python3-dev \
build-essential libssl-dev libffi-dev \
sqlite3 nginx supervisor \
xorg xinit openbox \
xserver-xorg-video-all \
x11-xserver-utils xdotool unclutter \
lightdm lightdm-gtk-greeter \
pulseaudio alsa-utils \
fonts-liberation fonts-dejavu \
ca-certificates apt-transport-https \
|| warning "Paket-Installation teilweise fehlgeschlagen"
log "✅ Essenzielle Pakete installiert"
}
# ========================== BOOT-OPTIMIERUNG ==========================
optimize_boot() {
log "=== BOOT-OPTIMIERUNG ==="
progress "Optimiere Raspberry Pi Boot-Parameter..."
# Raspberry Pi Boot-Konfiguration
if [ -f "/boot/config.txt" ]; then
# GPU Memory für bessere Performance
if ! grep -q "gpu_mem=" /boot/config.txt; then
echo "gpu_mem=128" >> /boot/config.txt
info "GPU Memory auf 128MB gesetzt"
fi
# Disable Boot-Splash für schnelleren Start
if ! grep -q "disable_splash=" /boot/config.txt; then
echo "disable_splash=1" >> /boot/config.txt
info "Boot-Splash deaktiviert"
fi
# Boot-Delay reduzieren
if ! grep -q "boot_delay=" /boot/config.txt; then
echo "boot_delay=0" >> /boot/config.txt
info "Boot-Delay auf 0 gesetzt"
fi
# HDMI-Hotplug für bessere Display-Erkennung
if ! grep -q "hdmi_force_hotplug=" /boot/config.txt; then
echo "hdmi_force_hotplug=1" >> /boot/config.txt
info "HDMI-Hotplug aktiviert"
fi
# Disable Rainbow-Splash
if ! grep -q "disable_overscan=" /boot/config.txt; then
echo "disable_overscan=1" >> /boot/config.txt
info "Overscan deaktiviert"
fi
fi
# Kernel-Parameter optimieren
if [ -f "/boot/cmdline.txt" ]; then
# Backup erstellen
cp /boot/cmdline.txt /boot/cmdline.txt.backup
# Entferne Boot-Splash und optimiere
sed -i 's/splash//g' /boot/cmdline.txt
# Füge Performance-Parameter hinzu
if ! grep -q "quiet" /boot/cmdline.txt; then
sed -i 's/$/ quiet/' /boot/cmdline.txt
fi
if ! grep -q "loglevel=3" /boot/cmdline.txt; then
sed -i 's/$/ loglevel=3/' /boot/cmdline.txt
fi
if ! grep -q "logo.nologo" /boot/cmdline.txt; then
sed -i 's/$/ logo.nologo/' /boot/cmdline.txt
fi
if ! grep -q "vt.global_cursor_default=0" /boot/cmdline.txt; then
sed -i 's/$/ vt.global_cursor_default=0/' /boot/cmdline.txt
fi
info "Kernel-Parameter optimiert"
fi
log "✅ Boot-Optimierung abgeschlossen"
}
# ========================== AUTOLOGIN VERSTÄRKEN ==========================
strengthen_autologin() {
log "=== AUTOLOGIN-VERSTÄRKUNG ==="
progress "Verstärke automatischen Login..."
# Sicherstellen dass graphical.target Standard ist
systemctl set-default graphical.target
info "Graphical.target als Standard gesetzt"
# Getty Auto-Login verstärken
mkdir -p "/etc/systemd/system/getty@tty1.service.d"
cat > "/etc/systemd/system/getty@tty1.service.d/autologin.conf" << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin $KIOSK_USER --noclear %I \$TERM
Type=simple
Restart=always
RestartSec=3
EOF
# Getty Service aktivieren
systemctl enable getty@tty1.service
info "Getty Auto-Login konfiguriert"
# LightDM Auto-Login verstärken
if [ -f "/etc/lightdm/lightdm.conf" ]; then
# Backup erstellen
cp /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.backup
# Neue Konfiguration
cat > "/etc/lightdm/lightdm.conf" << EOF
[Seat:*]
# Automatischer Login für Kiosk-Benutzer
autologin-user=$KIOSK_USER
autologin-user-timeout=0
autologin-session=openbox
user-session=openbox
session-wrapper=/etc/X11/Xsession
greeter-session=lightdm-gtk-greeter
allow-guest=false
# Kein Benutzer-Wechsel möglich
greeter-hide-users=true
greeter-show-manual-login=false
# Automatischer Start ohne Verzögerung
autologin-in-background=false
# Session-Setup
session-setup-script=/usr/share/lightdm/setup-kiosk-session.sh
[SeatDefaults]
# Zusätzliche Sicherheitseinstellungen
autologin-user=$KIOSK_USER
autologin-user-timeout=0
autologin-session=openbox
greeter-hide-users=true
greeter-show-manual-login=false
allow-user-switching=false
EOF
info "LightDM Auto-Login verstärkt"
fi
# LightDM Service-Override
mkdir -p "/etc/systemd/system/lightdm.service.d"
cat > "/etc/systemd/system/lightdm.service.d/autologin-override.conf" << EOF
[Unit]
After=multi-user.target network.target myp-druckerverwaltung.service
Wants=myp-druckerverwaltung.service
[Service]
# Automatischer Restart bei Fehlern
Restart=always
RestartSec=3
# Umgebungsvariablen für Kiosk
Environment=DISPLAY=:0
Environment=KIOSK_MODE=1
# Verzögerung für Backend-Start
ExecStartPre=/bin/bash -c 'for i in {1..30}; do if curl -s http://localhost:5000 >/dev/null 2>&1; then break; fi; sleep 2; done'
EOF
systemctl enable lightdm.service
info "LightDM Service-Override konfiguriert"
log "✅ Autologin-Verstärkung abgeschlossen"
}
# ========================== KIOSK-BENUTZER OPTIMIERUNG ==========================
optimize_kiosk_user() {
log "=== KIOSK-BENUTZER OPTIMIERUNG ==="
KIOSK_HOME="/home/$KIOSK_USER"
progress "Optimiere Kiosk-Benutzer Autostart..."
# Verstärkte .bashrc
cat >> "$KIOSK_HOME/.bashrc" << 'EOF'
# ===== VERSTÄRKTER KIOSK AUTOSTART =====
if [ -z "$SSH_CLIENT" ] && [ -z "$SSH_TTY" ] && [ -z "$KIOSK_STARTED" ]; then
export KIOSK_STARTED=1
# Logge Autostart-Versuch
echo "$(date): Bashrc Autostart-Versuch auf $(tty)" >> /var/log/kiosk-autostart.log
# Prüfe ob wir auf tty1 sind und X noch nicht läuft
if [ "$(tty)" = "/dev/tty1" ] && [ -z "$DISPLAY" ]; then
echo "$(date): Starte X-Session automatisch via bashrc" >> /var/log/kiosk-autostart.log
exec startx
fi
# Falls X läuft aber Kiosk-App nicht, starte sie
if [ -n "$DISPLAY" ] && ! pgrep -f "chromium.*kiosk" > /dev/null; then
echo "$(date): Starte Kiosk-Anwendung via bashrc" >> /var/log/kiosk-autostart.log
exec $HOME/start-kiosk.sh
fi
fi
EOF
# Verstärkte .profile
cat >> "$KIOSK_HOME/.profile" << 'EOF'
# ===== VERSTÄRKTER KIOSK AUTOSTART (PROFILE) =====
if [ -z "$SSH_CLIENT" ] && [ -z "$SSH_TTY" ] && [ -z "$KIOSK_STARTED" ]; then
export KIOSK_STARTED=1
# Logge Profile-Autostart
echo "$(date): Profile Autostart-Versuch auf $(tty)" >> /var/log/kiosk-autostart.log
# Starte X-Session falls nicht vorhanden
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
echo "$(date): Starte X-Session via profile" >> /var/log/kiosk-autostart.log
exec startx
fi
fi
EOF
# Optimierte .xinitrc
cat > "$KIOSK_HOME/.xinitrc" << EOF
#!/bin/bash
# Optimierte Xinit-Konfiguration für Kiosk-Modus
# Logge X-Start
echo "\$(date): X-Session gestartet via xinitrc" >> /var/log/kiosk-autostart.log
# Export Display
export DISPLAY=:0
# Session-Setup
xset s off
xset s noblank
xset s noexpose
xset -dpms
# Verstecke Mauszeiger
unclutter -idle 0.5 -root &
# Warte kurz auf System-Stabilisierung
sleep 3
# Starte Openbox
exec openbox-session
EOF
chmod +x "$KIOSK_HOME/.xinitrc"
# Desktop Autostart verstärken
mkdir -p "$KIOSK_HOME/.config/autostart"
cat > "$KIOSK_HOME/.config/autostart/kiosk-app.desktop" << EOF
[Desktop Entry]
Type=Application
Name=MYP Kiosk Application
Comment=Startet die MYP Kiosk-Anwendung automatisch
Exec=$KIOSK_HOME/start-kiosk.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
StartupNotify=false
Terminal=false
EOF
# Berechtigungen setzen
chown -R "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/.config"
chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/.bashrc"
chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/.profile"
chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/.xinitrc"
info "Kiosk-Benutzer Autostart optimiert"
log "✅ Kiosk-Benutzer Optimierung abgeschlossen"
}
# ========================== WATCHDOG VERSTÄRKEN ==========================
strengthen_watchdog() {
log "=== WATCHDOG-VERSTÄRKUNG ==="
progress "Verstärke Kiosk-Überwachung..."
# Verstärkter Kiosk-Watchdog Service
cat > "/etc/systemd/system/kiosk-watchdog-enhanced.service" << EOF
[Unit]
Description=Enhanced Kiosk Watchdog Service
After=multi-user.target lightdm.service
Wants=lightdm.service myp-druckerverwaltung.service
[Service]
Type=simple
User=root
ExecStart=/bin/bash -c '
while true; do
# Prüfe Backend-Service
if ! systemctl is-active --quiet myp-druckerverwaltung; then
echo "\$(date): Backend-Service nicht aktiv - starte neu" >> /var/log/kiosk-watchdog-enhanced.log
systemctl start myp-druckerverwaltung
sleep 5
fi
# Prüfe Backend-Erreichbarkeit
if ! curl -s http://localhost:5000 >/dev/null 2>&1; then
echo "\$(date): Backend nicht erreichbar - starte Service neu" >> /var/log/kiosk-watchdog-enhanced.log
systemctl restart myp-druckerverwaltung
sleep 10
fi
# Prüfe LightDM
if ! systemctl is-active --quiet lightdm; then
echo "\$(date): LightDM nicht aktiv - starte neu" >> /var/log/kiosk-watchdog-enhanced.log
systemctl start lightdm
sleep 5
fi
# Prüfe Kiosk-Benutzer Session
if ! pgrep -u $KIOSK_USER > /dev/null; then
echo "\$(date): Kiosk-Benutzer nicht angemeldet - starte LightDM neu" >> /var/log/kiosk-watchdog-enhanced.log
systemctl restart lightdm
sleep 10
fi
# Prüfe Chromium Kiosk-Prozess
if ! pgrep -u $KIOSK_USER -f "chromium.*kiosk" > /dev/null; then
echo "\$(date): Chromium-Kiosk nicht gefunden - starte Kiosk-Session neu" >> /var/log/kiosk-watchdog-enhanced.log
# Versuche Kiosk-Neustart als Kiosk-Benutzer
sudo -u $KIOSK_USER DISPLAY=:0 /home/$KIOSK_USER/start-kiosk.sh &
sleep 5
fi
# Prüfe X-Server
if ! pgrep -f "X.*:0" > /dev/null; then
echo "\$(date): X-Server nicht gefunden - starte LightDM neu" >> /var/log/kiosk-watchdog-enhanced.log
systemctl restart lightdm
sleep 10
fi
sleep 20
done
'
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# Alten Watchdog deaktivieren und neuen aktivieren
systemctl disable kiosk-watchdog.service 2>/dev/null || true
systemctl enable kiosk-watchdog-enhanced.service
# Cron-Watchdog verstärken
cat > "/etc/cron.d/kiosk-watchdog-enhanced" << EOF
# Verstärkter Kiosk-Watchdog: Prüft alle 2 Minuten
*/2 * * * * $KIOSK_USER /bin/bash -c 'if ! pgrep -f "chromium.*kiosk" > /dev/null; then echo "\$(date): Cron-Watchdog startet Kiosk neu" >> /var/log/kiosk-cron-watchdog.log; DISPLAY=:0 $HOME/start-kiosk.sh & fi'
# System-Watchdog: Prüft Services alle 5 Minuten
*/5 * * * * root /bin/bash -c 'if ! systemctl is-active --quiet lightdm; then echo "\$(date): Cron startet LightDM neu" >> /var/log/system-cron-watchdog.log; systemctl start lightdm; fi'
EOF
# RC.Local verstärken
cat > "/etc/rc.local" << EOF
#!/bin/bash
# Verstärkter rc.local - Kiosk-Fallback
# Logge Start
echo "\$(date): rc.local gestartet" >> /var/log/kiosk-fallback.log
# Warte auf System-Initialisierung
sleep 20
# Starte Backend-Service falls nicht läuft
if ! systemctl is-active --quiet myp-druckerverwaltung; then
echo "\$(date): Starte Backend-Service" >> /var/log/kiosk-fallback.log
systemctl start myp-druckerverwaltung
sleep 10
fi
# Warte auf Backend-Verfügbarkeit
for i in {1..30}; do
if curl -s http://localhost:5000 >/dev/null 2>&1; then
echo "\$(date): Backend verfügbar nach \$i Versuchen" >> /var/log/kiosk-fallback.log
break
fi
sleep 2
done
# Starte LightDM falls nicht läuft
if ! systemctl is-active --quiet lightdm; then
echo "\$(date): Starte LightDM" >> /var/log/kiosk-fallback.log
systemctl start lightdm
sleep 5
fi
# Prüfe nach 30 Sekunden ob Kiosk-Benutzer angemeldet ist
sleep 30
if ! pgrep -u $KIOSK_USER > /dev/null; then
echo "\$(date): Kiosk-Benutzer nicht angemeldet - starte LightDM neu" >> /var/log/kiosk-fallback.log
systemctl restart lightdm
fi
echo "\$(date): rc.local Kiosk-Fallback abgeschlossen" >> /var/log/kiosk-fallback.log
exit 0
EOF
chmod +x "/etc/rc.local"
info "Watchdog-Services verstärkt"
log "✅ Watchdog-Verstärkung abgeschlossen"
}
# ========================== SYSTEM-SERVICES OPTIMIEREN ==========================
optimize_services() {
log "=== SERVICE-OPTIMIERUNG ==="
progress "Optimiere System-Services für schnelleren Start..."
# Deaktiviere unnötige Services
DISABLE_SERVICES=(
"bluetooth"
"hciuart"
"triggerhappy"
"avahi-daemon"
"cups"
"cups-browsed"
"ModemManager"
"wpa_supplicant"
)
for service in "${DISABLE_SERVICES[@]}"; do
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
systemctl disable "$service" 2>/dev/null || true
info "Service '$service' deaktiviert"
fi
done
# Optimiere wichtige Services
systemctl enable myp-druckerverwaltung
systemctl enable lightdm
systemctl enable kiosk-watchdog-enhanced
# Systemd-Daemon neu laden
systemctl daemon-reload
info "Services optimiert"
log "✅ Service-Optimierung abgeschlossen"
}
# ========================== SYSTEM-PARAMETER OPTIMIEREN ==========================
optimize_system_parameters() {
log "=== SYSTEM-PARAMETER OPTIMIERUNG ==="
progress "Optimiere System-Parameter..."
# Systemd-Logind für Kiosk optimieren
mkdir -p "/etc/systemd/logind.conf.d"
cat > "/etc/systemd/logind.conf.d/kiosk.conf" << EOF
[Login]
# Verhindere dass System bei Inaktivität heruntergefahren wird
IdleAction=ignore
IdleActionSec=infinity
# Verhindere Suspend/Hibernate
HandlePowerKey=ignore
HandleSuspendKey=ignore
HandleHibernateKey=ignore
HandleLidSwitch=ignore
# Session-Einstellungen für Kiosk
KillUserProcesses=no
UserStopDelaySec=10
# Automatic VT allocation
ReserveVT=1
EOF
# Kernel-Parameter für bessere Performance
cat > "/etc/sysctl.d/99-kiosk-performance.conf" << EOF
# Kiosk-Performance Optimierungen
vm.swappiness=10
vm.dirty_ratio=15
vm.dirty_background_ratio=5
kernel.sched_autogroup_enabled=0
EOF
# Tmpfs für bessere Performance
if ! grep -q "tmpfs.*tmp" /etc/fstab; then
echo "tmpfs /tmp tmpfs defaults,noatime,nosuid,size=100m 0 0" >> /etc/fstab
info "Tmpfs für /tmp konfiguriert"
fi
info "System-Parameter optimiert"
log "✅ System-Parameter Optimierung abgeschlossen"
}
# ========================== WARTUNGSTOOLS ERSTELLEN ==========================
create_maintenance_tools() {
log "=== WARTUNGSTOOLS ERSTELLEN ==="
progress "Erstelle Wartungs-Skript..."
# Wartungsskript
cat > "/usr/local/bin/myp-maintenance" << 'EOF'
#!/bin/bash
case "$1" in
start)
echo "Starte alle MYP-Services..."
systemctl start myp-druckerverwaltung
systemctl start nginx
systemctl start lightdm
echo "Services gestartet."
;;
stop)
echo "Stoppe alle MYP-Services..."
systemctl stop lightdm
systemctl stop nginx
systemctl stop myp-druckerverwaltung
echo "Services gestoppt."
;;
restart)
echo "Starte alle MYP-Services neu..."
systemctl restart myp-druckerverwaltung
sleep 3
systemctl restart nginx
systemctl restart lightdm
echo "Services neugestartet."
;;
status)
echo "=== MYP SYSTEM STATUS ==="
echo
echo "📱 Anwendung:"
systemctl status myp-druckerverwaltung --no-pager -l
echo
echo "🌐 Nginx Proxy:"
systemctl status nginx --no-pager -l
echo
echo "🖥️ Display Manager:"
systemctl status lightdm --no-pager -l
echo
echo "👤 Kiosk-Benutzer-Sessions:"
who | grep kiosk || echo "Kein Kiosk-Benutzer angemeldet"
echo
echo "🌐 Anwendung erreichbar:"
if curl -s http://localhost:5000 > /dev/null; then
echo "✅ http://localhost:5000 erreichbar"
else
echo "❌ http://localhost:5000 NICHT erreichbar"
fi
;;
logs)
echo "=== ANWENDUNGS-LOGS (Strg+C zum Beenden) ==="
journalctl -u myp-druckerverwaltung -f
;;
kiosk-logs)
echo "=== KIOSK-LOGS (Strg+C zum Beenden) ==="
echo "LightDM-Logs:"
journalctl -u lightdm -f &
echo "Session-Logs:"
tail -f /var/log/kiosk-session.log 2>/dev/null &
wait
;;
exit-kiosk)
echo "🔐 KIOSK-MODUS BEENDEN"
echo "WARNUNG: Stoppt den Kiosk und aktiviert Wartungsmodus!"
echo "Passwort erforderlich für Sicherheit."
read -s -p "Kiosk-Passwort: " password
echo
if [ "$password" = "744563017196A" ]; then
echo "✅ Passwort korrekt - beende Kiosk-Modus..."
systemctl stop lightdm
systemctl enable ssh
systemctl start ssh
echo "🔧 Wartungsmodus aktiviert:"
echo " • Kiosk gestoppt"
echo " • SSH aktiviert"
echo " • Console verfügbar"
echo "Kiosk-Neustart mit: myp-maintenance start"
else
echo "❌ Falsches Passwort! Kiosk bleibt aktiv."
exit 1
fi
;;
enable-ssh)
echo "Aktiviere SSH für Wartung..."
systemctl enable ssh
systemctl start ssh
echo "✅ SSH aktiviert für Remote-Wartung"
echo "SSH-Status: $(systemctl is-active ssh)"
echo "IP-Adresse: $(hostname -I | awk '{print $1}')"
;;
disable-ssh)
echo "Deaktiviere SSH für Sicherheit..."
systemctl stop ssh
systemctl disable ssh
echo "✅ SSH deaktiviert"
;;
check-health)
echo "=== SYSTEM-GESUNDHEITSCHECK ==="
echo
# Services-Check
echo "📋 Service-Status:"
for service in myp-druckerverwaltung nginx lightdm; do
if systemctl is-active --quiet $service; then
echo "$service: aktiv"
else
echo "$service: INAKTIV"
fi
done
echo
# Netzwerk-Check
echo "🌐 Netzwerk-Status:"
if curl -s http://localhost:5000 > /dev/null; then
echo " ✅ Anwendung erreichbar"
else
echo " ❌ Anwendung NICHT erreichbar"
fi
echo
# Kiosk-Check
echo "🖥️ Kiosk-Status:"
if pgrep -u kiosk > /dev/null; then
echo " ✅ Kiosk-Benutzer angemeldet"
else
echo " ❌ Kiosk-Benutzer NICHT angemeldet"
fi
if pgrep -f "chromium.*kiosk" > /dev/null; then
echo " ✅ Chromium-Kiosk läuft"
else
echo " ❌ Chromium-Kiosk läuft NICHT"
fi
echo
;;
*)
echo "MYP Druckerverwaltung - Wartungstool"
echo
echo "VERWENDUNG: $0 BEFEHL"
echo
echo "SERVICE-MANAGEMENT:"
echo " start Alle Services starten"
echo " stop Alle Services stoppen"
echo " restart Alle Services neustarten"
echo " status Detaillierter Status aller Services"
echo
echo "LOGS & MONITORING:"
echo " logs Live Anwendungs-Logs anzeigen"
echo " kiosk-logs Live Kiosk-Logs anzeigen"
echo " check-health System-Gesundheitscheck"
echo
echo "KIOSK-KONTROLLE:"
echo " exit-kiosk Kiosk beenden (Passwort: 744563017196A)"
echo " enable-ssh SSH für Remote-Wartung aktivieren"
echo " disable-ssh SSH wieder deaktivieren"
echo
;;
esac
EOF
chmod +x "/usr/local/bin/myp-maintenance"
# Kiosk-Starter-Skript
progress "Erstelle Kiosk-Starter-Skript..."
KIOSK_HOME="/home/$KIOSK_USER"
cat > "$KIOSK_HOME/start-kiosk.sh" << EOF
#!/bin/bash
# MYP Kiosk-Starter
export DISPLAY=:0
# Logging für Debugging
exec > >(tee -a /var/log/kiosk-session.log) 2>&1
echo "\$(date): Kiosk-Session gestartet für Benutzer $KIOSK_USER"
# Bildschirmschoner deaktivieren
xset s off
xset s noblank
xset s noexpose
xset -dpms
# Mauszeiger verstecken
unclutter -idle 0.5 -root &
# Warte auf Anwendung
echo "Warte auf MYP-Anwendung..."
WAIT_COUNT=0
while ! curl -s http://localhost:5000 > /dev/null; do
echo "Warte auf MYP-Anwendung... (\$WAIT_COUNT/30)"
sleep 2
WAIT_COUNT=\$((WAIT_COUNT + 1))
if [ \$WAIT_COUNT -gt 30 ]; then
echo "FEHLER: MYP-Anwendung nach 60s nicht erreichbar!"
break
fi
done
# Starte Chromium im Kiosk-Modus
if command -v chromium &> /dev/null; then
CHROMIUM_BIN="chromium"
elif command -v chromium-browser &> /dev/null; then
CHROMIUM_BIN="chromium-browser"
else
echo "Chromium nicht gefunden! Versuche alternativ Firefox..."
if command -v firefox &> /dev/null; then
firefox --kiosk http://localhost:5000
exit 0
else
echo "Kein unterstützter Browser gefunden!"
exit 1
fi
fi
echo "Starte \$CHROMIUM_BIN im Kiosk-Modus..."
\$CHROMIUM_BIN --kiosk --no-sandbox --disable-infobars --disable-session-crashed-bubble http://localhost:5000
echo "\$(date): Kiosk-Session beendet"
EOF
chmod +x "$KIOSK_HOME/start-kiosk.sh"
chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME/start-kiosk.sh"
# Erstelle leere Log-Dateien
touch /var/log/kiosk-session.log
touch /var/log/kiosk-watchdog.log
touch /var/log/kiosk-autostart.log
touch /var/log/kiosk-fallback.log
chmod 666 /var/log/kiosk-session.log
chmod 666 /var/log/kiosk-watchdog.log
chmod 666 /var/log/kiosk-autostart.log
chmod 666 /var/log/kiosk-fallback.log
log "✅ Wartungstools erstellt"
}
# ========================== SERVICE-DATEIEN ERSTELLEN ==========================
create_service_files() {
log "=== SERVICE-DATEIEN ERSTELLEN ==="
progress "Erstelle myp-druckerverwaltung.service..."
# Service-Datei für die Hauptanwendung
cat > "/etc/systemd/system/myp-druckerverwaltung.service" << EOF
[Unit]
Description=MYP Druckerverwaltung Flask Application
After=network.target
[Service]
Type=simple
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_DIR
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=PYTHONPATH=$APP_DIR
ExecStart=/usr/bin/python3 $APP_DIR/app.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Erstelle eine einfache app.py falls keine vorhanden ist
if [ ! -f "$APP_DIR/app.py" ]; then
progress "Erstelle einfache app.py als Platzhalter..."
mkdir -p "$APP_DIR"
cat > "$APP_DIR/app.py" << 'EOF'
#!/usr/bin/python3
# Einfache Flask-Anwendung als Platzhalter
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/')
def home():
return render_template_string("""
<!DOCTYPE html>
<html>
<head>
<title>MYP Druckerverwaltung</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.box { background: white; border-radius: 5px; padding: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div class="container">
<h1>MYP Druckerverwaltung</h1>
<div class="box">
<h2>System erfolgreich gestartet</h2>
<p>Die MYP Druckerverwaltung läuft im Kiosk-Modus.</p>
<p>Sie können diese Anwendung nun durch Ihre eigentliche Anwendung ersetzen.</p>
</div>
</div>
</body>
</html>
""")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
EOF
chmod +x "$APP_DIR/app.py"
chown "$APP_USER:$APP_USER" "$APP_DIR/app.py"
# Installiere Flask falls nicht vorhanden
if ! python3 -c "import flask" &>/dev/null; then
progress "Installiere Flask..."
pip3 install flask --break-system-packages || true
fi
fi
# Erstelle Templates-Verzeichnis falls nicht vorhanden
if [ ! -d "$APP_DIR/templates" ]; then
mkdir -p "$APP_DIR/templates"
chown "$APP_USER:$APP_USER" "$APP_DIR/templates"
fi
# Erstelle Static-Verzeichnis falls nicht vorhanden
if [ ! -d "$APP_DIR/static" ]; then
mkdir -p "$APP_DIR/static"
chown "$APP_USER:$APP_USER" "$APP_DIR/static"
fi
# Systemd neu laden
systemctl daemon-reload
log "✅ Service-Dateien erstellt"
}
# ========================== HAUPTFUNKTION ==========================
main() {
log "=== RASPBERRY PI SCHNELLSTART-OPTIMIERUNG GESTARTET ==="
check_root
check_system
install_essential_packages
create_service_files
optimize_boot
strengthen_autologin
optimize_kiosk_user
create_maintenance_tools
strengthen_watchdog
optimize_services
optimize_system_parameters
log "=== OPTIMIERUNG ABGESCHLOSSEN ==="
log ""
log "🎉 RASPBERRY PI SCHNELLSTART-OPTIMIERUNG ERFOLGREICH!"
log ""
log "📋 ZUSAMMENFASSUNG:"
log " ✅ Service-Dateien erstellt"
log " ✅ Boot-Parameter optimiert"
log " ✅ Autologin verstärkt"
log " ✅ Kiosk-Benutzer optimiert"
log " ✅ Wartungstools erstellt"
log " ✅ Watchdog-Services verstärkt"
log " ✅ System-Services optimiert"
log " ✅ System-Parameter optimiert"
log ""
log "🔄 NEUSTART ERFORDERLICH:"
log " sudo reboot"
log ""
log "📊 NACH DEM NEUSTART:"
log " - System startet automatisch ohne Anmeldung"
log " - Kiosk-Modus wird automatisch gestartet"
log " - Web-UI ist sofort verfügbar"
log " - Mehrfache Überwachung aktiv"
log ""
log "🔧 WARTUNG:"
log " sudo myp-maintenance status # System-Status prüfen"
log " sudo myp-maintenance logs # Logs anzeigen"
log " sudo myp-maintenance restart # Services neustarten"
log ""
warning "WICHTIG: Führen Sie jetzt 'sudo reboot' aus, um die Optimierungen zu aktivieren!"
}
# Skript ausführen
main "$@"

View File

@ -1,396 +0,0 @@
"""
Queue Manager für die Verwaltung von Druckjobs in Warteschlangen.
Überwacht offline Drucker und aktiviert Jobs automatisch.
"""
import threading
import time
import logging
import subprocess
import os
import requests
import signal
import atexit
from datetime import datetime, timedelta
from typing import List, Dict, Optional, Tuple
from contextlib import contextmanager
from models import get_db_session, Job, Printer, User, Notification
from utils.logging_config import get_logger
# Windows-spezifische Imports
if os.name == 'nt':
try:
from utils.windows_fixes import get_windows_thread_manager
except ImportError:
get_windows_thread_manager = None
else:
get_windows_thread_manager = None
# Logger für Queue-Manager
queue_logger = get_logger("queue_manager")
def check_printer_status(ip_address: str, timeout: int = 5) -> Tuple[str, bool]:
"""
Vereinfachte Drucker-Status-Prüfung für den Queue Manager.
Args:
ip_address: IP-Adresse der Drucker-Steckdose
timeout: Timeout in Sekunden (Standard: 5)
Returns:
Tuple[str, bool]: (Status, Aktiv) - Status ist "online" oder "offline", Aktiv ist True/False
"""
if not ip_address or ip_address.strip() == "":
return "offline", False
try:
# Ping-Test um Erreichbarkeit zu prüfen
if os.name == 'nt': # Windows
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()]
else: # Unix/Linux/macOS
cmd = ['ping', '-c', '1', '-W', str(timeout), ip_address.strip()]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 1,
encoding='utf-8',
errors='replace'
)
# Wenn Ping erfolgreich ist, als online betrachten
if result.returncode == 0:
queue_logger.debug(f"✅ Drucker {ip_address} ist erreichbar (Ping erfolgreich)")
return "online", True
else:
queue_logger.debug(f"❌ Drucker {ip_address} nicht erreichbar (Ping fehlgeschlagen)")
return "offline", False
except subprocess.TimeoutExpired:
queue_logger.warning(f"⏱️ Ping-Timeout für Drucker {ip_address} nach {timeout} Sekunden")
return "offline", False
except Exception as e:
queue_logger.error(f"❌ Fehler beim Status-Check für Drucker {ip_address}: {str(e)}")
return "offline", False
class PrinterQueueManager:
"""
Verwaltet die Warteschlangen für offline Drucker und überwacht deren Status.
Verbesserte Version mit ordnungsgemäßem Thread-Management für Windows.
"""
def __init__(self):
self.is_running = False
self.monitor_thread = None
self.shutdown_event = threading.Event() # Sauberes Shutdown-Signal
self.check_interval = 120 # 2 Minuten zwischen Status-Checks
self.last_status_cache = {} # Cache für letzten bekannten Status
self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen
self._lock = threading.Lock() # Thread-Sicherheit
# Windows-spezifische Signal-Handler registrieren
if os.name == 'nt':
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def _signal_handler(self, signum, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
queue_logger.warning(f"🛑 Signal {signum} empfangen - stoppe Queue Manager...")
self.stop()
def start(self):
"""Startet den Queue-Manager mit verbessertem Thread-Management."""
with self._lock:
if not self.is_running:
self.is_running = True
self.shutdown_event.clear()
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=False)
self.monitor_thread.name = "PrinterQueueMonitor"
# Windows Thread-Manager verwenden falls verfügbar
if os.name == 'nt' and get_windows_thread_manager:
try:
thread_manager = get_windows_thread_manager()
thread_manager.register_thread(self.monitor_thread)
thread_manager.register_cleanup_function(self.stop)
queue_logger.debug("✅ Queue Manager bei Windows Thread-Manager registriert")
except Exception as e:
queue_logger.warning(f"⚠️ Windows Thread-Manager nicht verfügbar: {str(e)}")
self.monitor_thread.start()
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
def stop(self):
"""Stoppt den Queue-Manager ordnungsgemäß."""
with self._lock:
if self.is_running:
queue_logger.info("🔄 Beende Queue Manager...")
self.is_running = False
self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive():
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
self.monitor_thread.join(timeout=10)
if self.monitor_thread.is_alive():
queue_logger.warning("⚠️ Thread konnte nicht ordnungsgemäß beendet werden")
else:
queue_logger.info("✅ Monitor-Thread erfolgreich beendet")
self.monitor_thread = None
queue_logger.info("❌ Printer Queue Manager gestoppt")
def _monitor_loop(self):
"""Hauptschleife für die Überwachung der Drucker mit verbessertem Shutdown-Handling."""
queue_logger.info(f"🔄 Queue-Überwachung gestartet (Intervall: {self.check_interval} Sekunden)")
while self.is_running and not self.shutdown_event.is_set():
try:
self._check_waiting_jobs()
# Verwende Event.wait() statt time.sleep() für unterbrechbares Warten
if self.shutdown_event.wait(timeout=self.check_interval):
# Shutdown-Signal erhalten
queue_logger.info("🛑 Shutdown-Signal empfangen - beende Monitor-Loop")
break
except Exception as e:
queue_logger.error(f"❌ Fehler in Monitor-Schleife: {str(e)}")
# Kürzere Wartezeit bei Fehlern, aber auch unterbrechbar
if self.shutdown_event.wait(timeout=30):
break
queue_logger.info("🔚 Monitor-Loop beendet")
def _check_waiting_jobs(self):
"""Überprüft alle wartenden Jobs und aktiviert sie bei verfügbaren Druckern."""
if self.shutdown_event.is_set():
return
db_session = get_db_session()
try:
# Alle wartenden Jobs abrufen
waiting_jobs = db_session.query(Job).filter(
Job.status == "waiting_for_printer"
).all()
if not waiting_jobs:
return
queue_logger.info(f"🔍 Überprüfe {len(waiting_jobs)} wartende Jobs...")
activated_jobs = []
for job in waiting_jobs:
# Shutdown-Check zwischen Jobs
if self.shutdown_event.is_set():
break
# Drucker-Status prüfen
printer = db_session.get(Printer, job.printer_id)
if not printer:
continue
# Status-Check mit Cache-Optimierung
printer_key = f"printer_{printer.id}"
current_status = None
try:
if printer.plug_ip:
status, active = check_printer_status(printer.plug_ip, timeout=5)
current_status = "online" if (status == "online" and active) else "offline"
else:
current_status = "offline"
except Exception as e:
queue_logger.warning(f"⚠️ Status-Check für Drucker {printer.name} fehlgeschlagen: {str(e)}")
current_status = "offline"
# Prüfen, ob Drucker online geworden ist
last_status = self.last_status_cache.get(printer_key, "offline")
self.last_status_cache[printer_key] = current_status
if current_status == "online" and last_status == "offline":
# Drucker ist online geworden!
queue_logger.info(f"🟢 Drucker {printer.name} ist ONLINE geworden - aktiviere wartende Jobs")
# Job aktivieren
job.status = "scheduled"
printer.status = "available"
printer.active = True
printer.last_checked = datetime.now()
activated_jobs.append({
"job": job,
"printer": printer
})
elif current_status == "online":
# Drucker ist bereits online, Job kann aktiviert werden
job.status = "scheduled"
printer.status = "available"
printer.active = True
printer.last_checked = datetime.now()
activated_jobs.append({
"job": job,
"printer": printer
})
else:
# Drucker bleibt offline
printer.status = "offline"
printer.active = False
printer.last_checked = datetime.now()
# Speichere alle Änderungen
if activated_jobs:
db_session.commit()
queue_logger.info(f"{len(activated_jobs)} Jobs erfolgreich aktiviert")
# Benachrichtigungen versenden (nur wenn nicht im Shutdown)
if not self.shutdown_event.is_set():
for item in activated_jobs:
self._send_job_activation_notification(item["job"], item["printer"])
else:
# Auch offline-Status speichern
db_session.commit()
except Exception as e:
db_session.rollback()
queue_logger.error(f"❌ Fehler beim Überprüfen wartender Jobs: {str(e)}")
finally:
db_session.close()
def _send_job_activation_notification(self, job: Job, printer: Printer):
"""Sendet eine Benachrichtigung, wenn ein Job aktiviert wird."""
if self.shutdown_event.is_set():
return
try:
# Cooldown prüfen (keine Spam-Benachrichtigungen)
cooldown_key = f"job_{job.id}_activated"
now = datetime.now()
if cooldown_key in self.notification_cooldown:
last_notification = self.notification_cooldown[cooldown_key]
if (now - last_notification).total_seconds() < 300: # 5 Minuten Cooldown
return
self.notification_cooldown[cooldown_key] = now
# Benachrichtigung erstellen
db_session = get_db_session()
try:
user = db_session.get(User, job.user_id)
if not user:
return
notification = Notification(
user_id=user.id,
type="job_activated",
payload={
"job_id": job.id,
"job_name": job.name,
"printer_id": printer.id,
"printer_name": printer.name,
"start_time": job.start_at.isoformat() if job.start_at else None,
"message": f"🎉 Gute Nachrichten! Drucker '{printer.name}' ist online. Ihr Job '{job.name}' wurde aktiviert und startet bald."
}
)
db_session.add(notification)
db_session.commit()
queue_logger.info(f"📧 Benachrichtigung für User {user.name} gesendet: Job {job.name} aktiviert")
except Exception as e:
db_session.rollback()
queue_logger.error(f"❌ Fehler beim Erstellen der Benachrichtigung: {str(e)}")
finally:
db_session.close()
except Exception as e:
queue_logger.error(f"❌ Fehler beim Senden der Aktivierungs-Benachrichtigung: {str(e)}")
def get_queue_status(self) -> Dict:
"""Gibt den aktuellen Status der Warteschlangen zurück."""
db_session = get_db_session()
try:
# Wartende Jobs zählen
waiting_jobs = db_session.query(Job).filter(
Job.status == "waiting_for_printer"
).count()
# Offline Drucker mit wartenden Jobs
offline_printers_with_queue = db_session.query(Printer).join(Job).filter(
Printer.status == "offline",
Job.status == "waiting_for_printer"
).distinct().count()
# Online Drucker
online_printers = db_session.query(Printer).filter(
Printer.status == "available"
).count()
total_printers = db_session.query(Printer).count()
return {
"waiting_jobs": waiting_jobs,
"offline_printers_with_queue": offline_printers_with_queue,
"online_printers": online_printers,
"total_printers": total_printers,
"queue_manager_running": self.is_running,
"last_check": datetime.now().isoformat(),
"check_interval_seconds": self.check_interval
}
except Exception as e:
queue_logger.error(f"❌ Fehler beim Abrufen des Queue-Status: {str(e)}")
return {
"error": str(e),
"queue_manager_running": self.is_running
}
finally:
db_session.close()
def is_healthy(self) -> bool:
"""Prüft, ob der Queue Manager ordnungsgemäß läuft."""
return (self.is_running and
self.monitor_thread is not None and
self.monitor_thread.is_alive() and
not self.shutdown_event.is_set())
# Globale Instanz des Queue-Managers
_queue_manager_instance = None
_queue_manager_lock = threading.Lock()
def get_queue_manager() -> PrinterQueueManager:
"""Gibt die globale Instanz des Queue-Managers zurück."""
global _queue_manager_instance
with _queue_manager_lock:
if _queue_manager_instance is None:
_queue_manager_instance = PrinterQueueManager()
return _queue_manager_instance
def start_queue_manager():
"""Startet den globalen Queue-Manager."""
manager = get_queue_manager()
manager.start()
return manager
def stop_queue_manager():
"""Stoppt den globalen Queue-Manager."""
global _queue_manager_instance
with _queue_manager_lock:
if _queue_manager_instance:
_queue_manager_instance.stop()
_queue_manager_instance = None
# Automatisches Cleanup bei Prozess-Ende registrieren
atexit.register(stop_queue_manager)

View File

@ -1,353 +0,0 @@
"""
Windows-spezifische Fixes für Thread- und Socket-Probleme
Behebt bekannte Issues mit Flask Auto-Reload auf Windows.
"""
import os
import sys
import signal
import threading
import time
import atexit
from typing import List, Callable
from utils.logging_config import get_logger
# Logger für Windows-Fixes
windows_logger = get_logger("windows_fixes")
# Globale Flags um doppelte Anwendung zu verhindern
_windows_fixes_applied = False
_socket_patches_applied = False
class WindowsThreadManager:
"""
Verwaltet Threads und deren ordnungsgemäße Beendigung auf Windows.
Behebt Socket-Fehler beim Flask Auto-Reload.
"""
def __init__(self):
self.managed_threads: List[threading.Thread] = []
self.cleanup_functions: List[Callable] = []
self.shutdown_event = threading.Event()
self._lock = threading.Lock()
self._is_shutting_down = False
# Signal-Handler nur auf Windows registrieren
if os.name == 'nt':
self._register_signal_handlers()
def _register_signal_handlers(self):
"""Registriert Windows-spezifische Signal-Handler."""
try:
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
# Windows-spezifisches SIGBREAK
if hasattr(signal, 'SIGBREAK'):
signal.signal(signal.SIGBREAK, self._signal_handler)
windows_logger.debug("✅ Windows Signal-Handler registriert")
except Exception as e:
windows_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {str(e)}")
def _signal_handler(self, sig, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
if not self._is_shutting_down:
windows_logger.warning(f"🛑 Windows Signal {sig} empfangen - initiiere Shutdown")
self.shutdown_all()
def register_thread(self, thread: threading.Thread):
"""Registriert einen Thread für ordnungsgemäße Beendigung."""
with self._lock:
if thread not in self.managed_threads:
self.managed_threads.append(thread)
windows_logger.debug(f"📝 Thread {thread.name} registriert")
def register_cleanup_function(self, func: Callable):
"""Registriert eine Cleanup-Funktion."""
with self._lock:
if func not in self.cleanup_functions:
self.cleanup_functions.append(func)
windows_logger.debug(f"📝 Cleanup-Funktion registriert")
def shutdown_all(self):
"""Beendet alle verwalteten Threads und führt Cleanup durch."""
if self._is_shutting_down:
return
with self._lock:
self._is_shutting_down = True
windows_logger.info("🔄 Starte Windows Thread-Shutdown...")
# Shutdown-Event setzen
self.shutdown_event.set()
# Cleanup-Funktionen ausführen
for func in self.cleanup_functions:
try:
windows_logger.debug(f"🧹 Führe Cleanup-Funktion aus: {func.__name__}")
func()
except Exception as e:
windows_logger.error(f"❌ Fehler bei Cleanup-Funktion {func.__name__}: {str(e)}")
# Threads beenden
active_threads = [t for t in self.managed_threads if t.is_alive()]
if active_threads:
windows_logger.info(f"⏳ Warte auf {len(active_threads)} aktive Threads...")
for thread in active_threads:
try:
windows_logger.debug(f"🔄 Beende Thread: {thread.name}")
thread.join(timeout=5)
if thread.is_alive():
windows_logger.warning(f"⚠️ Thread {thread.name} konnte nicht ordnungsgemäß beendet werden")
else:
windows_logger.debug(f"✅ Thread {thread.name} erfolgreich beendet")
except Exception as e:
windows_logger.error(f"❌ Fehler beim Beenden von Thread {thread.name}: {str(e)}")
windows_logger.info("✅ Windows Thread-Shutdown abgeschlossen")
# Globale Instanz
_windows_thread_manager = None
def get_windows_thread_manager() -> WindowsThreadManager:
"""Gibt die globale Instanz des Windows Thread-Managers zurück."""
global _windows_thread_manager
if _windows_thread_manager is None:
_windows_thread_manager = WindowsThreadManager()
return _windows_thread_manager
def fix_windows_socket_issues():
"""
Anwendung von Windows-spezifischen Socket-Fixes.
Vereinfachte, sichere Version ohne Monkey-Patching.
"""
global _socket_patches_applied
if os.name != 'nt':
return
if _socket_patches_applied:
windows_logger.debug("⏭️ Socket-Patches bereits angewendet")
return
try:
# SICHERERE Alternative: Nur TCP Socket-Optionen setzen ohne Monkey-Patching
import socket
# Erweitere die Socket-Klasse mit einer Hilfsmethode
if not hasattr(socket.socket, 'windows_bind_with_reuse'):
def windows_bind_with_reuse(self, address):
"""Windows-optimierte bind-Methode mit SO_REUSEADDR."""
try:
# SO_REUSEADDR aktivieren
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
windows_logger.debug(f"SO_REUSEADDR aktiviert für Socket {address}")
except Exception as e:
windows_logger.debug(f"SO_REUSEADDR konnte nicht gesetzt werden: {str(e)}")
# Standard-bind ausführen
return self.bind(address)
# Füge die Hilfsmethode hinzu ohne die ursprüngliche bind-Methode zu überschreiben
socket.socket.windows_bind_with_reuse = windows_bind_with_reuse
# Setze globale Socket-Optionen für bessere Windows-Kompatibilität
socket.setdefaulttimeout(30) # 30 Sekunden Standard-Timeout
_socket_patches_applied = True
windows_logger.debug("✅ Windows Socket-Optimierungen angewendet (sicher)")
except Exception as e:
windows_logger.warning(f"⚠️ Socket-Optimierungen konnten nicht angewendet werden: {str(e)}")
def apply_safe_socket_options():
"""
Wendet sichere Socket-Optionen für Windows an ohne Monkey-Patching.
"""
if os.name != 'nt':
return
try:
import socket
# Sichere Socket-Defaults für Windows
if hasattr(socket, 'TCP_NODELAY'):
# TCP_NODELAY als Standard aktivieren für bessere Performance
pass # Wird pro Socket gesetzt, nicht global
windows_logger.debug("✅ Sichere Socket-Optionen angewendet")
except Exception as e:
windows_logger.debug(f"Socket-Optionen konnten nicht gesetzt werden: {str(e)}")
def setup_windows_environment():
"""
Richtet die Windows-Umgebung für bessere Flask-Kompatibilität ein.
"""
if os.name != 'nt':
return
try:
# Umgebungsvariablen für bessere Windows-Kompatibilität
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['PYTHONUTF8'] = '1'
windows_logger.debug("✅ Windows-Umgebung optimiert")
except Exception as e:
windows_logger.warning(f"⚠️ Windows-Umgebung konnte nicht optimiert werden: {str(e)}")
def is_flask_reloader_process() -> bool:
"""
Prüft, ob der aktuelle Prozess der Flask-Reloader-Prozess ist.
"""
return os.environ.get('WERKZEUG_RUN_MAIN') != 'true'
def apply_all_windows_fixes():
"""Wendet alle Windows-spezifischen Fixes an."""
global _windows_fixes_applied
if _windows_fixes_applied:
return
try:
logger.info("🔧 Wende Windows-spezifische Fixes an...")
# 1. Encoding-Fixes
apply_encoding_fixes()
# 2. Threading-Fixes
apply_threading_fixes()
# 3. Signal-Handler-Fixes
apply_signal_fixes()
# 4. Subprocess-Patch für UTF-8 Encoding
patch_subprocess()
# 5. Globaler Subprocess-Patch für bereits importierte Module
apply_global_subprocess_patch()
_windows_fixes_applied = True
logger.info("✅ Alle Windows-Fixes erfolgreich angewendet")
except Exception as e:
logger.error(f"❌ Fehler beim Anwenden der Windows-Fixes: {str(e)}")
raise e
# Automatisch Windows-Fixes beim Import anwenden (nur einmal)
if os.name == 'nt' and not _windows_fixes_applied:
# Sehr früher subprocess-Patch für sofortige Wirkung
try:
import subprocess
if not hasattr(subprocess, '_early_patched'):
patch_subprocess()
subprocess._early_patched = True
logger.info("✅ Früher subprocess-Patch beim Import angewendet")
except Exception as e:
logger.warning(f"⚠️ Früher subprocess-Patch fehlgeschlagen: {str(e)}")
apply_all_windows_fixes()
# ===== SICHERE SUBPROCESS-WRAPPER =====
def safe_subprocess_run(*args, **kwargs):
"""
Sicherer subprocess.run Wrapper für Windows mit UTF-8 Encoding.
Verhindert charmap-Fehler durch explizite Encoding-Einstellungen.
"""
import subprocess
# Standard-Encoding für Windows setzen
if 'encoding' not in kwargs and kwargs.get('text', False):
kwargs['encoding'] = 'utf-8'
kwargs['errors'] = 'replace'
# Timeout-Standard setzen falls nicht vorhanden
if 'timeout' not in kwargs:
kwargs['timeout'] = 30
try:
return subprocess.run(*args, **kwargs)
except subprocess.TimeoutExpired as e:
logger.warning(f"Subprocess-Timeout nach {kwargs.get('timeout', 30)}s: {' '.join(args[0]) if args and isinstance(args[0], list) else str(args)}")
raise e
except UnicodeDecodeError as e:
logger.error(f"Unicode-Decode-Fehler in subprocess: {str(e)}")
# Fallback ohne text=True
kwargs_fallback = kwargs.copy()
kwargs_fallback.pop('text', None)
kwargs_fallback.pop('encoding', None)
kwargs_fallback.pop('errors', None)
return subprocess.run(*args, **kwargs_fallback)
except Exception as e:
logger.error(f"Subprocess-Fehler: {str(e)}")
raise e
# ===== SUBPROCESS-MONKEY-PATCH =====
def patch_subprocess():
"""
Patcht subprocess.run und subprocess.Popen um automatisch sichere Encoding-Einstellungen zu verwenden.
"""
import subprocess
# Original-Funktionen speichern
if not hasattr(subprocess, '_original_run'):
subprocess._original_run = subprocess.run
subprocess._original_popen = subprocess.Popen
def patched_run(*args, **kwargs):
# Automatisch UTF-8 Encoding für text=True setzen
if kwargs.get('text', False) and 'encoding' not in kwargs:
kwargs['encoding'] = 'utf-8'
kwargs['errors'] = 'replace'
return subprocess._original_run(*args, **kwargs)
def patched_popen(*args, **kwargs):
# Automatisch UTF-8 Encoding für text=True setzen
if kwargs.get('text', False) and 'encoding' not in kwargs:
kwargs['encoding'] = 'utf-8'
kwargs['errors'] = 'replace'
# Auch für universal_newlines (ältere Python-Versionen)
if kwargs.get('universal_newlines', False) and 'encoding' not in kwargs:
kwargs['encoding'] = 'utf-8'
kwargs['errors'] = 'replace'
return subprocess._original_popen(*args, **kwargs)
subprocess.run = patched_run
subprocess.Popen = patched_popen
logger.info("✅ Subprocess automatisch gepatcht für UTF-8 Encoding (run + Popen)")
# ===== GLOBALER SUBPROCESS-PATCH =====
def apply_global_subprocess_patch():
"""
Wendet den subprocess-Patch global an, auch für bereits importierte Module.
"""
import sys
import subprocess
# Patch subprocess direkt
patch_subprocess()
# Patch auch in bereits importierten Modulen
for module_name, module in sys.modules.items():
if hasattr(module, 'subprocess') and module.subprocess is subprocess:
# Modul verwendet subprocess - patch es
module.subprocess = subprocess
logger.debug(f"✅ Subprocess in Modul {module_name} gepatcht")
logger.info("✅ Globaler subprocess-Patch angewendet")
# ===== EXPORT SAFE SUBPROCESS =====
# Sichere subprocess-Funktion exportieren
__all__.append('safe_subprocess_run')
__all__.append('patch_subprocess')
__all__.append('apply_global_subprocess_patch')

View File

@ -1,550 +0,0 @@
import json
from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
from flask_login import current_user, login_required
from sqlalchemy import and_, or_, func
from models import Job, Printer, User, UserPermission, get_cached_session
from utils.logging_config import get_logger
calendar_blueprint = Blueprint('calendar', __name__)
logger = get_logger("calendar")
def can_edit_events(user):
"""Prüft, ob ein Benutzer Kalendereinträge bearbeiten darf."""
if user.is_admin:
return True
with get_cached_session() as db_session:
permission = db_session.query(UserPermission).filter_by(user_id=user.id).first()
if not permission:
return False
return permission.can_approve_jobs
def get_smart_printer_assignment(start_date, end_date, priority="normal", db_session=None):
"""
Intelligente Druckerzuweisung basierend auf verschiedenen Faktoren.
Args:
start_date: Startzeit des Jobs
end_date: Endzeit des Jobs
priority: Prioritätsstufe ('urgent', 'high', 'normal', 'low')
db_session: Datenbankverbindung
Returns:
printer_id: ID des empfohlenen Druckers oder None
"""
if not db_session:
return None
try:
# Verfügbare Drucker ermitteln
available_printers = db_session.query(Printer).filter(
Printer.active == True
).all()
if not available_printers:
logger.warning("Keine aktiven Drucker für automatische Zuweisung gefunden")
return None
printer_scores = []
for printer in available_printers:
score = 0
# 1. Verfügbarkeit prüfen - Jobs im gleichen Zeitraum
conflicting_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.status.in_(["scheduled", "running"]),
or_(
and_(Job.start_at >= start_date, Job.start_at < end_date),
and_(Job.end_at > start_date, Job.end_at <= end_date),
and_(Job.start_at <= start_date, Job.end_at >= end_date)
)
).count()
if conflicting_jobs > 0:
continue # Drucker ist nicht verfügbar
score += 100 # Grundpunkte für Verfügbarkeit
# 2. Auslastung in den letzten 24 Stunden bewerten
last_24h = datetime.now() - timedelta(hours=24)
recent_jobs = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.start_at >= last_24h,
Job.status.in_(["scheduled", "running", "finished"])
).count()
# Weniger Auslastung = höhere Punktzahl
score += max(0, 50 - (recent_jobs * 10))
# 3. Prioritätsbasierte Zuweisung
if priority == "urgent":
# Für dringende Jobs: Express-Drucker bevorzugen
if "express" in printer.name.lower() or "schnell" in printer.name.lower():
score += 30
elif priority == "high":
# Für hohe Priorität: Weniger belastete Drucker
if recent_jobs <= 2:
score += 20
# 4. Zeitfenster-basierte Zuweisung
start_hour = start_date.hour
if start_hour >= 18 or start_hour <= 6: # Nachtschicht
if "nacht" in printer.name.lower() or printer.location and "c" in printer.location.lower():
score += 25
elif start_hour >= 8 and start_hour <= 17: # Tagschicht
if "tag" in printer.name.lower() or printer.location and "a" in printer.location.lower():
score += 15
# 5. Standort-basierte Bewertung (Round-Robin ähnlich)
if printer.location:
location_penalty = hash(printer.location) % 10 # Verteilung basierend auf Standort
score += location_penalty
# 6. Druckerdauer-Eignung
job_duration_hours = (end_date - start_date).total_seconds() / 3600
if job_duration_hours > 8: # Lange Jobs
if "langzeit" in printer.name.lower() or "marathon" in printer.name.lower():
score += 20
elif job_duration_hours <= 2: # Kurze Jobs
if "express" in printer.name.lower() or "schnell" in printer.name.lower():
score += 15
# 7. Wartungszyklen berücksichtigen
# Neuere Drucker (falls last_maintenance_date verfügbar) bevorzugen
# TODO: Implementierung abhängig von Printer-Model-Erweiterungen
printer_scores.append({
'printer': printer,
'score': score,
'conflicts': conflicting_jobs,
'recent_load': recent_jobs
})
# Nach Punktzahl sortieren und besten Drucker auswählen
if not printer_scores:
logger.warning("Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden")
return None
printer_scores.sort(key=lambda x: x['score'], reverse=True)
best_printer = printer_scores[0]
logger.info(f"Automatische Druckerzuweisung: {best_printer['printer'].name} "
f"(Score: {best_printer['score']}, Load: {best_printer['recent_load']})")
return best_printer['printer'].id
except Exception as e:
logger.error(f"Fehler bei automatischer Druckerzuweisung: {str(e)}")
return None
@calendar_blueprint.route('/calendar', methods=['GET'])
@login_required
def calendar_view():
"""Kalender-Ansicht anzeigen."""
can_edit = can_edit_events(current_user)
with get_cached_session() as db_session:
printers = db_session.query(Printer).filter_by(active=True).all()
return render_template('calendar.html',
printers=printers,
can_edit=can_edit)
@calendar_blueprint.route('/api/calendar', methods=['GET'])
@login_required
def api_get_calendar_events():
"""Kalendereinträge als JSON für FullCalendar zurückgeben."""
try:
# Datumsbereich aus Anfrage
start_str = request.args.get('from')
end_str = request.args.get('to')
if not start_str or not end_str:
# Standardmäßig eine Woche anzeigen
start_date = datetime.now()
end_date = start_date + timedelta(days=7)
else:
try:
start_date = datetime.fromisoformat(start_str)
end_date = datetime.fromisoformat(end_str)
except ValueError:
return jsonify({"error": "Ungültiges Datumsformat"}), 400
# Optional: Filter nach Druckern
printer_id = request.args.get('printer_id')
with get_cached_session() as db_session:
# Jobs im angegebenen Zeitraum abfragen
query = db_session.query(Job).filter(
or_(
# Jobs, die im Zeitraum beginnen
and_(Job.start_at >= start_date, Job.start_at <= end_date),
# Jobs, die im Zeitraum enden
and_(Job.end_at >= start_date, Job.end_at <= end_date),
# Jobs, die den Zeitraum komplett umfassen
and_(Job.start_at <= start_date, Job.end_at >= end_date)
)
)
if printer_id:
query = query.filter(Job.printer_id == printer_id)
jobs = query.all()
# Jobs in FullCalendar-Event-Format umwandeln
events = []
for job in jobs:
# Farbe basierend auf Status bestimmen
color = "#6B7280" # Grau für pending
if job.status == "running":
color = "#3B82F6" # Blau für running
elif job.status == "finished":
color = "#10B981" # Grün für finished
elif job.status == "scheduled":
color = "#10B981" # Grün für approved
elif job.status == "cancelled" or job.status == "failed":
color = "#EF4444" # Rot für abgebrochen/fehlgeschlagen
# Benutzerinformationen laden
user = db_session.query(User).filter_by(id=job.user_id).first()
user_name = user.name if user else "Unbekannt"
# Druckerinformationen laden
printer = db_session.query(Printer).filter_by(id=job.printer_id).first()
printer_name = printer.name if printer else "Unbekannt"
event = {
"id": job.id,
"title": job.name,
"start": job.start_at.isoformat(),
"end": job.end_at.isoformat(),
"color": color,
"extendedProps": {
"status": job.status,
"description": job.description,
"userName": user_name,
"printerId": job.printer_id,
"printerName": printer_name
}
}
events.append(event)
return jsonify(events)
except Exception as e:
logger.error(f"Fehler beim Abrufen der Kalendereinträge: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@calendar_blueprint.route('/api/calendar/event', methods=['POST'])
@login_required
def api_create_calendar_event():
"""Neuen Kalendereintrag (Job) erstellen."""
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge erstellen
if not can_edit_events(current_user):
return jsonify({"error": "Keine Berechtigung zum Erstellen von Kalendereinträgen"}), 403
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
# Pflichtfelder prüfen
title = data.get('title')
start = data.get('start')
end = data.get('end')
printer_id = data.get('printerId') # Jetzt optional
priority = data.get('priority', 'normal')
if not all([title, start, end]):
return jsonify({"error": "Titel, Start und Ende sind erforderlich"}), 400
# Datumsfelder konvertieren
try:
start_date = datetime.fromisoformat(start)
end_date = datetime.fromisoformat(end)
except ValueError:
return jsonify({"error": "Ungültiges Datumsformat"}), 400
# Dauer in Minuten berechnen
duration_minutes = int((end_date - start_date).total_seconds() / 60)
with get_cached_session() as db_session:
# Intelligente Druckerzuweisung falls kein Drucker angegeben
if not printer_id:
logger.info(f"Automatische Druckerzuweisung wird verwendet für Job '{title}'")
printer_id = get_smart_printer_assignment(
start_date=start_date,
end_date=end_date,
priority=priority,
db_session=db_session
)
if not printer_id:
return jsonify({
"error": "Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden. "
"Bitte wählen Sie einen spezifischen Drucker oder einen anderen Zeitraum."
}), 409
# Drucker prüfen/validieren
printer = db_session.query(Printer).filter_by(id=printer_id).first()
if not printer:
return jsonify({"error": "Drucker nicht gefunden"}), 404
if not printer.active:
return jsonify({"error": f"Drucker '{printer.name}' ist nicht aktiv"}), 400
# Nochmals Verfügbarkeit prüfen bei automatischer Zuweisung
if not data.get('printerId'): # War automatische Zuweisung
conflicting_jobs = db_session.query(Job).filter(
Job.printer_id == printer_id,
Job.status.in_(["scheduled", "running"]),
or_(
and_(Job.start_at >= start_date, Job.start_at < end_date),
and_(Job.end_at > start_date, Job.end_at <= end_date),
and_(Job.start_at <= start_date, Job.end_at >= end_date)
)
).first()
if conflicting_jobs:
return jsonify({
"error": f"Automatisch zugewiesener Drucker '{printer.name}' ist nicht mehr verfügbar. "
"Bitte wählen Sie einen anderen Zeitraum oder spezifischen Drucker."
}), 409
# Neuen Job erstellen
job = Job(
name=title,
description=data.get('description', ''),
user_id=current_user.id,
printer_id=printer_id,
start_at=start_date,
end_at=end_date,
status="scheduled",
duration_minutes=duration_minutes,
owner_id=current_user.id
)
db_session.add(job)
db_session.commit()
assignment_type = "automatisch" if not data.get('printerId') else "manuell"
logger.info(f"Neuer Kalendereintrag erstellt: ID {job.id}, Name: {title}, "
f"Drucker: {printer.name} ({assignment_type} zugewiesen)")
return jsonify({
"success": True,
"id": job.id,
"title": job.name,
"start": job.start_at.isoformat(),
"end": job.end_at.isoformat(),
"status": job.status,
"printer": {
"id": printer.id,
"name": printer.name,
"location": printer.location,
"assignment_type": assignment_type
},
"message": f"Auftrag erfolgreich erstellt und {assignment_type} dem Drucker '{printer.name}' zugewiesen."
})
except Exception as e:
logger.error(f"Fehler beim Erstellen des Kalendereintrags: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@calendar_blueprint.route('/api/calendar/event/<int:event_id>', methods=['PUT'])
@login_required
def api_update_calendar_event(event_id):
"""Kalendereintrag (Job) aktualisieren."""
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge bearbeiten
if not can_edit_events(current_user):
return jsonify({"error": "Keine Berechtigung zum Bearbeiten von Kalendereinträgen"}), 403
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
with get_cached_session() as db_session:
job = db_session.query(Job).filter_by(id=event_id).first()
if not job:
return jsonify({"error": "Kalendereintrag nicht gefunden"}), 404
# Felder aktualisieren, die im Request enthalten sind
if 'title' in data:
job.name = data['title']
if 'description' in data:
job.description = data['description']
if 'start' in data and 'end' in data:
try:
start_date = datetime.fromisoformat(data['start'])
end_date = datetime.fromisoformat(data['end'])
job.start_at = start_date
job.end_at = end_date
job.duration_minutes = int((end_date - start_date).total_seconds() / 60)
except ValueError:
return jsonify({"error": "Ungültiges Datumsformat"}), 400
if 'printerId' in data:
printer = db_session.query(Printer).filter_by(id=data['printerId']).first()
if not printer:
return jsonify({"error": "Drucker nicht gefunden"}), 404
job.printer_id = data['printerId']
if 'status' in data:
# Status nur ändern, wenn er gültig ist
valid_statuses = ["scheduled", "running", "finished", "cancelled"]
if data['status'] in valid_statuses:
job.status = data['status']
db_session.commit()
logger.info(f"Kalendereintrag aktualisiert: ID {job.id}")
return jsonify({
"success": True,
"id": job.id,
"title": job.name,
"start": job.start_at.isoformat(),
"end": job.end_at.isoformat(),
"status": job.status
})
except Exception as e:
logger.error(f"Fehler beim Aktualisieren des Kalendereintrags: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@calendar_blueprint.route('/api/calendar/event/<int:event_id>', methods=['DELETE'])
@login_required
def api_delete_calendar_event(event_id):
"""Kalendereintrag (Job) löschen."""
# Nur Admins und Benutzer mit can_approve_jobs dürfen Einträge löschen
if not can_edit_events(current_user):
return jsonify({"error": "Keine Berechtigung zum Löschen von Kalendereinträgen"}), 403
try:
with get_cached_session() as db_session:
job = db_session.query(Job).filter_by(id=event_id).first()
if not job:
return jsonify({"error": "Kalendereintrag nicht gefunden"}), 404
db_session.delete(job)
db_session.commit()
logger.info(f"Kalendereintrag gelöscht: ID {event_id}")
return jsonify({"success": True})
except Exception as e:
logger.error(f"Fehler beim Löschen des Kalendereintrags: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@calendar_blueprint.route('/api/calendar/smart-recommendation', methods=['POST'])
@login_required
def api_get_smart_recommendation():
"""Intelligente Druckerempfehlung für gegebenes Zeitfenster abrufen."""
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
start = data.get('start')
end = data.get('end')
priority = data.get('priority', 'normal')
if not all([start, end]):
return jsonify({"error": "Start und Ende sind erforderlich"}), 400
# Datumsfelder konvertieren
try:
start_date = datetime.fromisoformat(start)
end_date = datetime.fromisoformat(end)
except ValueError:
return jsonify({"error": "Ungültiges Datumsformat"}), 400
with get_cached_session() as db_session:
# Empfohlenen Drucker ermitteln
recommended_printer_id = get_smart_printer_assignment(
start_date=start_date,
end_date=end_date,
priority=priority,
db_session=db_session
)
if not recommended_printer_id:
return jsonify({
"success": False,
"message": "Keine verfügbaren Drucker für den gewünschten Zeitraum gefunden."
})
# Drucker-Details abrufen
printer = db_session.query(Printer).filter_by(id=recommended_printer_id).first()
if not printer:
return jsonify({
"success": False,
"message": "Empfohlener Drucker nicht mehr verfügbar."
})
# Zusätzliche Statistiken für die Empfehlung berechnen
last_24h = datetime.now() - timedelta(hours=24)
recent_jobs_count = db_session.query(Job).filter(
Job.printer_id == printer.id,
Job.start_at >= last_24h,
Job.status.in_(["scheduled", "running", "finished"])
).count()
# Verfügbarkeit als Prozentsatz berechnen
total_time_slots = 24 # Stunden pro Tag
availability_percent = max(0, 100 - (recent_jobs_count * 4)) # Grobe Schätzung
# Auslastung berechnen
utilization_percent = min(100, recent_jobs_count * 8) # Grobe Schätzung
# Eignung basierend auf Priorität und Zeitfenster bestimmen
suitability = "Gut"
if priority == "urgent" and ("express" in printer.name.lower() or "schnell" in printer.name.lower()):
suitability = "Perfekt"
elif priority == "high" and recent_jobs_count <= 2:
suitability = "Ausgezeichnet"
elif recent_jobs_count == 0:
suitability = "Optimal"
# Begründung generieren
reason = f"Optimale Verfügbarkeit und geringe Auslastung im gewählten Zeitraum"
job_duration_hours = (end_date - start_date).total_seconds() / 3600
start_hour = start_date.hour
if priority == "urgent":
reason = "Schnellster verfügbarer Drucker für dringende Aufträge"
elif start_hour >= 18 or start_hour <= 6:
reason = "Speziell für Nachtschichten optimiert"
elif job_duration_hours > 8:
reason = "Zuverlässig für lange Druckaufträge"
elif job_duration_hours <= 2:
reason = "Optimal für schnelle Druckaufträge"
return jsonify({
"success": True,
"recommendation": {
"printer_id": printer.id,
"printer_name": f"{printer.name}",
"location": printer.location or "Haupthalle",
"reason": reason,
"availability": f"{availability_percent}%",
"utilization": f"{utilization_percent}%",
"suitability": suitability,
"recent_jobs": recent_jobs_count,
"priority_optimized": priority in ["urgent", "high"] and suitability in ["Perfekt", "Ausgezeichnet"]
}
})
except Exception as e:
logger.error(f"Fehler beim Abrufen der intelligenten Empfehlung: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500

View File

@ -1,855 +0,0 @@
import json
import secrets
import bcrypt
from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort, session, flash
from flask_login import current_user, login_required
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, TextAreaField, IntegerField, SelectField
from wtforms.validators import DataRequired, Email, Optional, NumberRange
from functools import wraps
from sqlalchemy import desc
from sqlalchemy.orm import joinedload
from models import GuestRequest, Job, Printer, User, UserPermission, Notification, get_cached_session
from utils.logging_config import get_logger
guest_blueprint = Blueprint('guest', __name__)
logger = get_logger("guest")
# Flask-WTF Formular für Gastanfragen
class GuestRequestForm(FlaskForm):
name = StringField('Vollständiger Name', validators=[DataRequired()])
email = StringField('E-Mail-Adresse', validators=[DataRequired(), Email()])
printer_id = SelectField('Drucker auswählen', coerce=int, validators=[Optional()])
duration_min = IntegerField('Geschätzte Dauer (Minuten)',
validators=[DataRequired(), NumberRange(min=1, max=1440)],
default=60)
reason = TextAreaField('Projektbeschreibung', validators=[Optional()])
file = FileField('3D-Datei hochladen',
validators=[Optional(), FileAllowed(['stl', 'obj', '3mf', 'amf', 'gcode'],
'3D-Dateien sind erlaubt!')])
# Hilfsfunktionen
def can_approve_jobs(user_id):
"""Prüft, ob ein Benutzer Anfragen genehmigen darf."""
with get_cached_session() as db_session:
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
return False
return permission.can_approve_jobs
def approver_required(f):
"""Decorator zur Prüfung der Genehmigungsberechtigung."""
@wraps(f)
@login_required
def decorated_function(*args, **kwargs):
if not can_approve_jobs(current_user.id):
abort(403, "Keine Berechtigung zum Genehmigen von Anfragen")
return f(*args, **kwargs)
return decorated_function
# Gast-Routen
@guest_blueprint.route('/request', methods=['GET', 'POST'])
def guest_request_form():
"""Formular für Gastanfragen anzeigen und verarbeiten."""
with get_cached_session() as db_session:
# Aktive Drucker für SelectField laden
printers = db_session.query(Printer).filter_by(active=True).all()
# Formular erstellen
form = GuestRequestForm()
# Drucker-Optionen für SelectField setzen
printer_choices = [(0, 'Keinen spezifischen Drucker auswählen')]
printer_choices.extend([(p.id, f"{p.name} ({p.location or 'Kein Standort'})") for p in printers])
form.printer_id.choices = printer_choices
if form.validate_on_submit():
try:
# Daten aus dem Formular extrahieren
name = form.name.data
email = form.email.data
reason = form.reason.data
duration_min = form.duration_min.data
printer_id = form.printer_id.data if form.printer_id.data != 0 else None
# IP-Adresse erfassen
author_ip = request.remote_addr
# Drucker validieren, falls angegeben
if printer_id:
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
if not printer:
flash("Ungültiger Drucker ausgewählt.", "error")
return render_template('guest_request.html', form=form, printers=printers)
# Neue Anfrage erstellen
guest_request = GuestRequest(
name=name,
email=email,
reason=reason,
duration_min=duration_min,
printer_id=printer_id,
author_ip=author_ip
)
db_session.add(guest_request)
db_session.commit()
# Benachrichtigung für Genehmiger erstellen
Notification.create_for_approvers(
notification_type="guest_request",
payload={
"request_id": guest_request.id,
"name": guest_request.name,
"created_at": guest_request.created_at.isoformat(),
"status": guest_request.status
}
)
logger.info(f"Neue Gastanfrage erstellt: ID {guest_request.id}, Name: {name}")
flash("Ihr Antrag wurde erfolgreich eingereicht!", "success")
# Weiterleitung zur Status-Seite
return redirect(url_for('guest.guest_request_status', request_id=guest_request.id))
except Exception as e:
logger.error(f"Fehler beim Erstellen der Gastanfrage: {str(e)}")
flash("Fehler beim Verarbeiten Ihres Antrags. Bitte versuchen Sie es erneut.", "error")
# Drucker-Liste von der Session trennen für Template-Verwendung
db_session.expunge_all()
return render_template('guest_request.html', form=form, printers=printers)
@guest_blueprint.route('/start-job', methods=['GET'])
def guest_start_job_form():
"""Code-Eingabe-Formular für Gäste anzeigen."""
return render_template('guest_start_job.html')
@guest_blueprint.route('/job/<int:job_id>/status', methods=['GET'])
def guest_job_status(job_id):
"""Job-Status-Seite für Gäste anzeigen."""
with get_cached_session() as db_session:
# Job mit eager loading des printer-Relationships laden
job = db_session.query(Job).options(
joinedload(Job.printer),
joinedload(Job.user)
).filter_by(id=job_id).first()
if not job:
abort(404, "Job nicht gefunden")
# Zugehörige Gastanfrage finden
guest_request = db_session.query(GuestRequest).filter_by(job_id=job_id).first()
# Objekte explizit von der Session trennen, um sie außerhalb verwenden zu können
db_session.expunge(job)
if guest_request:
db_session.expunge(guest_request)
return render_template('guest_job_status.html',
job=job,
guest_request=guest_request)
@guest_blueprint.route('/requests/overview', methods=['GET'])
def guest_requests_overview():
"""Öffentliche Übersicht aller Druckanträge mit zensierten persönlichen Daten."""
try:
with get_cached_session() as db_session:
# Alle Gastanfragen mit eager loading des printer-Relationships laden
guest_requests = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer)
).order_by(desc(GuestRequest.created_at)).all()
# Daten für Gäste aufbereiten (persönliche Daten zensieren)
public_requests = []
for req in guest_requests:
# Name zensieren: Nur ersten Buchstaben und letzten Buchstaben anzeigen
censored_name = "***"
if req.name and len(req.name) > 0:
if len(req.name) == 1:
censored_name = req.name[0] + "***"
elif len(req.name) == 2:
censored_name = req.name[0] + "***" + req.name[-1]
else:
censored_name = req.name[0] + "***" + req.name[-1]
# E-Mail zensieren
censored_email = "***@***.***"
if req.email and "@" in req.email:
email_parts = req.email.split("@")
if len(email_parts[0]) > 2:
censored_email = email_parts[0][:2] + "***@" + email_parts[1]
else:
censored_email = "***@" + email_parts[1]
# Grund zensieren (nur erste 20 Zeichen anzeigen)
censored_reason = "***"
if req.reason and len(req.reason) > 20:
censored_reason = req.reason[:20] + "..."
elif req.reason:
censored_reason = req.reason
public_requests.append({
"id": req.id,
"name": censored_name,
"email": censored_email,
"reason": censored_reason,
"duration_min": req.duration_min,
"created_at": req.created_at,
"status": req.status,
"printer": req.printer.to_dict() if req.printer else None
})
# Objekte explizit von der Session trennen
db_session.expunge_all()
return render_template('guest_requests_overview.html', requests=public_requests)
except Exception as e:
logger.error(f"Fehler beim Laden der öffentlichen Gastanfragen: {str(e)}")
return render_template('guest_requests_overview.html', requests=[], error="Fehler beim Laden der Anfragen")
@guest_blueprint.route('/request/<int:request_id>', methods=['GET'])
def guest_request_status(request_id):
"""Status einer Gastanfrage anzeigen."""
with get_cached_session() as db_session:
# Guest Request mit eager loading des printer-Relationships laden
guest_request = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer)
).filter_by(id=request_id).first()
if not guest_request:
abort(404, "Anfrage nicht gefunden")
# OTP-Code nur anzeigen, wenn Anfrage genehmigt wurde
otp_code = None
show_start_link = False
if guest_request.status == "approved":
if not guest_request.otp_code:
# OTP generieren falls noch nicht vorhanden
otp_code = guest_request.generate_otp()
db_session.commit()
else:
# OTP existiert bereits - prüfen ob noch nicht verwendet
show_start_link = guest_request.otp_used_at is None
# Zugehörigen Job laden, falls vorhanden
job = None
if guest_request.job_id:
job = db_session.query(Job).filter_by(id=guest_request.job_id).first()
# Objekte explizit von der Session trennen, um sie außerhalb verwenden zu können
db_session.expunge(guest_request)
if job:
db_session.expunge(job)
return render_template('guest_status.html',
request=guest_request,
job=job,
otp_code=otp_code,
show_start_link=show_start_link)
# API-Endpunkte
@guest_blueprint.route('/api/guest/requests', methods=['POST'])
def api_create_guest_request():
"""Neue Gastanfrage erstellen."""
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
# Pflichtfelder prüfen
name = data.get('name')
if not name:
return jsonify({"error": "Name ist erforderlich"}), 400
# Optionale Felder
email = data.get('email')
reason = data.get('reason')
duration_min = data.get('duration_min', 60) # Standard: 1 Stunde
printer_id = data.get('printer_id')
# IP-Adresse erfassen
author_ip = request.remote_addr
try:
with get_cached_session() as db_session:
# Drucker prüfen
if printer_id:
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
if not printer:
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
# Neue Anfrage erstellen
guest_request = GuestRequest(
name=name,
email=email,
reason=reason,
duration_min=duration_min,
printer_id=printer_id,
author_ip=author_ip
)
db_session.add(guest_request)
db_session.commit()
# Benachrichtigung für Genehmiger erstellen
Notification.create_for_approvers(
notification_type="guest_request",
payload={
"request_id": guest_request.id,
"name": guest_request.name,
"created_at": guest_request.created_at.isoformat(),
"status": guest_request.status
}
)
logger.info(f"Neue Gastanfrage erstellt: ID {guest_request.id}, Name: {name}")
return jsonify({
"success": True,
"request_id": guest_request.id,
"status": guest_request.status,
"redirect_url": url_for('guest.guest_request_status', request_id=guest_request.id)
})
except Exception as e:
logger.error(f"Fehler beim Erstellen der Gastanfrage: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/guest/start-job', methods=['POST'])
def api_start_job_with_code():
"""Job mit OTP-Code starten."""
try:
data = request.get_json()
if not data or 'code' not in data:
return jsonify({"error": "Code ist erforderlich"}), 400
code = data['code'].strip().upper()
if len(code) != 6:
return jsonify({"error": "Code muss 6 Zeichen lang sein"}), 400
with get_cached_session() as db_session:
# Alle genehmigten Gastanfragen mit OTP-Codes finden
guest_requests = db_session.query(GuestRequest).filter(
GuestRequest.status == "approved",
GuestRequest.otp_code.isnot(None),
GuestRequest.otp_used_at.is_(None) # Noch nicht verwendet
).all()
matching_request = None
for req in guest_requests:
# Code validieren
if req.verify_otp(code):
matching_request = req
break
if not matching_request:
return jsonify({
"success": False,
"error": "Ungültiger oder bereits verwendeter Code"
}), 400
# Prüfen ob zugehöriger Job existiert
if not matching_request.job_id:
return jsonify({
"success": False,
"error": "Kein zugehöriger Job gefunden"
}), 400
job = db_session.query(Job).options(
joinedload(Job.printer)
).filter_by(id=matching_request.job_id).first()
if not job:
return jsonify({
"success": False,
"error": "Job nicht gefunden"
}), 400
# Prüfen ob Job noch startbar ist
if job.status not in ["scheduled", "waiting_for_printer"]:
return jsonify({
"success": False,
"error": f"Job kann im Status '{job.status}' nicht gestartet werden"
}), 400
# Job starten
now = datetime.now()
job.status = "running"
job.start_at = now
job.end_at = now + timedelta(minutes=matching_request.duration_min)
job.actual_start_time = now
# OTP als verwendet markieren
matching_request.otp_used_at = now
# Drucker einschalten (falls implementiert)
if job.printer and job.printer.plug_ip:
try:
from utils.job_scheduler import toggle_plug
toggle_plug(job.printer_id, True)
except Exception as e:
logger.warning(f"Fehler beim Einschalten des Druckers: {str(e)}")
db_session.commit()
logger.info(f"Job {job.id} mit OTP-Code gestartet für Gastanfrage {matching_request.id}")
return jsonify({
"success": True,
"job_id": job.id,
"job_name": job.name,
"start_time": job.start_at.strftime("%H:%M"),
"end_time": job.end_at.strftime("%H:%M"),
"duration_minutes": matching_request.duration_min,
"printer_name": job.printer.name if job.printer else "Unbekannt",
"message": f"Job '{job.name}' erfolgreich gestartet"
})
except Exception as e:
logger.error(f"Fehler beim Starten des Jobs mit Code: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler beim Starten des Jobs"
}), 500
@guest_blueprint.route('/api/guest/requests/<int:request_id>', methods=['GET'])
def api_get_guest_request(request_id):
"""Status einer Gastanfrage abrufen."""
try:
with get_cached_session() as db_session:
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
if not guest_request:
return jsonify({"error": "Anfrage nicht gefunden"}), 404
# OTP wird nie über die API zurückgegeben
response_data = guest_request.to_dict()
response_data.pop("otp_code", None)
return jsonify(response_data)
except Exception as e:
logger.error(f"Fehler beim Abrufen der Gastanfrage: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/guest/job/<int:job_id>/status', methods=['GET'])
def api_get_guest_job_status(job_id):
"""Job-Status für Gäste abrufen."""
try:
with get_cached_session() as db_session:
# Job mit Drucker-Information laden
job = db_session.query(Job).options(
joinedload(Job.printer)
).filter_by(id=job_id).first()
if not job:
return jsonify({"error": "Job nicht gefunden"}), 404
# Zugehörige Gastanfrage prüfen
guest_request = db_session.query(GuestRequest).filter_by(job_id=job_id).first()
if not guest_request:
return jsonify({"error": "Kein Gastjob"}), 403
# Aktuelle Zeit für Berechnungen
now = datetime.now()
# Restzeit berechnen
remaining_minutes = 0
if job.status == "running" and job.end_at:
remaining_seconds = (job.end_at - now).total_seconds()
remaining_minutes = max(0, int(remaining_seconds / 60))
# Fortschritt berechnen
progress_percent = 0
if job.status == "running" and job.start_at and job.end_at:
total_duration = (job.end_at - job.start_at).total_seconds()
elapsed_duration = (now - job.start_at).total_seconds()
progress_percent = min(100, max(0, int((elapsed_duration / total_duration) * 100)))
elif job.status in ["completed", "finished"]:
progress_percent = 100
job_data = {
"id": job.id,
"name": job.name,
"status": job.status,
"start_at": job.start_at.isoformat() if job.start_at else None,
"end_at": job.end_at.isoformat() if job.end_at else None,
"duration_minutes": job.duration_minutes,
"remaining_minutes": remaining_minutes,
"progress_percent": progress_percent,
"printer": {
"id": job.printer.id,
"name": job.printer.name,
"location": job.printer.location
} if job.printer else None,
"guest_request": {
"id": guest_request.id,
"name": guest_request.name,
"created_at": guest_request.created_at.isoformat()
},
"is_active": job.status in ["scheduled", "running"],
"is_completed": job.status in ["completed", "finished"],
"is_failed": job.status in ["failed", "cancelled"]
}
return jsonify({
"success": True,
"job": job_data
})
except Exception as e:
logger.error(f"Fehler beim Abrufen des Job-Status: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/notifications', methods=['GET'])
@login_required
def api_get_notifications():
"""Benachrichtigungen für den aktuellen Benutzer abrufen."""
try:
# Zeitstempel für Filter (nur neue Benachrichtigungen)
since = request.args.get('since')
if since:
try:
since_date = datetime.fromisoformat(since)
except ValueError:
return jsonify({"error": "Ungültiges Datumsformat"}), 400
else:
since_date = None
with get_cached_session() as db_session:
query = db_session.query(Notification).filter_by(
user_id=current_user.id,
read=False
)
if since_date:
query = query.filter(Notification.created_at > since_date)
notifications = query.order_by(desc(Notification.created_at)).all()
return jsonify({
"count": len(notifications),
"notifications": [n.to_dict() for n in notifications]
})
except Exception as e:
logger.error(f"Fehler beim Abrufen der Benachrichtigungen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/notifications/<int:notification_id>/read', methods=['POST'])
@login_required
def api_mark_notification_read(notification_id):
"""Benachrichtigung als gelesen markieren."""
try:
with get_cached_session() as db_session:
notification = db_session.query(Notification).filter_by(
id=notification_id,
user_id=current_user.id
).first()
if not notification:
return jsonify({"error": "Benachrichtigung nicht gefunden"}), 404
notification.read = True
db_session.commit()
return jsonify({"success": True})
except Exception as e:
logger.error(f"Fehler beim Markieren der Benachrichtigung als gelesen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/admin/requests', methods=['GET'])
@approver_required
def api_get_all_requests():
"""Alle Gastanfragen für Admins abrufen."""
try:
# Filter-Parameter
status_filter = request.args.get('status', 'all') # all, pending, approved, denied
limit = int(request.args.get('limit', 50))
offset = int(request.args.get('offset', 0))
with get_cached_session() as db_session:
# Query mit eager loading
query = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer),
joinedload(GuestRequest.job),
joinedload(GuestRequest.processed_by_user)
)
# Status-Filter anwenden
if status_filter != 'all':
query = query.filter(GuestRequest.status == status_filter)
# Sortierung: Pending zuerst, dann nach Erstellungsdatum
query = query.order_by(
desc(GuestRequest.status == 'pending'),
desc(GuestRequest.created_at)
)
# Pagination
total_count = query.count()
requests = query.offset(offset).limit(limit).all()
# Daten für Admin aufbereiten
admin_requests = []
for req in requests:
request_data = req.to_dict()
# Zusätzliche Admin-Informationen
request_data.update({
"can_be_processed": req.status == "pending",
"is_overdue": (
req.status == "approved" and
req.job and
req.job.end_at < datetime.now()
) if req.job else False,
"time_since_creation": (datetime.now() - req.created_at).total_seconds() / 3600 if req.created_at else 0 # Stunden
})
admin_requests.append(request_data)
return jsonify({
"success": True,
"requests": admin_requests,
"pagination": {
"total": total_count,
"limit": limit,
"offset": offset,
"has_more": (offset + limit) < total_count
},
"stats": {
"total": total_count,
"pending": db_session.query(GuestRequest).filter_by(status='pending').count(),
"approved": db_session.query(GuestRequest).filter_by(status='approved').count(),
"denied": db_session.query(GuestRequest).filter_by(status='denied').count()
}
})
except Exception as e:
logger.error(f"Fehler beim Abrufen der Admin-Gastanfragen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/admin/requests/<int:request_id>', methods=['GET'])
@approver_required
def api_get_request_details(request_id):
"""Detaillierte Informationen zu einer Gastanfrage für Admins abrufen."""
try:
with get_cached_session() as db_session:
guest_request = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer),
joinedload(GuestRequest.job),
joinedload(GuestRequest.processed_by_user)
).filter_by(id=request_id).first()
if not guest_request:
return jsonify({"error": "Anfrage nicht gefunden"}), 404
# Vollständige Admin-Informationen
request_data = guest_request.to_dict()
# Verfügbare Drucker für Zuweisung
available_printers = db_session.query(Printer).filter_by(active=True).all()
request_data["available_printers"] = [p.to_dict() for p in available_printers]
# Job-Historie falls vorhanden
if guest_request.job:
job_data = guest_request.job.to_dict()
job_data["is_active"] = guest_request.job.status in ["scheduled", "running"]
job_data["is_overdue"] = guest_request.job.end_at < datetime.now() if guest_request.job.end_at else False
request_data["job_details"] = job_data
return jsonify({
"success": True,
"request": request_data
})
except Exception as e:
logger.error(f"Fehler beim Abrufen der Gastanfrage-Details: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/admin/requests/<int:request_id>/update', methods=['PUT'])
@approver_required
def api_update_request(request_id):
"""Gastanfrage aktualisieren (nur für Admins)."""
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
with get_cached_session() as db_session:
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
if not guest_request:
return jsonify({"error": "Anfrage nicht gefunden"}), 404
# Erlaubte Felder für Updates
allowed_fields = ['printer_id', 'duration_min', 'approval_notes', 'rejection_reason']
changes_made = False
for field in allowed_fields:
if field in data:
if field == 'printer_id' and data[field]:
# Drucker validieren
printer = db_session.query(Printer).filter_by(id=data[field], active=True).first()
if not printer:
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
setattr(guest_request, field, data[field])
changes_made = True
if changes_made:
guest_request.processed_by = current_user.id
guest_request.processed_at = datetime.now()
db_session.commit()
logger.info(f"Gastanfrage {request_id} aktualisiert von Admin {current_user.id}")
return jsonify({
"success": True,
"message": "Anfrage erfolgreich aktualisiert",
"updated_by": current_user.name,
"updated_at": guest_request.processed_at.isoformat()
})
else:
return jsonify({"error": "Keine Änderungen vorgenommen"}), 400
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Gastanfrage: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
# Admin-Routen
@guest_blueprint.route('/admin/requests', methods=['GET'])
@approver_required
def admin_requests_management():
"""Admin-Oberfläche für die Verwaltung von Gastanfragen."""
return render_template('admin_guest_requests.html')
@guest_blueprint.route('/api/requests/<int:request_id>/approve', methods=['POST'])
@approver_required
def api_approve_request(request_id):
"""Gastanfrage genehmigen."""
try:
data = request.get_json() or {}
approval_notes = data.get('notes', '')
printer_id = data.get('printer_id') # Optional: Drucker zuweisen/ändern
with get_cached_session() as db_session:
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
if not guest_request:
return jsonify({"error": "Anfrage nicht gefunden"}), 404
if guest_request.status != "pending":
return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400
# Drucker validieren, falls angegeben
if printer_id:
printer = db_session.query(Printer).filter_by(id=printer_id, active=True).first()
if not printer:
return jsonify({"error": "Ungültiger Drucker ausgewählt"}), 400
guest_request.printer_id = printer_id
# Sicherstellen, dass ein Drucker zugewiesen ist
if not guest_request.printer_id:
return jsonify({"error": "Kein Drucker zugewiesen. Bitte wählen Sie einen Drucker aus."}), 400
# Anfrage genehmigen
guest_request.status = "approved"
guest_request.processed_by = current_user.id
guest_request.processed_at = datetime.now()
guest_request.approval_notes = approval_notes
# OTP generieren
otp_plain = guest_request.generate_otp()
# Zugehörigen Job erstellen
start_time = datetime.now() + timedelta(minutes=5) # Start in 5 Minuten
end_time = start_time + timedelta(minutes=guest_request.duration_min)
# Admin-Benutzer als Eigentümer verwenden
admin_user = db_session.query(User).filter_by(is_admin=True).first()
if not admin_user:
admin_user = current_user # Fallback auf aktuellen Benutzer
job = Job(
name=f"Gastauftrag: {guest_request.name}",
description=guest_request.reason or "Gastauftrag",
user_id=admin_user.id,
printer_id=guest_request.printer_id,
start_at=start_time,
end_at=end_time,
status="scheduled",
duration_minutes=guest_request.duration_min,
owner_id=admin_user.id
)
db_session.add(job)
db_session.flush() # ID generieren
# Job-ID in Gastanfrage speichern
guest_request.job_id = job.id
db_session.commit()
logger.info(f"Gastanfrage {request_id} genehmigt von Admin {current_user.id} ({current_user.username})")
return jsonify({
"success": True,
"status": "approved",
"job_id": job.id,
"otp": otp_plain, # Nur in dieser Antwort wird der OTP-Klartext zurückgegeben
"approved_by": current_user.username,
"approved_at": guest_request.processed_at.isoformat(),
"notes": approval_notes,
"message": f"Anfrage genehmigt. Zugangscode: {otp_plain}"
})
except Exception as e:
logger.error(f"Fehler beim Genehmigen der Gastanfrage: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@guest_blueprint.route('/api/requests/<int:request_id>/deny', methods=['POST'])
@approver_required
def api_deny_request(request_id):
"""Gastanfrage ablehnen."""
try:
data = request.get_json() or {}
rejection_reason = data.get('reason', '')
if not rejection_reason.strip():
return jsonify({"error": "Ablehnungsgrund ist erforderlich"}), 400
with get_cached_session() as db_session:
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
if not guest_request:
return jsonify({"error": "Anfrage nicht gefunden"}), 404
if guest_request.status != "pending":
return jsonify({"error": "Anfrage wurde bereits bearbeitet"}), 400
# Anfrage ablehnen
guest_request.status = "denied"
guest_request.processed_by = current_user.id
guest_request.processed_at = datetime.now()
guest_request.rejection_reason = rejection_reason
db_session.commit()
logger.info(f"Gastanfrage {request_id} abgelehnt von Admin {current_user.id} ({current_user.username}): {rejection_reason}")
return jsonify({
"success": True,
"status": "denied",
"rejected_by": current_user.username,
"rejected_at": guest_request.processed_at.isoformat(),
"reason": rejection_reason,
"message": "Anfrage wurde abgelehnt"
})
except Exception as e:
logger.error(f"Fehler beim Ablehnen der Gastanfrage: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500

View File

@ -1,181 +0,0 @@
"""
Drucker-Blueprint für MYP Platform
Enthält alle Routen und Funktionen zur Druckerverwaltung, Statusüberwachung und Steuerung.
"""
import os
import json
import time
from datetime import datetime, timedelta
from flask import Blueprint, request, jsonify, current_app, abort, Response
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from werkzeug.exceptions import NotFound, BadRequest
from sqlalchemy import func, desc, asc
from sqlalchemy.exc import SQLAlchemyError
from typing import Dict, List, Tuple, Any, Optional
from models import Printer, User, Job, get_db_session
from utils.logging_config import get_logger, measure_execution_time
from utils.permissions import require_permission, Permission, check_permission
from utils.printer_monitor import printer_monitor
# Logger initialisieren
printers_logger = get_logger("printers")
# Blueprint erstellen
printers_blueprint = Blueprint("printers", __name__, url_prefix="/api/printers")
@printers_blueprint.route("/monitor/live-status", methods=["GET"])
@login_required
@measure_execution_time(logger=printers_logger, task_name="API-Live-Drucker-Status-Abfrage")
def get_live_printer_status():
"""
Liefert den aktuellen Live-Status aller Drucker.
Query-Parameter:
- use_cache: ob Cache verwendet werden soll (default: true)
Returns:
JSON mit Live-Status aller Drucker
"""
printers_logger.info(f"🔄 Live-Status-Abfrage von Benutzer {current_user.name} (ID: {current_user.id})")
# Parameter auslesen
use_cache_param = request.args.get("use_cache", "true").lower()
use_cache = use_cache_param == "true"
try:
# Live-Status über den PrinterMonitor abrufen
status_data = printer_monitor.get_live_printer_status(use_session_cache=use_cache)
# Zusammenfassung der Druckerstatus erstellen
summary = printer_monitor.get_printer_summary()
# Antwort mit Status und Zusammenfassung
response = {
"success": True,
"status": status_data,
"summary": summary,
"timestamp": datetime.now().isoformat(),
"cache_used": use_cache
}
printers_logger.info(f"✅ Live-Status-Abfrage erfolgreich: {len(status_data)} Drucker")
return jsonify(response)
except Exception as e:
printers_logger.error(f"❌ Fehler bei Live-Status-Abfrage: {str(e)}")
return jsonify({
"success": False,
"error": "Fehler bei Abfrage des Druckerstatus",
"message": str(e)
}), 500
@printers_blueprint.route("/control/<int:printer_id>/power", methods=["POST"])
@login_required
@require_permission(Permission.CONTROL_PRINTER) # Verwende die bereits vorhandene Berechtigung
@measure_execution_time(logger=printers_logger, task_name="API-Drucker-Stromversorgung-Steuerung")
def control_printer_power(printer_id):
"""
Steuert die Stromversorgung eines Druckers (ein-/ausschalten).
Args:
printer_id: ID des zu steuernden Druckers
JSON-Parameter:
- action: "on" oder "off"
Returns:
JSON mit Ergebnis der Steuerungsaktion
"""
printers_logger.info(f"🔌 Stromsteuerung für Drucker {printer_id} von Benutzer {current_user.name}")
# Parameter validieren
data = request.get_json()
if not data or "action" not in data:
return jsonify({
"success": False,
"error": "Parameter 'action' fehlt"
}), 400
action = data["action"]
if action not in ["on", "off"]:
return jsonify({
"success": False,
"error": "Ungültige Aktion. Erlaubt sind 'on' oder 'off'."
}), 400
try:
# Drucker aus Datenbank holen
db_session = get_db_session()
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
if not printer:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker mit ID {printer_id} nicht gefunden"
}), 404
# Prüfen, ob Drucker eine Steckdose konfiguriert hat
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
db_session.close()
return jsonify({
"success": False,
"error": f"Drucker {printer.name} hat keine Steckdose konfiguriert"
}), 400
# Steckdose steuern
from PyP100 import PyP110
try:
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(printer.plug_ip, printer.plug_username, printer.plug_password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Steckdose ein- oder ausschalten
if action == "on":
p110.turnOn()
success = True
message = "Steckdose erfolgreich eingeschaltet"
printer.status = "starting" # Status aktualisieren
else:
p110.turnOff()
success = True
message = "Steckdose erfolgreich ausgeschaltet"
printer.status = "offline" # Status aktualisieren
# Zeitpunkt der letzten Prüfung aktualisieren
printer.last_checked = datetime.now()
db_session.commit()
# Cache leeren, damit neue Status-Abfragen aktuell sind
printer_monitor.clear_all_caches()
printers_logger.info(f"{action.upper()}: Drucker {printer.name} erfolgreich {message}")
except Exception as e:
printers_logger.error(f"❌ Fehler bei Steckdosensteuerung für {printer.name}: {str(e)}")
db_session.close()
return jsonify({
"success": False,
"error": f"Fehler bei Steckdosensteuerung: {str(e)}"
}), 500
db_session.close()
return jsonify({
"success": True,
"message": message,
"printer_id": printer_id,
"printer_name": printer.name,
"action": action,
"timestamp": datetime.now().isoformat()
})
except Exception as e:
printers_logger.error(f"❌ Allgemeiner Fehler bei Stromsteuerung: {str(e)}")
return jsonify({
"success": False,
"error": f"Allgemeiner Fehler: {str(e)}"
}), 500

View File

@ -1,168 +0,0 @@
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, abort
from flask_login import current_user, login_required
from sqlalchemy.exc import SQLAlchemyError
from functools import wraps
from models import User, UserPermission, get_cached_session
from utils.logging_config import get_logger
users_blueprint = Blueprint('users', __name__)
logger = get_logger("users")
def users_admin_required(f):
"""Decorator zur Prüfung der Admin-Berechtigung für Users Blueprint."""
@wraps(f)
@login_required
def users_decorated_function(*args, **kwargs):
if not current_user.is_admin:
abort(403, "Nur Administratoren haben Zugriff auf diese Seite")
return f(*args, **kwargs)
return users_decorated_function
@users_blueprint.route('/admin/users/<int:user_id>/permissions', methods=['GET'])
@users_admin_required
def admin_user_permissions(user_id):
"""Benutzerberechtigungen anzeigen und bearbeiten."""
with get_cached_session() as db_session:
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen, falls nicht vorhanden
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
db_session.commit()
return render_template('admin_user_permissions.html', user=user, permission=permission)
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['GET'])
@login_required
def api_get_user_permissions(user_id):
"""Benutzerberechtigungen als JSON zurückgeben."""
# Nur Admins oder der Benutzer selbst darf seine Berechtigungen sehen
if not current_user.is_admin and current_user.id != user_id:
return jsonify({"error": "Keine Berechtigung"}), 403
try:
with get_cached_session() as db_session:
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
# Falls keine Berechtigungen existieren, Standard-Werte zurückgeben
return jsonify({
"user_id": user_id,
"can_start_jobs": False,
"needs_approval": True,
"can_approve_jobs": False
})
return jsonify(permission.to_dict())
except Exception as e:
logger.error(f"Fehler beim Abrufen der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@users_blueprint.route('/api/users/<int:user_id>/permissions', methods=['PUT'])
@users_admin_required
def api_update_user_permissions(user_id):
"""Benutzerberechtigungen aktualisieren."""
try:
data = request.get_json()
if not data:
return jsonify({"error": "Keine Daten erhalten"}), 400
with get_cached_session() as db_session:
# Benutzer prüfen
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
return jsonify({"error": "Benutzer nicht gefunden"}), 404
# Berechtigungen laden oder erstellen
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
# Berechtigungen aktualisieren
if 'can_start_jobs' in data:
permission.can_start_jobs = bool(data['can_start_jobs'])
if 'needs_approval' in data:
permission.needs_approval = bool(data['needs_approval'])
if 'can_approve_jobs' in data:
permission.can_approve_jobs = bool(data['can_approve_jobs'])
db_session.commit()
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id}")
return jsonify({
"success": True,
"permissions": permission.to_dict()
})
except SQLAlchemyError as e:
logger.error(f"Datenbankfehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Datenbankfehler beim Verarbeiten der Anfrage"}), 500
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
return jsonify({"error": "Fehler beim Verarbeiten der Anfrage"}), 500
@users_blueprint.route('/admin/users/<int:user_id>/permissions/update', methods=['POST'])
@users_admin_required
def admin_update_user_permissions(user_id):
"""Benutzerberechtigungen über Formular aktualisieren."""
try:
# Formularfelder auslesen
can_start_jobs = request.form.get('can_start_jobs') == 'on'
needs_approval = request.form.get('needs_approval') == 'on'
can_approve_jobs = request.form.get('can_approve_jobs') == 'on'
with get_cached_session() as db_session:
# Benutzer prüfen
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
# Berechtigungen aktualisieren
permission.can_start_jobs = can_start_jobs
permission.needs_approval = needs_approval
permission.can_approve_jobs = can_approve_jobs
db_session.commit()
logger.info(f"Berechtigungen für Benutzer {user_id} aktualisiert durch Admin {current_user.id} (Formular)")
return redirect(url_for('users.admin_user_permissions', user_id=user_id))
except Exception as e:
logger.error(f"Fehler beim Aktualisieren der Benutzerberechtigungen: {str(e)}")
abort(500, "Fehler beim Verarbeiten der Anfrage")
# Erweiterung des bestehenden Benutzer-Bearbeitungsformulars
@users_blueprint.route('/admin/users/<int:user_id>/edit/permissions', methods=['GET'])
@users_admin_required
def admin_edit_user_permissions_section(user_id):
"""Rendert nur den Berechtigungsteil für das Benutzer-Edit-Formular."""
with get_cached_session() as db_session:
user = db_session.query(User).filter_by(id=user_id).first()
if not user:
abort(404, "Benutzer nicht gefunden")
# Berechtigungen laden oder erstellen, falls nicht vorhanden
permission = db_session.query(UserPermission).filter_by(user_id=user_id).first()
if not permission:
permission = UserPermission(user_id=user_id)
db_session.add(permission)
db_session.commit()
return render_template('_user_permissions_form.html', user=user, permission=permission)

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
"""
Configuration Package for MYP Platform
======================================
This package contains all configuration modules for the Mercedes-Benz 3D Printing Platform.
Modules:
- security: Security configuration and middleware
- database: Database configuration and settings
- logging: Logging configuration
- app_config: Main application configuration
"""
__version__ = "2.0.0"
__author__ = "MYP Development Team"
# Import main configuration modules
try:
from .security import SecurityConfig, get_security_headers
from .app_config import Config, DevelopmentConfig, ProductionConfig, TestingConfig
except ImportError as e:
print(f"Warning: Could not import configuration modules: {e}")
# Fallback configurations
SecurityConfig = None
get_security_headers = None
Config = None
# Export main configuration classes
__all__ = [
'SecurityConfig',
'get_security_headers',
'Config',
'DevelopmentConfig',
'ProductionConfig',
'TestingConfig'
]
def get_config(config_name='development'):
"""
Get configuration object based on environment name.
Args:
config_name (str): Configuration environment name
Returns:
Config: Configuration object
"""
configs = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
return configs.get(config_name, DevelopmentConfig)
def validate_config(config_obj):
"""
Validate configuration object.
Args:
config_obj: Configuration object to validate
Returns:
bool: True if valid, False otherwise
"""
required_attrs = ['SECRET_KEY', 'DATABASE_URL']
for attr in required_attrs:
if not hasattr(config_obj, attr):
print(f"Missing required configuration: {attr}")
return False
return True

View File

@ -1,181 +0,0 @@
# -*- coding: utf-8 -*-
"""
Application Configuration Module for MYP Platform
================================================
Flask configuration classes for different environments.
"""
import os
from datetime import timedelta
# Base configuration directory
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', '..'))
class Config:
"""Base configuration class with common settings."""
# Secret key for Flask sessions and CSRF protection
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production-744563017196A'
# Session configuration
PERMANENT_SESSION_LIFETIME = timedelta(hours=24)
SESSION_COOKIE_SECURE = False # Set to True in production with HTTPS
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
# Database configuration
DATABASE_URL = os.environ.get('DATABASE_URL') or f'sqlite:///{os.path.join(PROJECT_ROOT, "data", "myp_platform.db")}'
SQLALCHEMY_DATABASE_URI = DATABASE_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_pre_ping': True,
'pool_recycle': 300,
}
# Upload configuration
UPLOAD_FOLDER = os.path.join(PROJECT_ROOT, 'uploads')
MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB max file size
ALLOWED_EXTENSIONS = {'gcode', 'stl', 'obj', '3mf', 'amf'}
# Security configuration
WTF_CSRF_ENABLED = True
WTF_CSRF_TIME_LIMIT = 3600 # 1 hour
# Mail configuration (optional)
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
# Logging configuration
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
LOG_FILE_MAX_BYTES = 10 * 1024 * 1024 # 10MB
LOG_BACKUP_COUNT = 5
# Application-specific settings
SCHEDULER_ENABLED = os.environ.get('SCHEDULER_ENABLED', 'true').lower() in ['true', 'on', '1']
SCHEDULER_INTERVAL = int(os.environ.get('SCHEDULER_INTERVAL', '60')) # seconds
# SSL/HTTPS configuration
SSL_ENABLED = os.environ.get('SSL_ENABLED', 'false').lower() in ['true', 'on', '1']
SSL_CERT_PATH = os.environ.get('SSL_CERT_PATH')
SSL_KEY_PATH = os.environ.get('SSL_KEY_PATH')
# Network configuration
DEFAULT_PORT = int(os.environ.get('PORT', '5000'))
DEFAULT_HOST = os.environ.get('HOST', '0.0.0.0')
@staticmethod
def init_app(app):
"""Initialize application with this configuration."""
pass
class DevelopmentConfig(Config):
"""Development environment configuration."""
DEBUG = True
TESTING = False
# More verbose logging in development
LOG_LEVEL = 'DEBUG'
# Disable some security features for easier development
SESSION_COOKIE_SECURE = False
WTF_CSRF_ENABLED = False # Disable CSRF for easier API testing
@staticmethod
def init_app(app):
Config.init_app(app)
# Development-specific initialization
import logging
logging.basicConfig(level=logging.DEBUG)
class TestingConfig(Config):
"""Testing environment configuration."""
TESTING = True
DEBUG = True
# Use in-memory database for testing
DATABASE_URL = 'sqlite:///:memory:'
SQLALCHEMY_DATABASE_URI = DATABASE_URL
# Disable CSRF for testing
WTF_CSRF_ENABLED = False
# Shorter session lifetime for testing
PERMANENT_SESSION_LIFETIME = timedelta(minutes=5)
@staticmethod
def init_app(app):
Config.init_app(app)
class ProductionConfig(Config):
"""Production environment configuration."""
DEBUG = False
TESTING = False
# Strict security settings for production
SESSION_COOKIE_SECURE = True # Requires HTTPS
WTF_CSRF_ENABLED = True
# Production logging
LOG_LEVEL = 'WARNING'
# SSL should be enabled in production
SSL_ENABLED = True
@staticmethod
def init_app(app):
Config.init_app(app)
# Production-specific initialization
import logging
from logging.handlers import RotatingFileHandler
# Set up file logging for production
log_dir = os.path.join(os.path.dirname(app.instance_path), 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
file_handler = RotatingFileHandler(
os.path.join(log_dir, 'myp_platform.log'),
maxBytes=Config.LOG_FILE_MAX_BYTES,
backupCount=Config.LOG_BACKUP_COUNT
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.WARNING)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.WARNING)
# Configuration dictionary for easy access
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
def get_config_by_name(config_name):
"""
Get configuration class by name.
Args:
config_name (str): Name of the configuration ('development', 'testing', 'production')
Returns:
Config: Configuration class
"""
return config.get(config_name, config['default'])

View File

@ -1,81 +0,0 @@
"""
Sicherheitskonfiguration für die MYP Platform
"""
# Sicherheits-Headers für HTTP-Responses
SECURITY_HEADERS = {
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-eval' 'unsafe-inline'; "
"script-src-elem 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'; "
"font-src 'self'; "
"img-src 'self' data:; "
"connect-src 'self'; "
"worker-src 'self' blob:; "
"frame-src 'none'; "
"object-src 'none'; "
"base-uri 'self'; "
"form-action 'self'; "
"frame-ancestors 'none';"
),
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()'
}
# Rate Limiting Konfiguration
RATE_LIMITS = {
'default': "200 per day, 50 per hour",
'login': "5 per minute",
'api': "100 per hour",
'admin': "500 per hour"
}
# Session-Sicherheit
SESSION_CONFIG = {
'SESSION_COOKIE_SECURE': False, # Für Offline-Betrieb auf False setzen
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SAMESITE': 'Lax',
'PERMANENT_SESSION_LIFETIME': 3600 # 1 Stunde
}
# CSRF-Schutz
CSRF_CONFIG = {
'CSRF_ENABLED': True,
'CSRF_SESSION_KEY': 'csrf_token',
'CSRF_TIME_LIMIT': 3600
}
class SecurityConfig:
"""Sicherheitskonfiguration für die Anwendung"""
def __init__(self):
self.headers = SECURITY_HEADERS
self.rate_limits = RATE_LIMITS
self.session_config = SESSION_CONFIG
self.csrf_config = CSRF_CONFIG
def get_headers(self):
"""Gibt die Sicherheits-Headers zurück"""
return self.headers
def get_rate_limits(self):
"""Gibt die Rate-Limiting-Konfiguration zurück"""
return self.rate_limits
def get_session_config(self):
"""Gibt die Session-Konfiguration zurück"""
return self.session_config
def get_csrf_config(self):
"""Gibt die CSRF-Konfiguration zurück"""
return self.csrf_config
def get_security_headers():
"""Gibt die Sicherheits-Headers zurück"""
return SECURITY_HEADERS

View File

@ -1,187 +0,0 @@
import os
import json
from datetime import timedelta
def get_env_variable(name: str, default: str = None) -> str:
"""
Holt eine Umgebungsvariable oder gibt den Standardwert zurück.
Args:
name: Name der Umgebungsvariable
default: Standardwert, falls die Variable nicht gesetzt ist
Returns:
str: Wert der Umgebungsvariable oder Standardwert
"""
return os.environ.get(name, default)
# Hardcodierte Konfiguration
SECRET_KEY = "7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F"
# Dynamische Pfade basierend auf dem aktuellen Arbeitsverzeichnis
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # backend/app
PROJECT_ROOT = os.path.dirname(BASE_DIR) # backend
DATABASE_PATH = os.path.join(BASE_DIR, "database", "myp.db")
# ===== SMART PLUG KONFIGURATION =====
# TP-Link Tapo P110 Standardkonfiguration
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196A"
# Automatische Steckdosen-Erkennung aktivieren
TAPO_AUTO_DISCOVERY = True
# Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden)
DEFAULT_TAPO_IPS = [
"192.168.0.103", # Erreichbare Steckdose laut Test
"192.168.0.104", # Erreichbare Steckdose laut Test
"192.168.0.100",
"192.168.0.101",
"192.168.0.102",
"192.168.0.105"
]
# Timeout-Konfiguration für Tapo-Verbindungen
TAPO_TIMEOUT = 10 # Sekunden
TAPO_RETRY_COUNT = 3 # Anzahl Wiederholungsversuche
# Drucker-Konfiguration
PRINTERS = {
"Printer 1": {"ip": "192.168.0.100"},
"Printer 2": {"ip": "192.168.0.101"},
"Printer 3": {"ip": "192.168.0.102"},
"Printer 4": {"ip": "192.168.0.103"},
"Printer 5": {"ip": "192.168.0.104"},
"Printer 6": {"ip": "192.168.0.106"}
}
# Logging-Konfiguration
LOG_DIR = os.path.join(BASE_DIR, "logs")
LOG_SUBDIRS = ["app", "scheduler", "auth", "jobs", "printers", "errors"]
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# Flask-Konfiguration
FLASK_HOST = "0.0.0.0"
FLASK_PORT = 443 # Geändert von 443 auf 8443 (nicht-privilegierter Port)
FLASK_FALLBACK_PORT = 8080 # Geändert von 80 auf 8080 (nicht-privilegierter Port)
FLASK_DEBUG = True
SESSION_LIFETIME = timedelta(hours=2) # Reduziert von 7 Tagen auf 2 Stunden für bessere Sicherheit
# Upload-Konfiguration
UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße
# Umgebungskonfiguration
ENVIRONMENT = get_env_variable("MYP_ENVIRONMENT", "development")
# SSL-Konfiguration
SSL_ENABLED = get_env_variable("MYP_SSL_ENABLED", "True").lower() in ("true", "1", "yes")
SSL_CERT_PATH = os.path.join(BASE_DIR, "certs", "myp.crt")
SSL_KEY_PATH = os.path.join(BASE_DIR, "certs", "myp.key")
SSL_HOSTNAME = get_env_variable("MYP_SSL_HOSTNAME", "localhost")
# Scheduler-Konfiguration
SCHEDULER_INTERVAL = 60 # Sekunden
SCHEDULER_ENABLED = True
# Datenbank-Konfiguration
DB_ENGINE = f"sqlite:///{DATABASE_PATH}"
def get_log_file(category: str) -> str:
"""
Gibt den Pfad zur Log-Datei für eine bestimmte Kategorie zurück.
Args:
category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors)
Returns:
str: Pfad zur Log-Datei
"""
if category not in LOG_SUBDIRS:
category = "app"
return os.path.join(LOG_DIR, category, f"{category}.log")
def ensure_log_directories():
"""Erstellt alle erforderlichen Log-Verzeichnisse."""
os.makedirs(LOG_DIR, exist_ok=True)
for subdir in LOG_SUBDIRS:
os.makedirs(os.path.join(LOG_DIR, subdir), exist_ok=True)
def ensure_database_directory():
"""Erstellt das Datenbank-Verzeichnis."""
db_dir = os.path.dirname(DATABASE_PATH)
if db_dir:
os.makedirs(db_dir, exist_ok=True)
def ensure_ssl_directory():
"""Erstellt das SSL-Verzeichnis, falls es nicht existiert."""
ssl_dir = os.path.dirname(SSL_CERT_PATH)
if ssl_dir and not os.path.exists(ssl_dir):
os.makedirs(ssl_dir, exist_ok=True)
def ensure_upload_directory():
"""Erstellt das Upload-Verzeichnis, falls es nicht existiert."""
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def get_ssl_context():
"""
Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist.
Returns:
tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None
"""
if not SSL_ENABLED:
return None
# Wenn Zertifikate nicht existieren, diese automatisch erstellen
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
ensure_ssl_directory()
# Im Entwicklungsmodus versuchen wir, einfache Zertifikate zu erstellen
if FLASK_DEBUG:
print("SSL-Zertifikate nicht gefunden. Erstelle einfache selbstsignierte Zertifikate...")
try:
# Einfache Zertifikate mit Python erstellen
create_simple_ssl_cert()
# Prüfen, ob die Zertifikate erfolgreich erstellt wurden
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
print("Konnte keine SSL-Zertifikate erstellen.")
return None
except Exception as e:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}")
return None
else:
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
return None
return (SSL_CERT_PATH, SSL_KEY_PATH)
def create_simple_ssl_cert():
"""
Erstellt ein Mercedes-Benz SSL-Zertifikat mit dem neuen SSL-Manager.
"""
try:
# Verwende den neuen SSL-Manager
from utils.ssl_manager import ssl_manager
success = ssl_manager.generate_mercedes_certificate()
if success:
print(f"Mercedes-Benz SSL-Zertifikat erfolgreich erstellt: {SSL_CERT_PATH}")
return True
else:
print("Fehler beim Erstellen des Mercedes-Benz SSL-Zertifikats")
return None
except ImportError as e:
print(f"SSL-Manager nicht verfügbar: {e}")
return None
except Exception as e:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}")
return None

View File

@ -1,116 +0,0 @@
import os
import json
from datetime import timedelta
# Hardcodierte Konfiguration
SECRET_KEY = "7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F"
DATABASE_PATH = "database/myp.db"
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196A"
# Drucker-Konfiguration
PRINTERS = {
"Printer 1": {"ip": "192.168.0.100"},
"Printer 2": {"ip": "192.168.0.101"},
"Printer 3": {"ip": "192.168.0.102"},
"Printer 4": {"ip": "192.168.0.103"},
"Printer 5": {"ip": "192.168.0.104"},
"Printer 6": {"ip": "192.168.0.106"}
}
# Logging-Konfiguration
LOG_DIR = "logs"
LOG_SUBDIRS = ["app", "scheduler", "auth", "jobs", "printers", "errors"]
LOG_LEVEL = "INFO"
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# Flask-Konfiguration
FLASK_HOST = "0.0.0.0"
FLASK_PORT = 443
FLASK_FALLBACK_PORT = 80
FLASK_DEBUG = True
SESSION_LIFETIME = timedelta(days=7)
# SSL-Konfiguration
SSL_ENABLED = True
SSL_CERT_PATH = "instance/ssl/myp.crt"
SSL_KEY_PATH = "instance/ssl/myp.key"
SSL_HOSTNAME = "raspberrypi"
# Scheduler-Konfiguration
SCHEDULER_INTERVAL = 60 # Sekunden
SCHEDULER_ENABLED = True
# Datenbank-Konfiguration
DB_ENGINE = f"sqlite:///{DATABASE_PATH}"
def get_log_file(category: str) -> str:
"""
Gibt den Pfad zur Log-Datei für eine bestimmte Kategorie zurück.
Args:
category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors)
Returns:
str: Pfad zur Log-Datei
"""
if category not in LOG_SUBDIRS:
category = "app"
return os.path.join(LOG_DIR, category, f"{category}.log")
def ensure_log_directories():
"""Erstellt alle erforderlichen Log-Verzeichnisse."""
os.makedirs(LOG_DIR, exist_ok=True)
for subdir in LOG_SUBDIRS:
os.makedirs(os.path.join(LOG_DIR, subdir), exist_ok=True)
def ensure_database_directory():
"""Erstellt das Datenbank-Verzeichnis."""
db_dir = os.path.dirname(DATABASE_PATH)
if db_dir:
os.makedirs(db_dir, exist_ok=True)
def ensure_ssl_directory():
"""Erstellt das SSL-Verzeichnis, falls es nicht existiert."""
ssl_dir = os.path.dirname(SSL_CERT_PATH)
if ssl_dir and not os.path.exists(ssl_dir):
os.makedirs(ssl_dir, exist_ok=True)
def get_ssl_context():
"""
Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist.
Returns:
tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None
"""
if not SSL_ENABLED:
return None
# Wenn Zertifikate nicht existieren, diese automatisch erstellen
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
ensure_ssl_directory()
# Prüfen, ob wir uns im Entwicklungsmodus befinden
if FLASK_DEBUG:
print("SSL-Zertifikate nicht gefunden. Erstelle selbstsignierte Zertifikate...")
# Pfad zum create_ssl_cert.sh-Skript ermitteln
script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"install", "create_ssl_cert.sh")
# Ausführungsrechte setzen
if os.path.exists(script_path):
os.system(f"chmod +x {script_path}")
# Zertifikate erstellen mit spezifischem Hostnamen
os.system(f"{script_path} -c {SSL_CERT_PATH} -k {SSL_KEY_PATH} -h {SSL_HOSTNAME}")
else:
print(f"WARNUNG: SSL-Zertifikat-Generator nicht gefunden: {script_path}")
return None
else:
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
return None
return (SSL_CERT_PATH, SSL_KEY_PATH)

View File

@ -1,2 +0,0 @@
# Database package initialization file
# Makes the directory a proper Python package

View File

@ -1,133 +0,0 @@
import os
import logging
from typing import List, Optional, Any
from datetime import datetime
from sqlalchemy import create_engine, func
from sqlalchemy.orm import sessionmaker, Session, joinedload
from models import User, Printer, Job, Stats, Base
from config.settings import DATABASE_PATH, ensure_database_directory
logger = logging.getLogger(__name__)
class DatabaseManager:
"""Database manager class to handle database operations."""
def __init__(self):
"""Initialize the database manager."""
ensure_database_directory()
self.engine = create_engine(f"sqlite:///{DATABASE_PATH}")
self.Session = sessionmaker(bind=self.engine)
def get_session(self) -> Session:
"""Get a new database session.
Returns:
Session: A new SQLAlchemy session.
"""
return self.Session()
def test_connection(self) -> bool:
"""Test the database connection.
Returns:
bool: True if the connection is successful, False otherwise.
"""
try:
session = self.get_session()
session.execute("SELECT 1")
session.close()
return True
except Exception as e:
logger.error(f"Database connection test failed: {str(e)}")
return False
def get_all_jobs(self) -> List[Job]:
"""Get all jobs with eager loading of relationships.
Returns:
List[Job]: A list of all jobs.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).all()
return jobs
finally:
session.close()
def get_jobs_by_status(self, status: str) -> List[Job]:
"""Get jobs by status with eager loading of relationships.
Args:
status: The job status to filter by.
Returns:
List[Job]: A list of jobs with the specified status.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.status == status).all()
return jobs
finally:
session.close()
def get_job_by_id(self, job_id: int) -> Optional[Job]:
"""Get a job by ID with eager loading of relationships.
Args:
job_id: The job ID to find.
Returns:
Optional[Job]: The job if found, None otherwise.
"""
session = self.get_session()
try:
job = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.id == job_id).first()
return job
finally:
session.close()
def get_available_printers(self) -> List[Printer]:
"""Get all available printers.
Returns:
List[Printer]: A list of available printers.
"""
session = self.get_session()
try:
printers = session.query(Printer).filter(
Printer.active == True,
Printer.status != "busy"
).all()
return printers
finally:
session.close()
def get_jobs_since(self, since_date: datetime) -> List[Job]:
"""Get jobs created since a specific date.
Args:
since_date: The date to filter jobs from.
Returns:
List[Job]: A list of jobs created since the specified date.
"""
session = self.get_session()
try:
jobs = session.query(Job).options(
joinedload(Job.user),
joinedload(Job.printer)
).filter(Job.created_at >= since_date).all()
return jobs
finally:
session.close()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,345 +0,0 @@
# Admin Panel & Einstellungen - Reparaturen und Verbesserungen
## Übersicht der durchgeführten Arbeiten
### 🔧 Reparierte Admin-Panel Funktionen
#### 1. Fehlende API-Endpunkte hinzugefügt
- **`/api/admin/cache/clear`** - System-Cache leeren
- **`/api/admin/system/restart`** - System-Neustart (Development)
- **`/api/admin/printers/update-all`** - Alle Drucker-Status aktualisieren
- **`/api/admin/settings`** (GET/POST) - Admin-Einstellungen verwalten
- **`/api/admin/logs/export`** - System-Logs exportieren
#### 2. JavaScript-Funktionen implementiert
- **`showSystemSettings()`** - System-Einstellungen Modal
- **`saveSystemSettings()`** - Einstellungen speichern
- **`updateAllPrinters()`** - Drucker-Status aktualisieren
- **`restartSystem()`** - System-Neustart
- **`managePrinter()`** - Drucker-Verwaltung
- **`showPrinterSettings()`** - Drucker-Einstellungen
- **`editUser()`** - Benutzer bearbeiten
- **`deleteUser()`** - Benutzer löschen
- **`handleJobAction()`** - Job-Aktionen (cancel, delete, finish)
- **`filterUsers()`** - Benutzer-Suche
- **`filterJobs()`** - Job-Filter
- **`exportData()`** - Daten-Export
- **`loadAnalyticsData()`** - Analytics laden
- **`startLiveAnalytics()`** - Live-Analytics starten
#### 3. UI-Verbesserungen
- **Loading-Overlay** hinzugefügt für bessere UX
- **Moderne Benachrichtigungen** mit Animationen
- **Responsive Modals** für alle Admin-Funktionen
- **Fehlerbehandlung** für alle API-Aufrufe
- **CSRF-Token** Unterstützung für Sicherheit
### ⚙️ Reparierte Einstellungen-Funktionen
#### 1. API-Endpunkte für Benutzereinstellungen
- **`/api/user/settings`** (GET) - Einstellungen abrufen
- **`/user/update-settings`** (POST) - Einstellungen speichern (erweitert)
#### 2. JavaScript-Funktionen implementiert
- **`loadUserSettings()`** - Einstellungen beim Laden abrufen
- **`saveAllSettings()`** - Alle Einstellungen speichern
- **Theme-Switcher** - Vollständig funktional
- **Kontrast-Einstellungen** - Implementiert
- **Benachrichtigungseinstellungen** - Funktional
- **Datenschutz & Sicherheit** - Vollständig implementiert
#### 3. Persistierung
- **Session-basierte Speicherung** als Fallback
- **Datenbank-Integration** vorbereitet
- **LocalStorage** für Theme und Kontrast
- **Automatisches Laden** beim Seitenaufruf
### 🛡️ Sicherheitsverbesserungen
#### 1. CSRF-Schutz
- **CSRF-Token** in allen Templates
- **Token-Validierung** in allen API-Aufrufen
- **Sichere Headers** für AJAX-Requests
#### 2. Admin-Berechtigung
- **`@admin_required`** Decorator für alle Admin-Funktionen
- **Berechtigungsprüfung** in JavaScript
- **Sichere API-Endpunkte** mit Validierung
#### 3. Fehlerbehandlung
- **Try-Catch** Blöcke in allen Funktionen
- **Benutzerfreundliche Fehlermeldungen**
- **Logging** für alle kritischen Operationen
### 📊 Funktionale Verbesserungen
#### 1. Real-Time Updates
- **Live-Statistiken** alle 30 Sekunden
- **System-Status** alle 10 Sekunden
- **Drucker-Status** mit Caching
- **Job-Monitoring** in Echtzeit
#### 2. Performance-Optimierungen
- **Caching-System** für häufige Abfragen
- **Lazy Loading** für große Datensätze
- **Optimierte Datenbankabfragen**
- **Session-basiertes Caching**
#### 3. Benutzerfreundlichkeit
- **Animierte Übergänge** und Effekte
- **Responsive Design** für alle Geräte
- **Intuitive Navigation** und Bedienung
- **Sofortiges Feedback** bei Aktionen
## 🧪 Getestete Funktionen
### Admin Panel
**System-Cache leeren** - Funktional
**Datenbank optimieren** - Funktional
**Backup erstellen** - Funktional
**System-Einstellungen** - Modal funktional
**Drucker aktualisieren** - Funktional
**System-Neustart** - Funktional (Development)
**Benutzer-Verwaltung** - CRUD-Operationen
**Drucker-Verwaltung** - Vollständig funktional
**Job-Verwaltung** - Alle Aktionen verfügbar
**Live-Analytics** - Real-time Updates
**Log-Export** - ZIP-Download funktional
### Einstellungen
**Theme-Switcher** - Light/Dark/System
**Kontrast-Einstellungen** - Normal/Hoch
**Benachrichtigungen** - Alle Optionen
**Datenschutz & Sicherheit** - Vollständig
**Automatisches Laden** - Beim Seitenaufruf
**Persistierung** - Session & LocalStorage
## 🔄 Nächste Schritte
### Empfohlene Verbesserungen
1. **Datenbank-Schema erweitern** um `settings` Spalte in User-Tabelle
2. **WebSocket-Integration** für noch bessere Real-time Updates
3. **Erweiterte Analytics** mit Charts und Grafiken
4. **Backup-Scheduling** für automatische Backups
5. **Erweiterte Benutzerrollen** und Berechtigungen
### Wartung
- **Regelmäßige Cache-Bereinigung** implementiert
- **Automatische Datenbank-Optimierung** alle 5 Minuten
- **Log-Rotation** für bessere Performance
- **Session-Management** optimiert
## 📝 Technische Details
### Verwendete Technologien
- **Backend**: Flask, SQLAlchemy, SQLite mit WAL-Modus
- **Frontend**: Vanilla JavaScript, Tailwind CSS
- **Sicherheit**: CSRF-Token, Admin-Decorators
- **Performance**: Caching, Lazy Loading, Optimierte Queries
### Architektur-Verbesserungen
- **Modulare JavaScript-Struktur** für bessere Wartbarkeit
- **Einheitliche API-Responses** mit Erfolgs-/Fehler-Handling
- **Konsistente Fehlerbehandlung** in allen Komponenten
- **Responsive Design-Patterns** für alle Bildschirmgrößen
---
**Status**: ✅ **VOLLSTÄNDIG FUNKTIONAL**
**Letzte Aktualisierung**: 27.05.2025
**Getestet auf**: Windows 10, Python 3.x, Flask 2.x
# Admin Panel Features Dokumentation
## Neue Features - Gastanfragen-Verwaltung
**Datum:** 2025-05-29 12:20:00
**Feature:** Vollständige Administrator-Oberfläche für Gastanfragen mit Genehmigung/Ablehnung und Begründungen
### Implementierte Features
### 1. Erweiterte Datenbank-Struktur ✅
**Neue Felder in `guest_requests` Tabelle:**
- `processed_by` (INTEGER) - ID des Admins der die Anfrage bearbeitet hat
- `processed_at` (DATETIME) - Zeitpunkt der Bearbeitung
- `approval_notes` (TEXT) - Notizen bei Genehmigung
- `rejection_reason` (TEXT) - Grund bei Ablehnung
**Migration durchgeführt:** Alle neuen Felder erfolgreich hinzugefügt
### 2. Erweiterte API-Endpoints ✅
#### Admin-Verwaltung:
- `GET /api/admin/requests` - Alle Gastanfragen mit Filterung und Pagination
- `GET /api/admin/requests/<id>` - Detaillierte Anfrage-Informationen
- `PUT /api/admin/requests/<id>/update` - Anfrage aktualisieren
#### Erweiterte Genehmigung/Ablehnung:
- `POST /api/requests/<id>/approve` - Mit Begründungen und Drucker-Zuweisung
- `POST /api/requests/<id>/deny` - Mit verpflichtender Ablehnungsbegründung
### 3. Admin-Oberfläche ✅
**Route:** `/admin/requests`
**Template:** `admin_guest_requests.html`
**Features:**
- ✅ **Übersichtliche Darstellung** aller Gastanfragen
- ✅ **Echtzeit-Statistiken** (Gesamt, Wartend, Genehmigt, Abgelehnt)
- ✅ **Filter-System** nach Status (Alle, Wartend, Genehmigt, Abgelehnt)
- ✅ **Such-Funktion** nach Name, E-Mail, Begründung
- ✅ **Pagination** für große Anzahl von Anfragen
- ✅ **Dringlichkeits-Kennzeichnung** für Anfragen > 24h alt
- ✅ **Drucker-Zuweisung** bei Genehmigung
- ✅ **Verpflichtende Begründung** bei Ablehnung
- ✅ **Detail-Modal** mit vollständigen Informationen
- ✅ **Aktions-Tracking** (wer hat wann bearbeitet)
### 4. Benutzerfreundlichkeit ✅
#### Design:
- **Moderne UI** mit Tailwind CSS
- **Responsive Design** für Desktop und Mobile
- **Intuitive Icons** und Status-Badges
- **Color-Coding** für verschiedene Status
#### Funktionalität:
- **Ein-Klick-Aktionen** für Genehmigung/Ablehnung
- **Modale Dialoge** für detaillierte Bearbeitung
- **Echtzeit-Updates** nach Aktionen
- **Fehlerbehandlung** mit benutzerfreundlichen Meldungen
### 5. Admin-Workflow ✅
#### Genehmigungsworkflow:
1. **Drucker auswählen** (optional, falls nicht bereits zugewiesen)
2. **Genehmigungsnotizen** hinzufügen (optional)
3. **Automatische Job-Erstellung** mit OTP-Generierung
4. **Admin-Tracking** wird gespeichert
#### Ablehnungsworkflow:
1. **Verpflichtende Begründung** eingeben
2. **Detaillierter Ablehnungsgrund** für Transparenz
3. **Admin-Tracking** wird gespeichert
4. **Keine Job-Erstellung**
### 6. Sicherheit und Berechtigungen ✅
- **@approver_required** Decorator für alle Admin-Endpunkte
- **UserPermission.can_approve_jobs** Berechtigung erforderlich
- **Admin-Rolle** oder spezielle Genehmigungsberechtigung
- **Audit-Trail** durch processed_by und processed_at
### 7. API-Verbesserungen ✅
#### Erweiterte Approve-API:
```json
POST /api/requests/<id>/approve
{
"printer_id": 123,
"notes": "Zusätzliche Anweisungen..."
}
Response:
{
"success": true,
"status": "approved",
"job_id": 456,
"otp": "ABC123",
"approved_by": "Admin Name",
"approved_at": "2025-05-29T12:20:00",
"notes": "Zusätzliche Anweisungen..."
}
```
#### Erweiterte Deny-API:
```json
POST /api/requests/<id>/deny
{
"reason": "Detaillierte Begründung für Ablehnung..."
}
Response:
{
"success": true,
"status": "denied",
"rejected_by": "Admin Name",
"rejected_at": "2025-05-29T12:20:00",
"reason": "Detaillierte Begründung..."
}
```
### 8. Datenintegrität ✅
#### Model-Erweiterungen:
- **to_dict()** Methode erweitert um neue Felder
- **Relationship** zu processed_by_user für Admin-Info
- **Eager Loading** für bessere Performance
- **Cascade Analysis** für alle betroffenen Komponenten
### 9. Performance-Optimierungen ✅
- **Eager Loading** für Printer, Job und Admin-User
- **Pagination** für große Datenmengen
- **Caching** der Drucker-Liste
- **Debounced Search** für bessere UX
- **AJAX-Updates** ohne Seitenreload
### 10. Logging und Audit ✅
```python
# Genehmigung
logger.info(f"Gastanfrage {request_id} genehmigt von Admin {current_user.id} ({current_user.name})")
# Ablehnung
logger.info(f"Gastanfrage {request_id} abgelehnt von Admin {current_user.id} ({current_user.name}): {rejection_reason}")
```
## Verwendung für Administratoren
### 1. Zugriff
**URL:** `/admin/requests`
**Berechtigung:** Admin-Rolle oder `can_approve_jobs` Permission
### 2. Täglicher Workflow
1. **Wartende Anfragen** prüfen (Standardfilter)
2. **Dringende Anfragen** zuerst bearbeiten (>24h alt)
3. **Details ansehen** für vollständige Informationen
4. **Genehmigen** mit Drucker-Zuweisung und Notizen
5. **Ablehnen** mit detaillierter Begründung
### 3. Überwachung
- **Echtzeit-Statistiken** im Header
- **Status-Tracking** für alle Anfragen
- **Admin-Historie** für Accountability
- **Job-Überwachung** für genehmigte Anfragen
## Status
**VOLLSTÄNDIG IMPLEMENTIERT** - 2025-05-29 12:20:00
1. ✅ Datenbank-Schema erweitert und migriert
2. ✅ API-Endpoints implementiert und getestet
3. ✅ Admin-Oberfläche erstellt und funktional
4. ✅ Berechtigungen und Sicherheit implementiert
5. ✅ Workflow und Benutzerfreundlichkeit optimiert
6. ✅ Logging und Audit-Trail eingerichtet
**Die vollständige Administrator-Funktionalität für Gastanfragen-Verwaltung ist einsatzbereit.**

View File

@ -1 +0,0 @@

View File

@ -1,424 +0,0 @@
# Automatischer Start ohne Benutzeranmeldung
## Übersicht
Das MYP Druckerverwaltungssystem ist so konfiguriert, dass der Raspberry Pi automatisch ohne Benutzeranmeldung startet und direkt in den Kiosk-Modus wechselt. Diese Dokumentation beschreibt die implementierten Mechanismen und Troubleshooting-Optionen.
## Implementierte Auto-Login-Mechanismen
### 1. LightDM Display Manager
**Konfigurationsdatei:** `/etc/lightdm/lightdm.conf`
```ini
[Seat:*]
# Automatischer Login für Kiosk-Benutzer
autologin-user=kiosk
autologin-user-timeout=0
autologin-session=openbox
user-session=openbox
session-wrapper=/etc/X11/Xsession
greeter-session=lightdm-gtk-greeter
allow-guest=false
# Kein Benutzer-Wechsel möglich
greeter-hide-users=true
greeter-show-manual-login=false
# Automatischer Start ohne Verzögerung
autologin-in-background=false
# Session-Setup
session-setup-script=/usr/share/lightdm/setup-kiosk-session.sh
[SeatDefaults]
# Zusätzliche Sicherheitseinstellungen
autologin-user=kiosk
autologin-user-timeout=0
autologin-session=openbox
greeter-hide-users=true
greeter-show-manual-login=false
allow-user-switching=false
```
**Systemd-Override:** `/etc/systemd/system/lightdm.service.d/autologin-override.conf`
```ini
[Unit]
After=multi-user.target network.target myp-druckerverwaltung.service
Wants=myp-druckerverwaltung.service
[Service]
# Automatischer Restart bei Fehlern
Restart=always
RestartSec=3
# Umgebungsvariablen für Kiosk
Environment=DISPLAY=:0
Environment=KIOSK_MODE=1
# Verzögerung für Backend-Start
ExecStartPre=/bin/bash -c 'for i in {1..30}; do if curl -s http://localhost:5000 >/dev/null 2>&1; then break; fi; sleep 2; done'
```
### 2. Getty Auto-Login (Fallback)
**Konfigurationsdatei:** `/etc/systemd/system/getty@tty1.service.d/autologin.conf`
```ini
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin kiosk --noclear %I $TERM
Type=simple
Restart=always
RestartSec=3
```
### 3. Benutzer-Profile Auto-Start
**Bashrc-Autostart:** `/home/kiosk/.bashrc`
```bash
# ===== VERSTÄRKTER KIOSK AUTOSTART =====
if [ -z "$SSH_CLIENT" ] && [ -z "$SSH_TTY" ] && [ -z "$KIOSK_STARTED" ]; then
export KIOSK_STARTED=1
# Logge Autostart-Versuch
echo "$(date): Bashrc Autostart-Versuch auf $(tty)" >> /var/log/kiosk-autostart.log
# Prüfe ob wir auf tty1 sind und X noch nicht läuft
if [ "$(tty)" = "/dev/tty1" ] && [ -z "$DISPLAY" ]; then
echo "$(date): Starte X-Session automatisch via bashrc" >> /var/log/kiosk-autostart.log
exec startx
fi
# Falls X läuft aber Kiosk-App nicht, starte sie
if [ -n "$DISPLAY" ] && ! pgrep -f "chromium.*kiosk" > /dev/null; then
echo "$(date): Starte Kiosk-Anwendung via bashrc" >> /var/log/kiosk-autostart.log
exec $HOME/start-kiosk.sh
fi
fi
```
**Profile-Autostart:** `/home/kiosk/.profile`
```bash
# ===== VERSTÄRKTER KIOSK AUTOSTART (PROFILE) =====
if [ -z "$SSH_CLIENT" ] && [ -z "$SSH_TTY" ] && [ -z "$KIOSK_STARTED" ]; then
export KIOSK_STARTED=1
# Logge Profile-Autostart
echo "$(date): Profile Autostart-Versuch auf $(tty)" >> /var/log/kiosk-autostart.log
# Starte X-Session falls nicht vorhanden
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
echo "$(date): Starte X-Session via profile" >> /var/log/kiosk-autostart.log
exec startx
fi
fi
```
### 4. Desktop Autostart
**XDG Autostart:** `/home/kiosk/.config/autostart/kiosk-app.desktop`
```ini
[Desktop Entry]
Type=Application
Name=MYP Kiosk Application
Comment=Startet die MYP Kiosk-Anwendung automatisch
Exec=/home/kiosk/start-kiosk.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
StartupNotify=false
Terminal=false
```
### 5. Systemd-Watchdog Services
**Enhanced Watchdog:** `/etc/systemd/system/kiosk-watchdog-enhanced.service`
Überwacht kontinuierlich:
- Backend-Service Status
- Backend-Erreichbarkeit (HTTP)
- LightDM Status
- Kiosk-Benutzer Session
- Chromium Kiosk-Prozess
- X-Server Status
### 6. Cron-Überwachung
**Cron-Watchdog:** `/etc/cron.d/kiosk-watchdog-enhanced`
```bash
# Verstärkter Kiosk-Watchdog: Prüft alle 2 Minuten
*/2 * * * * kiosk /bin/bash -c 'if ! pgrep -f "chromium.*kiosk" > /dev/null; then echo "$(date): Cron-Watchdog startet Kiosk neu" >> /var/log/kiosk-cron-watchdog.log; DISPLAY=:0 $HOME/start-kiosk.sh & fi'
# System-Watchdog: Prüft Services alle 5 Minuten
*/5 * * * * root /bin/bash -c 'if ! systemctl is-active --quiet lightdm; then echo "$(date): Cron startet LightDM neu" >> /var/log/system-cron-watchdog.log; systemctl start lightdm; fi'
```
### 7. RC.Local Fallback
**Boot-Fallback:** `/etc/rc.local`
```bash
#!/bin/bash
# Verstärkter rc.local - Kiosk-Fallback
# Logge Start
echo "$(date): rc.local gestartet" >> /var/log/kiosk-fallback.log
# Warte auf System-Initialisierung
sleep 20
# Starte Backend-Service falls nicht läuft
if ! systemctl is-active --quiet myp-druckerverwaltung; then
echo "$(date): Starte Backend-Service" >> /var/log/kiosk-fallback.log
systemctl start myp-druckerverwaltung
sleep 10
fi
# Warte auf Backend-Verfügbarkeit
for i in {1..30}; do
if curl -s http://localhost:5000 >/dev/null 2>&1; then
echo "$(date): Backend verfügbar nach $i Versuchen" >> /var/log/kiosk-fallback.log
break
fi
sleep 2
done
# Starte LightDM falls nicht läuft
if ! systemctl is-active --quiet lightdm; then
echo "$(date): Starte LightDM" >> /var/log/kiosk-fallback.log
systemctl start lightdm
sleep 5
fi
# Prüfe nach 30 Sekunden ob Kiosk-Benutzer angemeldet ist
sleep 30
if ! pgrep -u kiosk > /dev/null; then
echo "$(date): Kiosk-Benutzer nicht angemeldet - starte LightDM neu" >> /var/log/kiosk-fallback.log
systemctl restart lightdm
fi
echo "$(date): rc.local Kiosk-Fallback abgeschlossen" >> /var/log/kiosk-fallback.log
exit 0
```
## Boot-Optimierungen
### Raspberry Pi Boot-Konfiguration
**Boot-Config:** `/boot/config.txt`
```ini
# GPU Memory für bessere Performance
gpu_mem=128
# Disable Boot-Splash für schnelleren Start
disable_splash=1
# Boot-Delay reduzieren
boot_delay=0
# HDMI-Hotplug für bessere Display-Erkennung
hdmi_force_hotplug=1
# Disable Rainbow-Splash
disable_overscan=1
```
**Kernel-Parameter:** `/boot/cmdline.txt`
```
# Zusätzliche Parameter für schnelleren Boot
quiet loglevel=3 logo.nologo vt.global_cursor_default=0
```
### Systemd-Konfiguration
**Standard-Target:** `graphical.target`
```bash
systemctl set-default graphical.target
```
**Logind-Konfiguration:** `/etc/systemd/logind.conf.d/kiosk.conf`
```ini
[Login]
# Verhindere dass System bei Inaktivität heruntergefahren wird
IdleAction=ignore
IdleActionSec=infinity
# Verhindere Suspend/Hibernate
HandlePowerKey=ignore
HandleSuspendKey=ignore
HandleHibernateKey=ignore
HandleLidSwitch=ignore
# Session-Einstellungen für Kiosk
KillUserProcesses=no
UserStopDelaySec=10
# Automatic VT allocation
ReserveVT=1
```
## Troubleshooting
### 1. System startet nicht automatisch
**Diagnose:**
```bash
# Prüfe systemd default target
systemctl get-default
# Prüfe LightDM Status
systemctl status lightdm
# Prüfe Getty Service
systemctl status getty@tty1
```
**Lösung:**
```bash
# Setze graphical target
sudo systemctl set-default graphical.target
# Aktiviere Services
sudo systemctl enable lightdm
sudo systemctl enable getty@tty1
```
### 2. Kiosk-Benutzer meldet sich nicht automatisch an
**Diagnose:**
```bash
# Prüfe LightDM Konfiguration
cat /etc/lightdm/lightdm.conf | grep autologin
# Prüfe PAM Konfiguration
cat /etc/pam.d/lightdm-autologin
# Prüfe Benutzer-Sessions
who
```
**Lösung:**
```bash
# LightDM neu konfigurieren
sudo dpkg-reconfigure lightdm
# Service neustarten
sudo systemctl restart lightdm
```
### 3. X-Session startet nicht
**Diagnose:**
```bash
# Prüfe X-Server Logs
cat /var/log/Xorg.0.log
# Prüfe Session-Logs
cat /var/log/kiosk-session.log
# Prüfe Autostart-Logs
cat /var/log/kiosk-autostart.log
```
**Lösung:**
```bash
# X-Server manuell starten
sudo -u kiosk DISPLAY=:0 startx
# Openbox neu installieren
sudo apt-get install --reinstall openbox
```
### 4. Kiosk-Anwendung startet nicht
**Diagnose:**
```bash
# Prüfe Backend-Service
systemctl status myp-druckerverwaltung
# Prüfe Backend-Erreichbarkeit
curl -s http://localhost:5000
# Prüfe Chromium-Prozesse
pgrep -f chromium
```
**Lösung:**
```bash
# Backend neustarten
sudo systemctl restart myp-druckerverwaltung
# Kiosk-Anwendung manuell starten
sudo -u kiosk DISPLAY=:0 /home/kiosk/start-kiosk.sh
```
## Wartungskommandos
### System-Status prüfen
```bash
sudo myp-maintenance status
```
### Services neustarten
```bash
sudo myp-maintenance restart
```
### Logs anzeigen
```bash
sudo myp-maintenance logs
sudo myp-maintenance kiosk-logs
```
### Kiosk-Modus beenden (für Wartung)
```bash
sudo myp-maintenance exit-kiosk
```
### SSH für Remote-Wartung aktivieren
```bash
sudo myp-maintenance enable-ssh
```
## Log-Dateien
### Wichtige Log-Dateien für Diagnose
- `/var/log/kiosk-autostart.log` - Autostart-Versuche
- `/var/log/kiosk-session.log` - X-Session Events
- `/var/log/kiosk-fallback.log` - RC.Local Fallback
- `/var/log/kiosk-watchdog-enhanced.log` - Watchdog-Service
- `/var/log/kiosk-cron-watchdog.log` - Cron-Watchdog
- `/var/log/system-cron-watchdog.log` - System-Cron-Watchdog
- `/var/log/Xorg.0.log` - X-Server Logs
- `journalctl -u lightdm` - LightDM Service Logs
- `journalctl -u myp-druckerverwaltung` - Backend Service Logs
## Optimierung nach Installation
Für bereits installierte Systeme kann die Schnellstart-Optimierung ausgeführt werden:
```bash
sudo ./schnellstart_raspberry_pi.sh
sudo reboot
```
Dieses Skript verstärkt alle Auto-Login-Mechanismen und optimiert die Boot-Performance.
## Sicherheitshinweise
- SSH ist standardmäßig deaktiviert für bessere Sicherheit
- Console-Zugang über Strg+Alt+F1 bis F6 möglich
- Root-Passwort: `744563017196A` (für Notfall-Wartung)
- Kiosk-Benutzer hat keine sudo-Berechtigung
- Automatische Updates sind konfiguriert
## Fazit
Das System ist mit mehrfachen redundanten Mechanismen ausgestattet, um einen zuverlässigen automatischen Start ohne Benutzeranmeldung zu gewährleisten. Bei Problemen stehen umfangreiche Diagnose- und Wartungstools zur Verfügung.

View File

@ -1,114 +0,0 @@
# Chart-Integration mit ApexCharts
Dieses Dokument beschreibt die Integration von ApexCharts in die MYP-Plattform zur Visualisierung von Daten.
## Übersicht
Die MYP-Plattform nutzt [ApexCharts](https://apexcharts.com/) für die Darstellung von Diagrammen und Visualisierungen. ApexCharts ist eine moderne JavaScript-Bibliothek zur Erstellung interaktiver Diagramme mit einem einfachen API und reaktionsfähigem Design.
## Dateien und Struktur
Die Chart-Integration besteht aus folgenden Komponenten:
1. **ApexCharts-Bibliothek**: `static/js/charts/apexcharts.min.js`
2. **Konfigurationsdatei**: `static/js/charts/chart-config.js`
3. **Renderer**: `static/js/charts/chart-renderer.js`
4. **Adapter**: `static/js/charts/chart-adapter.js`
### Funktionsweise
1. Die **Chart-Konfiguration** definiert Standardstile, Farben und Optionen für alle Diagrammtypen.
2. Der **Chart-Renderer** verwaltet die Erstellung, Aktualisierung und Zerstörung von Diagrammen.
3. Der **Chart-Adapter** verbindet die bestehende `renderChart`-Funktion mit der neuen ApexCharts-Implementierung.
## Verwendung in Templates
Um ein Diagramm in einer Template-Datei anzuzeigen:
```html
<div id="mein-chart" class="h-64" data-api-endpoint="/api/pfad/zu/daten" data-chart data-chart-type="line"></div>
```
### Verfügbare Attribute:
- `data-chart`: Markiert das Element als Diagramm-Container
- `data-api-endpoint`: Der API-Endpunkt, von dem Daten geladen werden
- `data-chart-type`: Der Diagrammtyp (optional, wird sonst automatisch bestimmt)
## Unterstützte Diagrammtypen
Die folgenden Diagrammtypen werden unterstützt:
- `line`: Liniendiagramm
- `area`: Flächendiagramm
- `bar`: Balkendiagramm
- `pie`: Kreisdiagramm
- `donut`: Ringdiagramm
- `radial`: Radialdiagramm
## Datenformat
Die API-Endpunkte sollten Daten in einem der folgenden Formate zurückgeben:
### Für Kreisdiagramme (pie/donut):
```json
{
"scheduled": 12,
"active": 5,
"completed": 25,
"cancelled": 3
}
```
### Für Balkendiagramme:
```json
[
{
"printer_name": "Drucker 1",
"job_count": 25,
"print_hours": 124.5
},
{
"printer_name": "Drucker 2",
"job_count": 18,
"print_hours": 85.2
}
]
```
### Für Linien- und Flächendiagramme:
```json
[
{
"date": "2023-01-01",
"value": 42
},
{
"date": "2023-01-02",
"value": 58
}
]
```
## Theme-Unterstützung
Die Diagramme unterstützen automatisch das Light- und Dark-Mode-Theme der MYP-Plattform. Bei einem Themenwechsel werden alle aktiven Diagramme mit den passenden Farben aktualisiert.
## Anpassung
Um die Diagramme anzupassen:
1. **Farben**: Anpassungen in `MYP_CHART_COLORS` in `chart-config.js`
2. **Standardoptionen**: Änderungen in `getBaseChartOptions()` in `chart-config.js`
3. **Spezifische Diagrammtypen**: Anpassungen in den jeweiligen Funktionen (`getLineChartConfig`, `getPieChartConfig`, etc.)
## Erweiterung
Um weitere Diagrammtypen oder Funktionen hinzuzufügen:
1. Fügen Sie eine neue Konfigurationsfunktion in `chart-config.js` hinzu
2. Erweitern Sie die `createChart`-Funktion in `chart-renderer.js`, um den neuen Diagrammtyp zu unterstützen
3. Aktualisieren Sie den `chart-adapter.js`, um die Datentransformation für den neuen Diagrammtyp zu unterstützen

View File

@ -1,595 +0,0 @@
# Häufige Fehler und Lösungen
Dieses Dokument enthält häufig auftretende Fehler und deren Lösungen für das MYP-3D-Druck-Management-System.
## Drucker-Status-Check mit 7-Sekunden-Timeout
### Implementierung (2024-12-19)
**Problem:** Drucker-Status wurde nur basierend auf hardkodierten Konfigurationen bestimmt, ohne echte Netzwerk-Überprüfung.
**Lösung:** Implementierung eines echten Ping-basierten Status-Checks mit 7-Sekunden-Timeout:
#### Backend-Änderungen:
1. **Neue Funktionen in `app.py`:**
- `check_printer_status(ip_address, timeout=7)`: Überprüft einzelnen Drucker via Ping
- `check_multiple_printers_status(printers, timeout=7)`: Parallel-Check mehrerer Drucker
2. **Aktualisierte API-Endpunkte:**
- `/api/printers`: Verwendet jetzt echten Status-Check
- `/api/printers/status`: Spezieller Endpunkt für Status-Überprüfung
3. **Features:**
- 7-Sekunden-Timeout pro Drucker
- Parallel-Ausführung mit ThreadPoolExecutor
- Windows- und Unix-kompatible Ping-Befehle
- Detailliertes Logging der Status-Checks
- Automatische Datenbank-Aktualisierung
#### Frontend-Änderungen:
1. **Erweiterte Drucker-Seite (`templates/printers.html`):**
- Funktionsfähiger "Aktualisieren"-Button
- Loading-States mit Timeout-Information
- Status-Nachrichten mit Erfolgs-/Fehler-Feedback
- Zeitstempel der letzten Überprüfung
2. **Neue JavaScript-Funktionen:**
- `refreshPrinters()`: Vollständiger Status-Check mit UI-Feedback
- `loadPrintersWithStatusCheck()`: Erweiterte Lade-Funktion
- `showStatusMessage()`: Toast-Nachrichten für Benutzer-Feedback
- `formatTime()`: Relative Zeitanzeige für Status-Checks
#### Benutzer-Erfahrung:
- **Vor dem Update:** Drucker-Status basierte nur auf Konfiguration
- **Nach dem Update:**
- Echter Ping-Test mit 7-Sekunden-Timeout
- Visuelles Feedback während der Überprüfung
- Erfolgs-/Fehler-Nachrichten
- Zeitstempel der letzten Überprüfung
- Button-Deaktivierung während des Checks
#### Technische Details:
- **Timeout:** 7 Sekunden pro Drucker (konfigurierbar)
- **Parallel-Verarbeitung:** Bis zu 10 gleichzeitige Checks
- **Plattform-Unterstützung:** Windows (`ping -n 1 -w 7000`) und Unix (`ping -c 1 -W 7`)
- **Fehlerbehandlung:** Graceful Fallback auf "offline" bei Timeouts oder Fehlern
- **Logging:** Detaillierte Protokollierung aller Status-Checks
#### Konfiguration:
```python
# Timeout kann in den API-Aufrufen angepasst werden
status_results = check_multiple_printers_status(printer_data, timeout=7)
```
#### Fehlerbehebung:
1. **Timeout-Probleme:** Timeout kann in der Funktion angepasst werden
2. **Netzwerk-Probleme:** Logs in `logs/printers/printers.log` prüfen
3. **Performance:** Bei vielen Druckern ThreadPool-Größe anpassen
## Job-Scheduler und Steckdosensteuerung
### Steckdose kann nicht eingeschaltet werden
**Problem:**
Log-Eintrag: `Fehler beim Starten von Job X: Steckdose konnte nicht eingeschaltet werden`
**Mögliche Ursachen und Lösungen:**
1. **Netzwerkverbindung zu Steckdose unterbrochen**
- Prüfen Sie, ob die Steckdose mit dem Netzwerk verbunden ist
- Ping-Test: `ping [IP-Adresse der Steckdose]`
- Steckdose neu starten (physischer Reset-Knopf)
2. **Falsche Zugangsdaten für Steckdose**
- Überprüfen Sie in der Datenbank, ob die korrekten Zugangsdaten (Benutzername/Passwort) für die Steckdose hinterlegt sind
- Admin-Bereich → Drucker → Steckdosenkonfiguration bearbeiten
3. **PyP110-Bibliothek veraltet**
- Bibliothek aktualisieren: `pip install PyP100 --upgrade`
### Job wird nicht gestartet oder beendet
**Problem:**
Ein geplanter Job startet nicht oder ein laufender Job wird nicht beendet
**Mögliche Ursachen und Lösungen:**
1. **Scheduler-Thread läuft nicht**
- Prüfen Sie den Status des Schedulers im Admin-Bereich
- Wenn nicht aktiv: Scheduler starten
- Bei wiederholtem Problem: Server neu starten
2. **Datenbankprobleme**
- Überprüfen Sie die Datenbank auf Konsistenz
- Backup einspielen, falls notwendig
### Job-Verlängerung funktioniert nicht
**Problem:**
Nach dem Verlängern eines Jobs wird er trotzdem zum ursprünglichen Zeitpunkt beendet
**Lösung:**
1. Prüfen Sie die Datenbankeinträge, ob die `end_at`-Zeit korrekt aktualisiert wurde
2. Prüfen Sie die Log-Dateien auf Fehler beim Aktualisieren des Jobs
3. Verlängern Sie den Job erneut mit größerem Zeitpuffer
## Sicherheitshinweise
### Sicherheitsmaßnahmen bei Stromausfällen
**Wichtig:**
- Bei Stromausfällen kehren die Steckdosen in ihren Standardzustand zurück (meist AUS)
- Nach Wiederherstellung der Stromversorgung wird der Scheduler automatisch Jobs wieder aufnehmen
- Laufende Jobs werden jedoch **nicht** automatisch fortgesetzt, um Schäden zu vermeiden
- Administrator muss laufende Jobs manuell neu starten
### 5-Minuten Sicherheitspuffer
Das System verwendet einen 5-Minuten Sicherheitspuffer, bevor es einen Job beendet. Dies verhindert, dass ein Druck zu früh beendet wird. Die tatsächliche Ausschaltzeit ist also immer 5 Minuten nach der im System angezeigten Endzeit.
## API-Endpunkte für Fehlerbehebung
| Endpunkt | Beschreibung |
|----------|--------------|
| `GET /api/scheduler/status` | Status des Job-Schedulers abfragen |
| `POST /api/scheduler/start` | Scheduler manuell starten (Admin) |
| `POST /api/scheduler/stop` | Scheduler manuell stoppen (Admin) |
| `GET /api/jobs/active` | Alle aktiven Jobs anzeigen |
| `POST /api/jobs/<id>/extend` | Job-Laufzeit verlängern |
| `POST /api/jobs/<id>/finish` | Job manuell beenden (Admin) |
## Flask-Login Fehler
### AttributeError: 'User' object has no attribute 'is_authenticated'
**Problem:**
```
AttributeError: 'User' object has no attribute 'is_authenticated'
```
**Ursache:** Die User-Klasse erbt nicht von `UserMixin` oder implementiert nicht die erforderlichen Flask-Login Attribute.
**Lösungen:**
1. Stellen Sie sicher, dass die User-Klasse von `UserMixin` erbt:
```python
from flask_login import UserMixin
class User(UserMixin, Base):
# ... Rest der Klasse
```
2. Implementieren Sie die erforderlichen Methoden manuell:
```python
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return self.active
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
```
3. Führen Sie die Datenbankmigration aus:
```bash
python3.11 migrate_db.py
```
### Fehlende username-Spalte
**Problem:** Die Anwendung versucht auf `username` zuzugreifen, aber die Spalte existiert nicht in der Datenbank.
**Lösungen:**
1. Führen Sie das Migrationsskript aus:
```bash
python3.11 migrate_db.py
```
2. Falls die Migration fehlschlägt, initialisieren Sie die Datenbank neu:
```bash
python3.11 init_db.py
```
## Datenbank-Fehler
### SQLite Database is locked
**Problem:**
```
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked
```
**Ursache:** Dieser Fehler tritt auf, wenn mehrere Prozesse gleichzeitig auf die Datenbank zugreifen.
**Lösungen:**
1. Stellen Sie sicher, dass Sie alle Datenbankverbindungen mit `db_session.close()` schließen
2. Verwenden Sie `with get_db_session() as session:` um Sessions automatisch zu schließen
3. Prüfen Sie, ob der Scheduler und die Hauptanwendung gleichzeitig auf die Datenbank zugreifen
### Detached Instance Error
**Problem:**
```
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Job at 0x...> is not bound to a Session
```
**Ursache:** Zugriff auf ein SQLAlchemy-Objekt nach dem Schließen der Session.
**Lösungen:**
1. Verwenden Sie `joinedload` für eager loading: `db_session.query(Job).options(joinedload(Job.user))`
2. Konvertieren Sie Objekte zu Dictionaries, bevor Sie die Session schließen: `job_dict = job.to_dict()`
3. Verwenden Sie `session.expunge_all()` gefolgt von `session.close()`, wenn Sie Objekte weiter verwenden müssen
## Tapo Smart Plug Fehler
### Verbindungsfehler
**Problem:**
```
PyP100.PyP100.P100Exception: Could not connect to device
```
**Ursache:** Smart Plug ist nicht erreichbar oder Authentifizierungsprobleme.
**Lösungen:**
1. Überprüfen Sie die IP-Adresse des Smart Plugs
2. Stellen Sie sicher, dass der Smart Plug eingeschaltet und mit dem WLAN verbunden ist
3. Überprüfen Sie die Zugangsdaten in `config/settings.py`
4. Verwenden Sie einen Try-Except-Block zur Fehlerbehandlung:
```python
try:
p110 = PyP110.P110(ip, username, password)
p110.handshake()
p110.login()
p110.turnOn()
except Exception as e:
printers_logger.error(f"Smart Plug Fehler: {str(e)}")
```
## Flask-Anwendungsfehler
### Interner Server-Fehler (500)
**Problem:** Die Anwendung gibt einen 500-Fehler zurück.
**Ursache:** Verschiedene mögliche Ursachen, von Datenbank- bis hin zu Programmierfehlern.
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/app/app.log` und `logs/errors/errors.log`
2. Starten Sie die Anwendung im Debug-Modus: `FLASK_DEBUG=True python3.11 app.py`
3. Implementieren Sie bessere Fehlerbehandlung mit Try-Except-Blöcken
### Authentifizierungsfehler
**Problem:** Benutzer können sich nicht anmelden oder erhalten unbefugte Zugriffsfehler.
**Ursachen:**
- Ungültige Anmeldedaten
- Session-Probleme
- Cookie-Probleme
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/auth/auth.log`
2. Setzen Sie das Passwort zurück mit:
```python
from models import User, get_db_session
db_session = get_db_session()
user = db_session.query(User).filter(User.username == "admin").first()
user.set_password("neues_passwort")
db_session.commit()
db_session.close()
```
3. Löschen Sie Browser-Cookies und -Cache
## CSS und Frontend-Fehler
### Tailwind-Stile werden nicht angewendet
**Problem:** Die Tailwind-Klassen haben keine Wirkung auf das Styling.
**Lösungen:**
1. Stellen Sie sicher, dass die generierte CSS-Datei korrekt eingebunden ist:
```html
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind-dark-consolidated.min.css') }}">
```
2. Bauen Sie die CSS-Datei neu: `npm run build:css`
3. Überprüfen Sie in der Browser-Konsole, ob Fehler beim Laden der CSS-Datei auftreten
### Dark Mode funktioniert nicht
**Problem:** Die Dark-Mode-Stile werden nicht angewendet.
**Lösungen:**
1. Stellen Sie sicher, dass das HTML-Element die entsprechende Klasse hat:
```html
<html class="dark" data-theme="dark">
```
2. Überprüfen Sie, ob die Tailwind-Konfiguration Dark Mode aktiviert hat:
```js
// tailwind.config.js
module.exports = {
darkMode: 'class',
// ...
}
```
## Scheduler-Fehler
### Scheduler führt keine Jobs aus
**Problem:** Der Scheduler schaltet Drucker nicht ein/aus wie erwartet.
**Ursachen:**
- Scheduler ist nicht gestartet
- Fehler bei der Tapo-Verbindung
- Fehler in der Scheduler-Logik
**Lösungen:**
1. Überprüfen Sie die Logs unter `logs/scheduler/scheduler.log`
2. Stellen Sie sicher, dass `SCHEDULER_ENABLED = True` in `config/settings.py` gesetzt ist
3. Starten Sie die Anwendung neu
## Performance-Probleme
### Langsame Ladezeiten
**Problem:** Die Anwendung lädt langsam oder reagiert träge.
**Lösungen:**
1. Optimieren Sie Datenbankabfragen mit geeigneten Indizes
2. Reduzieren Sie die Anzahl der Datenbankabfragen durch Eager Loading
3. Implementieren Sie Caching für häufig abgerufene Daten
4. Überprüfen Sie die Logfiles auf übermäßige Logging-Aktivitäten
## Content Security Policy (CSP) Fehler
### Script-src-elem 'none' Fehler
**Problem:**
```
Refused to load the script because it violates the following Content Security Policy directive: "script-src-elem 'none'".
```
**Ursache:** Die Content Security Policy ist zu restriktiv eingestellt und blockiert das Laden von Skripten.
**Lösungen:**
1. Überprüfen Sie die CSP-Konfiguration in `config/security.py`:
```python
'Content-Security-Policy': (
"default-src 'self'; "
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; "
"script-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; "
# ... weitere Regeln
)
```
2. Stellen Sie sicher, dass die CSP in der `after_request`-Funktion richtig angewendet wird:
```python
@app.after_request
def add_security_headers(response):
from config.security import get_security_headers
security_headers = get_security_headers()
for header, value in security_headers.items():
response.headers[header] = value
# CSP-Report-Only Header entfernen
if 'Content-Security-Policy-Report-Only' in response.headers:
del response.headers['Content-Security-Policy-Report-Only']
return response
```
3. Vermeiden Sie mehrere konkurrierende Service Worker Registrierungen, die CSP-Konflikte verursachen können.
### Inline Script Fehler
**Problem:**
```
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src-elem 'none'"
```
**Lösungen:**
1. Fügen Sie 'unsafe-inline' zur script-src Direktive hinzu
2. Alternativ fügen Sie einen Hash oder Nonce für das Inline-Skript hinzu:
```html
<script nonce="{{ csp_nonce }}">
// Inline JavaScript
</script>
```
und in der CSP:
```
script-src 'nonce-{{ csp_nonce }}';
```
### Service Worker Registration Fehler
**Problem:**
```
Refused to create a worker from 'URL' because it violates the following Content Security Policy directive: "script-src 'none'"
```
**Lösungen:**
1. Stellen Sie sicher, dass die `worker-src` Direktive korrekt konfiguriert ist:
```
worker-src 'self' blob:;
```
2. Erstellen Sie die erforderlichen Service Worker Dateien im richtigen Verzeichnis
3. Verwenden Sie einen einheitlichen Ansatz zur Service Worker Registrierung, vorzugsweise im offline-app.js:
```javascript
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/static/js/sw.js')
.then(function(registration) {
console.log('Service Worker registriert:', registration.scope);
})
.catch(function(error) {
console.log('Service Worker Registration fehlgeschlagen:', error);
// Fallback
});
});
}
```
### Icon 404 Fehler
**Problem:**
```
Failed to load resource: the server responded with a status of 404 (NOT FOUND)
Error while trying to use the following icon from the Manifest: URL (Download error or resource isn't a valid image)
```
**Lösungen:**
1. Erstellen Sie die fehlenden Icon-Dateien im Verzeichnis `static/icons/`
2. Aktualisieren Sie das Web App Manifest, um nur verfügbare Icons zu referenzieren:
```json
"icons": [
{
"src": "/static/icons/mercedes-logo.svg",
"sizes": "192x192",
"type": "image/svg+xml"
}
]
```
3. Verwenden Sie Tools wie Lighthouse, um fehlende PWA-Ressourcen zu identifizieren
## JavaScript-Referenzfehler
### Problem: Nicht definierte Funktionen in admin-panel.js
Fehlermeldungen wie "Uncaught ReferenceError: loadPrinters is not defined" können auftreten, wenn Funktionen in der JavaScript-Datei verwendet, aber nicht definiert werden.
**Lösung:**
- Implementiere die fehlenden Funktionen (loadPrinters, loadSchedulerStatus, loadSystemStats, loadLogs, loadUsers, etc.)
- Stelle sicher, dass alle aufgerufenen Funktionen im richtigen Scope definiert sind
- Überprüfe die Reihenfolge der Funktionsdefinitionen und -aufrufe
### Problem: Funktionen in anderen Dateien werden nicht gefunden
Fehler wie "Uncaught ReferenceError: refreshJobs is not defined" in jobs.html oder "exportStats is not defined" in stats.html.
**Lösung:**
- Stelle sicher, dass die Funktionen in der jeweiligen HTML-Datei oder in einer eingebundenen JavaScript-Datei definiert sind
- Überprüfe, ob die JavaScript-Dateien korrekt in der HTML-Datei eingebunden sind
- Nutze konsistente Namenskonventionen für Funktionen
## Service Worker Fehler
### Problem: Cache-API kann mit chrome-extension URLs nicht umgehen
Fehler: "Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported"
**Lösung:**
- Überprüfe die URL-Protokolle bevor du Cache-Operationen durchführst
- Füge eine spezielle Behandlung für chrome-extension-Protokolle hinzu
- Verwende try-catch-Blöcke, um Fehler bei der Cache-Handhabung abzufangen
```javascript
if (url.protocol === 'chrome-extension:') {
// Spezielle Behandlung für chrome-extension URLs
try {
return await fetch(request);
} catch (error) {
console.error('Failed to fetch from chrome-extension:', error);
return new Response(JSON.stringify({
error: 'Fehler beim Zugriff auf chrome-extension',
offline: true
}), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
}
```
## API-Endpunkt Fehler
### Problem: API-Ressourcen werden nicht gefunden (404)
Fehlermeldungen wie "Failed to load resource: the server responded with a status of 404 (NOT FOUND)"
**Lösung:**
- Überprüfe die API-Routen im Backend
- Stelle sicher, dass die API-Endpunkte korrekt implementiert sind
- Überprüfe die Anfrage-URLs in den Frontend-Skripten
- Füge Fehlerbehandlung für fehlende Ressourcen hinzu
## Frontend-Rendering Fehler
### Problem: Fehler beim Laden der Admin-Statistiken
"Error loading admin: admin-panel.js:484 Fehler beim Laden der Admin-Statistiken"
**Lösung:**
- Implementiere ordnungsgemäße Fehlerbehandlung in API-Anfragen
- Zeige benutzerfreundliche Fehlermeldungen an
- Füge Fallback-Werte für fehlende Daten hinzu
```javascript
try {
const response = await fetch('/api/admin/stats');
if (!response.ok) {
throw new Error('Fehler beim Laden der Admin-Statistiken');
}
const data = await response.json();
// Daten verarbeiten
} catch (error) {
console.error('Error loading admin stats:', error);
showNotification('Fehler beim Laden der Admin-Statistiken', 'error');
}
```
## Icon/Ressourcen Fehler
### Problem: Icon-Dateien werden nicht gefunden
"Error while trying to use the following icon from the Manifest: http://127.0.0.1:5000/static/icons/icon-144x144.png"
**Lösung:**
- Überprüfe, ob die Icon-Dateien im richtigen Verzeichnis vorhanden sind
- Stelle sicher, dass die Pfade in der Manifest-Datei korrekt sind
- Füge Fallback-Icons für den Fall hinzu, dass die primären Icons nicht geladen werden können
## Allgemeine Fehlerbehebung
1. **Browser-Konsole prüfen**: Die meisten JavaScript-Fehler werden in der Browser-Konsole angezeigt (F12 oder Rechtsklick -> Untersuchen -> Konsole)
2. **Netzwerk-Tab prüfen**: Im Netzwerk-Tab des Browsers können fehlgeschlagene API-Anfragen identifiziert werden
3. **Codestruktur überprüfen**: Stelle sicher, dass alle Funktionen in der richtigen Reihenfolge definiert werden
4. **Fehlerbehandlung verbessern**: Implementiere try-catch-Blöcke für alle asynchronen Funktionen
5. **Browser-Cache leeren**: Manchmal können Probleme durch veraltete gecachte Dateien verursacht werden
## 404 Fehler (Seite nicht gefunden)
### Fehlende Routen oder API-Endpunkte
**Problem:**
Die Anwendung zeigt 404-Fehler für bestimmte Routen wie `/privacy`, `/terms`, `/my/jobs`, `/api/user/export` oder `/api/user/profile`.
**Ursachen:**
- Die Route wurde nicht korrekt in `app.py` oder dem entsprechenden Blueprint registriert
- Bei API-Endpunkten: Die alte API-Route wurde verwendet, aber die Anwendung nutzt jetzt eine neue Route
- Möglicherweise wurden die Templates erstellt, aber die Routen dafür fehlen
**Lösungen:**
1. **Für öffentliche Seiten** (`/privacy`, `/terms`):
- Überprüfen Sie, ob die Routen in `app.py` definiert sind
- Stellen Sie sicher, dass der `@login_required` Decorator entfernt wurde, falls diese Seiten öffentlich sein sollen
- Prüfen Sie, ob die entsprechenden Template-Dateien unter `templates/` existieren
2. **Für Benutzer-spezifische Seiten** (`/my/jobs`):
- Fügen Sie eine Route hinzu, die zur Jobs-Seite mit vorgefiltertem Benutzer-Parameter weiterleitet
3. **Für API-Weiterleitungen** (`/api/user/export`, `/api/user/profile`):
- Erstellen Sie Weiterleitungsrouten, die zu den neuen Blueprint-Routen führen
- Beispiel: `/api/user/export` sollte zu `/user/export` weiterleiten
4. **Debugging von Routen:**
- Mit `flask routes` können Sie alle registrierten Routen auflisten
- Überprüfen Sie die Log-Dateien auf fehlgeschlagene Routenzugriffe
- Im Debug-Modus starten: `FLASK_DEBUG=True python3.11 app.py`
5. **Blueprint-Registrierung prüfen:**
- Stellen Sie sicher, dass alle Blueprints korrekt in `app.py` registriert sind:
```python
from blueprints.user import user_bp
app.register_blueprint(user_bp)
```
---
Dieses Dokument wird kontinuierlich aktualisiert, wenn neue Fehler auftreten und Lösungen gefunden werden.

View File

@ -1,95 +0,0 @@
# CSRF-Token Problem - Behebung und Dokumentation
## Problem-Beschreibung
**Fehler-Log**: `2025-05-29 11:51:15 - csrf - [INFO] INFO - The CSRF token is missing.`
**HTTP-Status**: `400 Bad Request` bei POST `/api/guest/requests`
Das Problem trat auf, weil CSRF-Token in JavaScript-Fetch-Requests nicht korrekt übertragen wurden.
## Ursachen-Analyse
1. **Fehlender CSRF-Error-Handler**: Die Flask-App hatte keinen konfigurierten CSRF-Error-Handler
2. **Unvollständige CSRF-Token-Übertragung**: Das `guest_request.html` Template sendete CSRF-Token nicht korrekt mit API-Requests
3. **Inkonsistente CSRF-Implementation**: Verschiedene Templates verwendeten unterschiedliche Methoden zur CSRF-Token-Übertragung
## Angewandte Lösungen
### 1. CSRF-Error-Handler hinzugefügt (`app.py`)
```python
@csrf.error_handler
def csrf_error(reason):
"""Behandelt CSRF-Fehler und gibt detaillierte Informationen zurück."""
app_logger.error(f"CSRF-Fehler für {request.path}: {reason}")
if request.path.startswith('/api/'):
# Für API-Anfragen: JSON-Response
return jsonify({
"error": "CSRF-Token fehlt oder ungültig",
"reason": str(reason),
"help": "Fügen Sie ein gültiges CSRF-Token zu Ihrer Anfrage hinzu"
}), 400
else:
# Für normale Anfragen: Weiterleitung zur Fehlerseite
flash("Sicherheitsfehler: Anfrage wurde abgelehnt. Bitte versuchen Sie es erneut.", "error")
return redirect(request.url)
```
### 2. CSRF-Token in JavaScript korrigiert (`templates/guest_request.html`)
**Vorher** (fehlerhaft):
```javascript
const response = await fetch('/api/guest/requests', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
```
**Nachher** (korrekt):
```javascript
// CSRF-Token aus Meta-Tag auslesen
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
const headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
};
// CSRF-Token hinzufügen, wenn vorhanden
if (csrfToken) {
headers['X-CSRFToken'] = csrfToken;
}
const response = await fetch('/api/guest/requests', {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
```
## Betroffene Dateien
1. **`app.py`** - CSRF-Error-Handler hinzugefügt
2. **`templates/guest_request.html`** - JavaScript CSRF-Token-Implementierung korrigiert
## Validierung der Lösung
1. ✅ CSRF-Error-Handler ist aktiv und loggt Fehler korrekt
2. ✅ API-Endpunkt `/api/guest/requests` akzeptiert jetzt CSRF-Token
3. ✅ Frontend sendet CSRF-Token korrekt mit POST-Requests
4. ✅ Konsistente CSRF-Token-Implementierung über alle Templates
## CSRF-Token Best Practices für zukünftige Entwicklung
1. **Meta-Tag immer einbinden**: `<meta name="csrf-token" content="{{ csrf_token() }}">`
2. **JavaScript CSRF-Token-Hilfsfunktion verwenden**: Nutze bestehende Hilfsfunktionen in `ui-components.js`
3. **API-Requests immer mit CSRF-Token versehen**: Besonders bei POST, PUT, DELETE-Requests
4. **Error-Handler testen**: Sicherstellen, dass CSRF-Fehler korrekt behandelt werden
## Sicherheits-Verbesserungen
- ✅ Schutz vor Cross-Site Request Forgery (CSRF) Attacken
- ✅ Detaillierte Logging für Sicherheitsverletzungen
- ✅ Benutzerfreundliche Fehlerbehandlung
- ✅ Konsistente Sicherheitsrichtlinien über alle API-Endpunkte
## Status
**Behoben**: ✅ CSRF-Token-Problem vollständig gelöst
**Getestet**: ✅ Alle API-Endpunkte funktionieren korrekt
**Dokumentiert**: ✅ Vollständige Dokumentation erstellt

View File

@ -1,170 +0,0 @@
# Datenbank Schema Fix Dokumentation
## Problem
**Datum:** 2025-05-29 12:07:12
**Fehlerbeschreibung:** SQLite OperationalError - table guest_requests has no column named otp_used_at
### Fehlerdetails
```
(sqlite3.OperationalError) no such column: guest_requests.otp_used_at
[SQL: SELECT guest_requests.id AS guest_requests_id, guest_requests.name AS guest_requests_name, guest_requests.email AS guest_requests_email, guest_requests.reason AS guest_requests_reason, guest_requests.duration_min AS guest_requests_duration_min, guest_requests.created_at AS guest_requests_created_at, guest_requests.status AS guest_requests_status, guest_requests.printer_id AS guest_requests_printer_id, guest_requests.otp_code AS guest_requests_otp_code, guest_requests.job_id AS guest_requests_job_id, guest_requests.author_ip AS guest_requests_author_ip, guest_requests.otp_used_at AS guest_requests_otp_used_at FROM guest_requests ORDER BY guest_requests.created_at DESC]
```
## Root Cause Analyse
Das Problem entstand durch mehrere Faktoren:
1. **Modell-Definition vorhanden:** Die `GuestRequest`-Klasse in `models.py` hatte bereits die `otp_used_at`-Spalte definiert (Zeile 762)
2. **Datenbankschema veraltet:** Die tatsächliche Datenbanktabelle `guest_requests` hatte diese Spalte noch nicht
3. **Migration nicht ausgeführt:** Das vorhandene Migrationsskript `migrate_db.py` war fehlerhaft konfiguriert
4. **Falscher Datenbankpfad:** Das Migrationsskript suchte nach `app.db` statt `myp.db`
5. **SQLite WAL-Problem:** Laufende Anwendung hatte alte Datenbankverbindungen mit veralteten Schema-Informationen
## Lösung
**Durchgeführte Aktionen:**
### 1. Manuelle Schema-Migration (Sofortfix)
```sql
ALTER TABLE guest_requests
ADD COLUMN otp_used_at DATETIME
```
### 2. Korrektur des Migrationsskripts
**Datei:** `migrate_db.py`
**Problem:** Falscher Datenbankpfad (suchte nach `app.db` statt `myp.db`)
**Lösung:** Verwendung des korrekten Datenbankpfads aus `config.settings.DATABASE_PATH`
```python
def get_database_path():
"""Ermittelt den Pfad zur Datenbankdatei."""
# Verwende den korrekten Datenbankpfad aus der Konfiguration
if os.path.exists(DATABASE_PATH):
return DATABASE_PATH
# ... Fallback-Logik mit korrekten Dateinamen
```
### 3. WAL-Problem Behebung
**Problem:** SQLite WAL-Modus führte dazu, dass laufende Verbindungen Schema-Änderungen nicht sahen
**Lösung:**
- WAL-Checkpoint (TRUNCATE) durchgeführt
- VACUUM zur Datenbankoptimierung
- SQLAlchemy Engine-Refresh für neue Verbindungen
```python
# WAL-Checkpoint und Optimierung
cursor.execute("PRAGMA wal_checkpoint(TRUNCATE)")
cursor.execute("PRAGMA optimize")
cursor.execute("VACUUM")
```
### 4. Engine-Refresh für SQLAlchemy
**Problem:** Laufende Flask-Anwendung hatte veraltete Schema-Informationen
**Lösung:** Engine-Verbindungen geschlossen und neu erstellt
### Tabellen-Struktur vorher
```
id (INTEGER)
name (VARCHAR(100))
email (VARCHAR(120))
reason (TEXT)
duration_min (INTEGER)
created_at (DATETIME)
status (VARCHAR(20))
printer_id (INTEGER)
otp_code (VARCHAR(100))
job_id (INTEGER)
author_ip (VARCHAR(50))
```
### Tabellen-Struktur nachher
```
id (INTEGER)
name (VARCHAR(100))
email (VARCHAR(120))
reason (TEXT)
duration_min (INTEGER)
created_at (DATETIME)
status (VARCHAR(20))
printer_id (INTEGER)
otp_code (VARCHAR(100))
job_id (INTEGER)
author_ip (VARCHAR(50))
otp_used_at (DATETIME) ← NEU HINZUGEFÜGT
```
## Implementierte Präventionsmaßnahmen
### 1. Migrationsskript korrigiert ✅
- Korrekter Datenbankpfad aus Konfiguration verwendet
- Robuste Fallback-Logik implementiert
- Vollständige Funktionsfähigkeit getestet
### 2. WAL-Problem gelöst ✅
- WAL-Checkpoint standardmäßig durchgeführt
- VACUUM für Datenbankoptimierung
- Schema-Refreshing implementiert
### 3. Engine-Management verbessert ✅
- Automatisches Schließen alter Verbindungen
- Neu-Erstellung der SQLAlchemy Engine
- Metadaten-Refresh für Schema-Updates
### 4. Empfohlene weitere Maßnahmen
- **Automatische Migrations-Überprüfung:** Migrationsskript bei App-Start ausführen
- **Schema-Validierung:** Automatische Überprüfung bei App-Start
- **Bessere Fehlerbehandlung:** Spezifische Behandlung von Schema-Diskrepanzen
## Cascade Analysis
**Betroffene Module/Komponenten:**
- ✅ `models.py` - GuestRequest Modell bereits korrekt definiert
- ✅ `database/myp.db` - Schema erfolgreich aktualisiert
- ✅ `migrate_db.py` - Migrationsskript korrigiert und getestet
- ✅ SQLAlchemy Engine - Verbindungen refreshed
- ✅ Alle Blueprint-Code, der GuestRequest verwendet - funktioniert nach Neustart
- ✅ Migration-System - funktional und robust
## Validation
Nach dem Fix:
- ✅ Spalte `otp_used_at` erfolgreich zur `guest_requests` Tabelle hinzugefügt
- ✅ Datenbankstruktur korrekt (12 Spalten inklusive otp_used_at)
- ✅ WAL-Checkpoint erfolgreich durchgeführt (0, 20, 20)
- ✅ VACUUM und Optimierung abgeschlossen
- ✅ SQLAlchemy Engine erkennt neue Spalte korrekt
- ✅ INSERT/SELECT-Operationen funktionieren in Tests
- ✅ 5 bestehende Datensätze nicht betroffen (NULL-Werte für neue Spalte)
## Tests durchgeführt
```bash
# 1. Migrationsskript erfolgreich getestet
python migrate_db.py
# Output: "Datenbank-Migration erfolgreich abgeschlossen"
# 2. Datenbankstruktur validiert
python debug_database.py
# Output: "✓ DATENBANK IST KORREKT KONFIGURIERT"
# 3. SQLAlchemy Engine refreshed
python refresh_db_connections.py
# Output: "✓ REFRESH ERFOLGREICH - Schema-Änderungen sind jetzt verfügbar"
```
## Anwendungsnestart erforderlich
**Status:** Die laufende Flask-Anwendung (PID: 25900) muss neu gestartet werden, um die Schema-Änderungen vollständig zu übernehmen.
**Grund:** Obwohl die Datenbank korrekt ist und die Engine refreshed wurde, hat die laufende Anwendung möglicherweise noch gecachte Schema-Informationen.
## Finale Lösung
**Zur vollständigen Behebung:**
1. Flask-Anwendung stoppen
2. Flask-Anwendung neu starten
3. Die Schema-Änderungen sind dann vollständig verfügbar
## Status
**TECHNISCH BEHOBEN** - 2025-05-29 12:10:45
1. ✅ Das ursprüngliche Schema-Problem wurde behoben
2. ✅ Das Migrationsskript wurde korrigiert und getestet
3. ✅ WAL-Probleme wurden gelöst
4. ✅ SQLAlchemy Engine wurde refreshed
5. ⏳ **Anwendungsnestart ausstehend** für vollständige Aktivierung
Die Datenbank-Schema-Diskrepanz wurde technisch vollständig behoben. Nach einem Neustart der Flask-Anwendung funktionieren alle Gastanfragen-Operationen wieder fehlerfrei.

View File

@ -1,164 +0,0 @@
# DetachedInstanceError Fix Dokumentation
## Problem
**Datum:** 2025-05-29 12:12:32
**Fehlerbeschreibung:** SQLAlchemy DetachedInstanceError beim Zugriff auf Relationship-Attribute in Templates
### Fehlerdetails
```
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <GuestRequest at 0x20a0356f130> is not bound to a Session; lazy load operation of attribute 'printer' cannot proceed
```
**Stack Trace:**
- Aufgerufen in `templates/guest_status.html`, Zeile 80: `{% if request.printer %}`
- Verursacht durch `blueprints/guest.py`, `guest_request_status` Funktion
- ORM-Objekt außerhalb der aktiven Session verwendet
## Root Cause Analyse
Das Problem entstand durch:
1. **Session-Scope-Problem:** `GuestRequest`-Objekt wurde innerhalb eines `with get_cached_session()` Blocks geladen
2. **Lazy Loading:** Das `printer`-Relationship wurde als lazy loading konfiguriert
3. **Template-Zugriff:** Template versuchte auf `request.printer` zuzugreifen, nachdem die Session geschlossen war
4. **Detached Instance:** SQLAlchemy konnte keine lazy loading operation durchführen
## Lösung
**Durchgeführte Aktionen:**
### 1. Eager Loading implementiert
**Betroffene Funktionen:**
- `guest_request_status()`
- `guest_requests_overview()`
**Lösung:** Verwendung von `joinedload()` für das `printer`-Relationship
```python
# Vorher (lazy loading)
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
# Nachher (eager loading)
guest_request = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer)
).filter_by(id=request_id).first()
```
### 2. Session Expunge für Template-Verwendung
**Problem:** Objekte bleiben an Session gebunden, auch nach Schließung
**Lösung:** Explizites Trennen der Objekte von der Session
```python
# Objekte explizit von der Session trennen
db_session.expunge(guest_request)
if job:
db_session.expunge(job)
```
### 3. Drucker-Liste Fix
**Betroffene Funktion:** `guest_request_form()`
**Problem:** Drucker-Query-Objekt statt Liste zurückgegeben
**Lösung:** `.all()` hinzugefügt und `expunge_all()` verwendet
```python
# Vorher
printers = db_session.query(Printer).filter_by(active=True)
# Nachher
printers = db_session.query(Printer).filter_by(active=True).all()
db_session.expunge_all()
```
## Implementierte Korrekturen
### 1. guest_request_status() ✅
```python
@guest_blueprint.route('/request/<int:request_id>', methods=['GET'])
def guest_request_status(request_id):
with get_cached_session() as db_session:
# Eager loading für printer-Relationship
guest_request = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer)
).filter_by(id=request_id).first()
# ... weitere Logik ...
# Objekte von Session trennen
db_session.expunge(guest_request)
if job:
db_session.expunge(job)
return render_template('guest_status.html',
request=guest_request,
job=job,
otp_code=otp_code)
```
### 2. guest_requests_overview() ✅
```python
# Eager loading für alle GuestRequests
guest_requests = db_session.query(GuestRequest).options(
joinedload(GuestRequest.printer)
).order_by(desc(GuestRequest.created_at)).all()
```
### 3. guest_request_form() ✅
```python
printers = db_session.query(Printer).filter_by(active=True).all()
db_session.expunge_all()
```
## Cascade Analysis
**Betroffene Module/Komponenten:**
- ✅ `blueprints/guest.py` - Alle drei problematischen Funktionen korrigiert
- ✅ `templates/guest_status.html` - Kann jetzt sicher auf `request.printer` zugreifen
- ✅ `templates/guest_requests_overview.html` - Printer-Namen werden korrekt angezeigt
- ✅ `templates/guest_request.html` - Drucker-Liste wird korrekt geladen
- ✅ SQLAlchemy ORM - Relationships funktionieren korrekt
## Präventionsmaßnahmen
### 1. Coding Guidelines ✅
- **Eager Loading:** Für alle Relationships, die in Templates verwendet werden
- **Session Expunge:** Objekte vor Template-Weitergabe von Session trennen
- **Query Completion:** `.all()` für Listen, `.first()` für Einzelobjekte
### 2. Template-Sicherheit
- Defensive Programmierung in Templates mit `{% if object.relationship %}`
- Null-Checks für optionale Relationships
### 3. Empfohlene weitere Maßnahmen
- **Code Review:** Systematische Überprüfung aller Template-verwendeten ORM-Objekte
- **Testing:** Unit-Tests für Template-Rendering mit Mock-Sessions
- **Documentation:** Dokumentation der Session-Handhabung in Templates
## Validation
Nach dem Fix:
- ✅ `guest_request_status` Template lädt ohne DetachedInstanceError
- ✅ `guest_requests_overview` zeigt Drucker-Namen korrekt an
- ✅ `guest_request_form` lädt Drucker-Liste fehlerfrei
- ✅ Eager Loading funktioniert für printer-Relationships
- ✅ Session-Management ist sauber implementiert
- ✅ Keine Performance-Regression durch JOIN-Queries
## Tests empfohlen
```python
# Test für Template-Rendering
def test_guest_request_status_template():
# Erstelle Test-GuestRequest mit Printer
# Rufe guest_request_status() auf
# Validiere Template-Rendering ohne DetachedInstanceError
def test_eager_loading():
# Validiere dass printer-Relationship geladen wird
# Ohne zusätzliche SQL-Queries
```
## Status
**VOLLSTÄNDIG BEHOBEN** - 2025-05-29 12:15:00
1. ✅ DetachedInstanceError in allen betroffenen Funktionen behoben
2. ✅ Eager Loading für kritische Relationships implementiert
3. ✅ Session-Management verbessert
4. ✅ Template-Sicherheit gewährleistet
5. ✅ Coding Guidelines etabliert
Der DetachedInstanceError wurde vollständig behoben. Alle Templates können jetzt sicher auf ORM-Relationships zugreifen, ohne Session-Probleme zu verursachen.

View File

@ -1,392 +0,0 @@
# DNS-Konfiguration und Netzwerk-Optimierung
## Übersicht
Das MYP Kiosk-System implementiert eine intelligente DNS-Konfiguration mit automatischer Router-Erkennung, Fallback-Mechanismen und IPv6-Deaktivierung für optimale Netzwerk-Performance.
## Funktionen
### 🎯 Intelligente DNS-Prioritäten
1. **Router-DNS** (Höchste Priorität)
- Automatische Erkennung via DHCP, systemd-resolved, NetworkManager
- Route-basierte Fallback-Erkennung
- Funktionalitätstest vor Verwendung
2. **Google DNS** (Fallback 1)
- `8.8.8.8` und `8.8.4.4`
- Zuverlässig und schnell
3. **Cloudflare DNS** (Fallback 2)
- `1.1.1.1` und `1.0.0.1`
- Privacy-fokussiert
4. **Custom DNS** (Fallback 3)
- `163.116.178.73` und `163.116.178.74`
- Benutzerdefinierte Server
### 🚫 IPv6-Deaktivierung
- **Kernel-Level**: Systemweite IPv6-Deaktivierung
- **Boot-Level**: GRUB und cmdline.txt Parameter
- **Network-Level**: NetworkManager und DHCP-Konfiguration
### 🔄 Automatische Aktualisierung
- **Alle 30 Minuten**: DNS-Prioritäten neu bewerten
- **Alle 10 Minuten**: DNS-Gesundheitscheck
- **Wöchentlich**: Root Hints aktualisieren
## Architektur
```
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ MYP Kiosk │───▶│ Unbound │───▶│ Router DNS │
│ Application │ │ Resolver │ │ (Priorität 1) │
└─────────────────┘ │ 127.0.0.1 │ └─────────────────┘
│ │ ┌─────────────────┐
│ │───▶│ Google DNS │
│ │ │ (Fallback 1) │
│ │ └─────────────────┘
│ │ ┌─────────────────┐
│ │───▶│ Cloudflare DNS │
│ │ │ (Fallback 2) │
│ │ └─────────────────┘
│ │ ┌─────────────────┐
│ │───▶│ Custom DNS │
│ │ │ (Fallback 3) │
└──────────────┘ └─────────────────┘
```
## Konfigurationsdateien
### Unbound Hauptkonfiguration
```bash
/etc/unbound/unbound.conf
```
**Wichtige Einstellungen:**
- IPv6 deaktiviert (`do-ip6: no`)
- Lokale Netzwerke erlaubt
- DNSSEC aktiviert
- Performance-optimiert (64MB Cache)
### DNS-Prioritätsskript
```bash
/usr/local/bin/configure-dns-priority
```
**Funktionen:**
- Router-DNS automatisch erkennen
- DNS-Server-Funktionalität testen
- Unbound-Konfiguration dynamisch aktualisieren
- Logging aller Änderungen
### Systemd-Services
```bash
/etc/systemd/system/dns-priority-config.service
```
**Abhängigkeiten:**
- Nach `network-online.target`
- Nach `unbound.service`
- Vor `myp-druckerverwaltung.service`
## Router-DNS-Erkennung
### Methode 1: DHCP Lease-Datei
```bash
grep "domain-name-servers" /var/lib/dhcp/dhclient.leases
```
### Methode 2: systemd-resolved
```bash
systemd-resolve --status | grep "DNS Servers"
```
### Methode 3: NetworkManager
```bash
nmcli dev show | grep "IP4.DNS"
```
### Methode 4: Route-basierte Erkennung
```bash
# Gateway als DNS-Server testen
gateway=$(ip route | grep default | awk '{print $3}')
nslookup google.com "$gateway"
```
## IPv6-Deaktivierung
### Kernel-Parameter
```bash
# /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
```
### Boot-Parameter
```bash
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="ipv6.disable=1 ..."
# /boot/cmdline.txt (Raspberry Pi)
... ipv6.disable=1
```
### NetworkManager
```bash
# /etc/NetworkManager/conf.d/dns-priority.conf
[connection]
ipv6.method=ignore
```
## DHCP-Schutz
### dhclient-Konfiguration
```bash
# /etc/dhcp/dhclient.conf
supersede domain-name-servers 127.0.0.1;
```
### resolv.conf-Schutz
```bash
# Schreibschutz aktivieren
chattr +i /etc/resolv.conf
```
## Monitoring und Wartung
### DNS-Status prüfen
```bash
myp-maintenance dns-status
```
**Zeigt an:**
- Unbound Service-Status
- Aktuelle DNS-Server
- Erkannte Router-DNS
- DNS-Statistiken
- Letzte Logs
### DNS-Test durchführen
```bash
myp-maintenance dns-test
```
**Testet:**
- google.com
- github.com
- debian.org
- cloudflare.com
### DNS-Konfiguration neu laden
```bash
myp-maintenance dns-reconfigure
```
### IPv6-Status prüfen
```bash
myp-maintenance ipv6-status
```
## Automatische Überwachung
### Cron-Jobs
```bash
# /etc/cron.d/dns-priority-update
# DNS-Priorität alle 30 Minuten aktualisieren
*/30 * * * * root /usr/local/bin/configure-dns-priority
# Root Hints wöchentlich aktualisieren
0 3 * * 0 root curl -s -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache
# DNS-Gesundheitscheck alle 10 Minuten
*/10 * * * * root /usr/local/bin/dns-health-check
```
### Gesundheitscheck
```bash
/usr/local/bin/dns-health-check
```
**Prüft:**
- Unbound Service-Status
- DNS-Auflösung für Test-Domains
- Automatischer Neustart bei Fehlern
- Konfiguration neu laden bei kritischen Fehlern
## Log-Dateien
### DNS-Konfiguration
```bash
/var/log/dns-configuration.log
```
**Enthält:**
- Router-DNS-Erkennungen
- Konfigurationsänderungen
- DNS-Server-Tests
- Unbound-Neustarts
### DNS-Gesundheit
```bash
/var/log/dns-health.log
```
**Enthält:**
- Regelmäßige Gesundheitschecks
- DNS-Auflösungsfehler
- Service-Neustarts
- Kritische Fehler
### Unbound-Logs
```bash
/var/log/unbound.log
```
**Enthält:**
- Unbound Service-Logs
- DNS-Anfragen (optional)
- Fehler und Warnungen
## Troubleshooting
### DNS-Auflösung funktioniert nicht
1. **Service-Status prüfen:**
```bash
systemctl status unbound
```
2. **DNS-Test durchführen:**
```bash
nslookup google.com 127.0.0.1
```
3. **Konfiguration neu laden:**
```bash
/usr/local/bin/configure-dns-priority
```
### Router-DNS wird nicht erkannt
1. **DHCP-Lease prüfen:**
```bash
cat /var/lib/dhcp/dhclient.leases | grep domain-name-servers
```
2. **Gateway-Test:**
```bash
gateway=$(ip route | grep default | awk '{print $3}')
nslookup google.com "$gateway"
```
3. **Manuelle Konfiguration:**
```bash
# Router-DNS manuell in Unbound eintragen
echo "forward-addr: 192.168.1.1" >> /etc/unbound/unbound.conf
systemctl reload unbound
```
### IPv6 noch aktiv
1. **Kernel-Parameter prüfen:**
```bash
sysctl net.ipv6.conf.all.disable_ipv6
```
2. **Boot-Parameter prüfen:**
```bash
cat /proc/cmdline | grep ipv6.disable
```
3. **Neustart erforderlich:**
```bash
sudo reboot
```
### Unbound startet nicht
1. **Konfiguration testen:**
```bash
unbound-checkconf /etc/unbound/unbound.conf
```
2. **Berechtigungen prüfen:**
```bash
chown -R unbound:unbound /var/lib/unbound
```
3. **Port-Konflikt prüfen:**
```bash
netstat -tulpn | grep :53
```
## Performance-Optimierung
### Cache-Einstellungen
```bash
# Unbound Cache-Konfiguration
msg-cache-size: 64m
rrset-cache-size: 128m
cache-max-ttl: 86400
cache-min-ttl: 300
```
### Thread-Konfiguration
```bash
# Optimiert für Raspberry Pi
num-threads: 2
msg-cache-slabs: 4
rrset-cache-slabs: 4
```
### Netzwerk-Puffer
```bash
# Erhöhte Puffer für bessere Performance
so-rcvbuf: 4m
so-sndbuf: 4m
outgoing-range: 4096
```
## Sicherheit
### Zugriffskontrolle
```bash
# Nur lokale Netzwerke erlaubt
access-control: 127.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
access-control: 10.0.0.0/8 allow
access-control: 172.16.0.0/12 allow
```
### DNSSEC
```bash
# Automatische Trust-Anchor-Verwaltung
auto-trust-anchor-file: "/var/lib/unbound/root.key"
```
### Private Adressen
```bash
# Verhindert DNS-Rebinding-Angriffe
private-address: 192.168.0.0/16
private-address: 172.16.0.0/12
private-address: 10.0.0.0/8
private-address: 127.0.0.0/8
```
---
**Status**: ✅ Produktionsreif
**Letzte Aktualisierung**: $(date +%Y-%m-%d)
**Version**: 1.0 (DNS-Optimiert)
## Referenzen
- [Unbound DNS Resolver](https://nlnetlabs.nl/projects/unbound/about/)
- [DNS-over-HTTPS RFC 8484](https://tools.ietf.org/html/rfc8484)
- [IPv6 Deaktivierung Best Practices](https://wiki.debian.org/DebianIPv6)
- [DNSSEC Validation](https://tools.ietf.org/html/rfc4033)

View File

@ -1 +0,0 @@

View File

@ -1,272 +0,0 @@
# MYP Error-Monitoring System - Dokumentation
## Übersicht
Das Error-Monitoring System ist eine umfassende Lösung zur automatischen Erkennung, Meldung und Behebung kritischer Systemfehler im MYP (Mercedes-Benz Your Platform) System. Es wurde entwickelt, um Administratoren sofortige Benachrichtigungen über Datenbankfehler, Schema-Probleme und andere kritische Systemprobleme zu geben.
## Problemstellung
**Ursprünglicher Fehler:**
```
sqlite3.OperationalError: no such column: guest_requests.duration_minutes
```
Dieser Fehler trat auf, weil das Datenmodell `GuestRequest` sowohl `duration_min` als auch `duration_minutes` definierte, aber die Datenbank nur die `duration_min` Spalte enthielt. Solche Schema-Inkonsistenzen führten zu Anwendungsfehlern und waren für Admins nicht sichtbar.
## Lösung
### 1. Automatische Datenbank-Migration ⚡
**Datei:** `utils/database_schema_migration.py`
**Erweiterte Funktionalität:**
- Vollständige Schema-Überprüfung für alle Tabellen
- Automatisches Hinzufügen fehlender Spalten
- Backup-Erstellung vor jeder Migration
- Datenmigration (kopiert `duration_min``duration_minutes`)
**Neue Spalten hinzugefügt:**
```python
required_columns = {
'duration_minutes': 'INTEGER', # ← Lösung für ursprünglichen Fehler
'file_name': 'VARCHAR(255)',
'file_path': 'VARCHAR(500)',
'copies': 'INTEGER DEFAULT 1',
'updated_at': 'DATETIME DEFAULT CURRENT_TIMESTAMP',
'approved_at': 'DATETIME',
'rejected_at': 'DATETIME',
'approved_by': 'INTEGER',
'rejected_by': 'INTEGER',
'otp_expires_at': 'DATETIME',
'assigned_printer_id': 'INTEGER'
}
```
### 2. Real-Time Error-Monitoring Dashboard 📊
**Datei:** `templates/admin.html`
**Neue Komponenten:**
- **Critical Errors Alert System**: Rote Warnmeldungen für kritische Fehler
- **Database Health Status**: Echtzeit-Überwachung der Datenbankgesundheit
- **Automatic Fix Button**: Ein-Klick-Reparatur für häufige Probleme
**Features:**
- 🚨 Sofortige Benachrichtigungen bei kritischen Fehlern
- 🗄️ Datenbank-Gesundheitsstatus mit Live-Indikatoren
- 🔧 Automatische Reparatur-Buttons
- 📊 System-Metriken (CPU, RAM, Festplatte)
### 3. Comprehensive Health Check API 🔍
**Datei:** `app.py` - Neue Endpoints:
#### `/api/admin/system-health` (GET)
**Funktionalität:**
```python
def api_admin_system_health():
# 1. Datenbank-Schema-Integrität prüfen
# 2. Kritische Spalten in wichtigen Tabellen überprüfen
# 3. Log-Dateien nach wiederkehrenden Fehlern durchsuchen
# 4. Drucker-Konnektivität überprüfen
# 5. System-Performance-Metriken sammeln
# 6. Letzte Migration-Informationen abrufen
```
**Response-Format:**
```json
{
"success": true,
"health_status": "healthy|warning|critical",
"critical_errors": [
{
"type": "database_schema",
"message": "Datenbank-Schema-Fehler erkannt",
"severity": "critical",
"suggested_fix": "Datenbank-Migration ausführen",
"timestamp": "2025-05-29T18:22:03"
}
],
"warnings": [...],
"schema_integrity": "OK|FEHLER",
"last_migration": "20250529_182203",
"recent_errors_count": 0,
"system_metrics": {
"cpu_usage": 15.2,
"memory_usage": 42.1,
"disk_usage": 68.9
}
}
```
#### `/api/admin/fix-errors` (POST)
**Funktionalität:**
- Führt automatische Datenbank-Migration aus
- Erstellt Backup vor Reparatur
- Protokolliert alle Aktionen
- Gibt detaillierte Ergebnis-Informationen zurück
### 4. Live JavaScript Error-Monitor 🔄
**Datei:** `static/js/admin-live.js`
**Neue Klassen-Methoden:**
- `initErrorMonitoring()`: Startet das Monitoring-System
- `checkSystemHealth()`: Prüft System alle 30 Sekunden
- `updateHealthDisplay()`: Aktualisiert UI-Indikatoren
- `updateErrorAlerts()`: Zeigt/versteckt Error-Alerts
- `fixErrors()`: Führt automatische Reparatur aus
- `showNotification()`: Toast-Benachrichtigungen
**Live-Features:**
- ⏱️ Automatische Überprüfung alle 30 Sekunden
- 🔴 Rote Indikatoren bei kritischen Fehlern
- 🟡 Gelbe Indikatoren bei Warnungen
- 🟢 Grüne Indikatoren bei gesundem System
- 📱 Toast-Benachrichtigungen für Aktionen
## Technische Details
### Schema-Migration-Prozess
1. **Backup-Erstellung:**
```sql
VACUUM INTO 'database/myp.db.backup_YYYYMMDD_HHMMSS'
```
2. **Spalten-Überprüfung:**
```python
cursor.execute("PRAGMA table_info(guest_requests)")
existing_columns = {row[1]: row[2] for row in cursor.fetchall()}
```
3. **Automatisches Hinzufügen:**
```sql
ALTER TABLE guest_requests ADD COLUMN duration_minutes INTEGER
UPDATE guest_requests SET duration_minutes = duration_min WHERE duration_minutes IS NULL
```
### Error-Detection-Algorithmus
1. **Schema-Integrität:** Testet kritische Spalten mit `SELECT ... LIMIT 1`
2. **Log-Analyse:** Durchsucht letzte 100 Log-Zeilen nach "OperationalError"
3. **Performance-Monitoring:** Nutzt `psutil` für System-Metriken
4. **Drucker-Status:** Überprüft offline/online Status
5. **Migration-Historie:** Analysiert Backup-Dateien für letzte Änderungen
## Admin-Interface
### Darstellung im Dashboard
```html
<!-- Critical Error Alert -->
🚨 Kritische Systemfehler erkannt
├── Datenbank-Schema-Fehler: no such column: duration_minutes
│ 💡 Suggested Fix: Datenbank-Migration ausführen
│ 📅 29.05.2025, 18:22:03
│ 🔧 [Automatisch reparieren] ❌ [Verwerfen] 📊 [Details]
<!-- Database Health Status -->
🗄️ Datenbank-Gesundheitsstatus 🟢 Gesund
├── Letzte Migration: 20250529_182203
├── Schema-Integrität: OK
└── Letzte Fehler: 0
```
### Benutzerinteraktion
1. **Fehler erkannt** → Alert wird automatisch angezeigt
2. **Admin klickt "Automatisch reparieren"** → Migration wird ausgeführt
3. **Erfolgsmeldung** → ✅ Grüne Toast-Benachrichtigung
4. **System aktualisiert sich** → Health-Check läuft erneut
## Konfiguration
### Monitoring-Intervalle
```javascript
// System Health Check alle 30 Sekunden
setInterval(() => this.checkSystemHealth(), 30000);
// Toast-Notifications verschwinden nach 5 Sekunden
setTimeout(() => notification.remove(), 5000);
```
### Schwellenwerte
```python
# Performance-Warnungen
cpu_usage > 90% # Warnung bei hoher CPU-Last
memory_usage > 85% # Warnung bei hohem RAM-Verbrauch
recent_db_errors > 5 # Kritisch bei vielen DB-Fehlern
```
## Deployment
### Automatische Aktivierung
Das Error-Monitoring System ist automatisch aktiv sobald:
1. Ein Administrator das Admin-Dashboard öffnet
2. Das JavaScript `admin-live.js` geladen wird
3. Die Health-Check-APIs verfügbar sind
### Voraussetzungen
```python
# Python-Dependencies
import psutil # Für System-Metriken
import subprocess # Für automatische Migration
import os # Für Log-Datei-Zugriff
```
## Logging und Dokumentation
### Error-Logging
```python
app_logger.error(f"Datenbank-Transaktion fehlgeschlagen: {str(e)}")
app_logger.info(f"Automatische Migration erfolgreich ausgeführt von Admin {current_user.email}")
```
### Admin-Aktionen
Alle Admin-Aktionen werden protokolliert:
- Wer hat welche Reparatur ausgeführt
- Zeitstempel aller Aktionen
- Erfolg/Fehlschlag-Status
- Detaillierte Fehlermeldungen
## Wartung
### Regelmäßige Aufgaben
1. **Log-Rotation:** Alte Log-Dateien archivieren
2. **Backup-Cleanup:** Alte Backup-Dateien löschen
3. **Performance-Monitoring:** System-Metriken überwachen
4. **Schema-Updates:** Neue Migrations bei Model-Änderungen
### Troubleshooting
**Problem:** Error-Monitor zeigt nichts an
**Lösung:**
1. Browser-Konsole überprüfen
2. `/api/admin/system-health` manuell testen
3. Admin-Berechtigung überprüfen
**Problem:** Automatische Reparatur schlägt fehl
**Lösung:**
1. Manuelle Migration: `python utils/database_schema_migration.py`
2. Log-Dateien überprüfen
3. Datenbank-Berechtigungen prüfen
## Ergebnis
**Problem gelöst:** Der ursprüngliche `duration_minutes` Fehler wurde behoben
**Proaktiv:** Zukünftige Schema-Probleme werden automatisch erkannt
**Benutzerfreundlich:** Admins sehen Probleme sofort und können sie mit einem Klick beheben
**Umfassend:** Monitoring von DB, Performance, Logs und System-Gesundheit
**Automatisiert:** Selbst-reparierendes System für häufige Probleme
Das Error-Monitoring System stellt sicher, dass kritische Systemfehler nicht unbemerkt bleiben und Administratoren die Werkzeuge haben, um schnell und effektiv zu reagieren.

View File

@ -1,197 +0,0 @@
# Behobene Installationsfehler
## Übersicht
Diese Dokumentation beschreibt die kritischen Fehler, die während der Installation aufgetreten sind und wie sie behoben wurden.
## 🔧 Behobene Fehler
### 1. chown: invalid user: 'syslog:adm' - FINAL BEHOBEN
**Problem**: Der `syslog`-Benutzer existiert nicht auf allen Systemen und verursachte Installationsabbrüche.
**Finale Lösung**: Komplette Entfernung der problematischen syslog-Berechtigungslogik:
```bash
# VORHER (problematisch):
chown syslog:adm /var/log/kiosk-*.log 2>/dev/null || chown root:root /var/log/kiosk-*.log
chown syslog:adm /var/log/myp-*.log 2>/dev/null || chown root:root /var/log/myp-*.log
# ... komplexe Fallback-Logik
# NACHHER (einfach und robust):
# Setze einfache root:root Berechtigungen für alle Log-Dateien (maximale Kompatibilität)
chown root:root /var/log/kiosk-session.log 2>/dev/null || true
chown root:root /var/log/kiosk-monitor.log 2>/dev/null || true
chown root:root /var/log/emergency-reset.log 2>/dev/null || true
# ... alle Log-Dateien mit root:root
```
**Ergebnis**:
- ✅ Keine Installationsabbrüche mehr
- ✅ Funktioniert auf allen Linux-Distributionen
- ✅ Einfache, wartbare Lösung
- ✅ Maximale Kompatibilität
### 2. chown: invalid user: 'unbound'
**Problem**: Der `unbound`-Benutzer wird nicht automatisch bei der Paket-Installation erstellt.
**Lösung**: Automatische Benutzer-Erstellung mit Fallback:
```bash
# Prüfe ob unbound-Benutzer existiert, sonst erstelle ihn oder verwende root
if ! id "unbound" &>/dev/null; then
warning "unbound-Benutzer nicht gefunden - versuche Erstellung..."
if ! useradd --system --no-create-home --shell /bin/false unbound 2>/dev/null; then
warning "unbound-Benutzer konnte nicht erstellt werden - verwende root"
chown -R root:root /var/lib/unbound 2>/dev/null || true
chown root:root /etc/unbound/unbound.conf 2>/dev/null || true
else
chown -R unbound:unbound /var/lib/unbound 2>/dev/null || true
chown unbound:unbound /etc/unbound/unbound.conf 2>/dev/null || true
fi
else
chown -R unbound:unbound /var/lib/unbound 2>/dev/null || true
chown unbound:unbound /etc/unbound/unbound.conf 2>/dev/null || true
fi
```
### 3. chown: invalid group: 'www-data'
**Problem**: Der `www-data`-Benutzer existiert nicht auf allen minimalen Systemen.
**Lösung**: Fallback auf APP_USER bei fehlendem www-data:
```bash
# Prüfe ob www-data existiert, sonst verwende APP_USER
if id "www-data" &>/dev/null; then
chown -R "$APP_USER:www-data" "$APP_DIR/uploads"
chown -R "$APP_USER:www-data" "$APP_DIR/static"
else
warning "www-data-Benutzer nicht verfügbar - verwende $APP_USER:$APP_USER"
chown -R "$APP_USER:$APP_USER" "$APP_DIR/uploads"
chown -R "$APP_USER:$APP_USER" "$APP_DIR/static"
fi
```
### 4. $HOME Variable nicht verfügbar
**Problem**: `$HOME` ist im systemd-Service-Kontext nicht verfügbar.
**Lösung**: Verwendung des absoluten Pfads:
```bash
# Vorher (fehlerhaft):
sudo -u $KIOSK_USER DISPLAY=:0 $HOME/start-kiosk.sh &
# Nachher (korrekt):
sudo -u $KIOSK_USER DISPLAY=:0 /home/$KIOSK_USER/start-kiosk.sh &
```
### 5. CHROMIUM_BIN Variable nicht global verfügbar
**Problem**: Die `CHROMIUM_BIN` Variable war nur lokal in der Funktion verfügbar.
**Lösung**: Globale Deklaration der Variable:
```bash
# In der Konfigurationssektion:
CHROMIUM_BIN="" # Global verfügbar machen
```
## 🛡️ Robustheit-Verbesserungen
### Fehlerbehandlung mit 2>/dev/null
Alle kritischen `chown`-Befehle wurden mit Fehlerbehandlung versehen:
```bash
chown syslog:adm /var/log/kiosk-*.log 2>/dev/null || chown root:root /var/log/kiosk-*.log
```
### Benutzer-Existenz-Prüfungen
Systematische Prüfung aller Systembenutzer vor Verwendung:
```bash
if id "username" &>/dev/null; then
# Benutzer existiert - verwende ihn
else
# Fallback-Lösung
fi
```
### Graceful Degradation
Das System funktioniert auch wenn bestimmte Benutzer nicht verfügbar sind:
- **syslog** → Fallback auf `root:root`
- **unbound** → Automatische Erstellung oder `root:root`
- **www-data** → Fallback auf `$APP_USER:$APP_USER`
## 📊 Auswirkungen der Behebungen
### Verbesserte Kompatibilität
- ✅ Funktioniert auf Ubuntu Server minimal
- ✅ Funktioniert auf Debian minimal
- ✅ Funktioniert auf Raspberry Pi OS Lite
- ✅ Funktioniert auf Standard-Distributionen
### Erhöhte Stabilität
- ✅ Keine Installationsabbrüche durch fehlende Benutzer
- ✅ Graceful Fallbacks bei System-Unterschieden
- ✅ Robuste Fehlerbehandlung
### Bessere Wartbarkeit
- ✅ Klare Fehlermeldungen
- ✅ Dokumentierte Fallback-Strategien
- ✅ Einfache Debugging-Möglichkeiten
## 🔍 Testing
Die Behebungen wurden getestet auf:
- **Ubuntu 22.04 Server** (minimal)
- **Debian 12** (minimal)
- **Raspberry Pi OS Lite**
- **Standard Ubuntu Desktop** (Referenz)
## 📝 Lessons Learned
1. **Niemals Systembenutzer als gegeben annehmen**
2. **Immer Fallback-Strategien implementieren**
3. **Fehlerbehandlung für alle kritischen Operationen**
4. **Umgebungsvariablen in systemd-Services vermeiden**
5. **Absolute Pfade statt relativer Pfade verwenden**
---
**Status**: ✅ Alle kritischen Fehler behoben
**Letzte Aktualisierung**: $(date +%Y-%m-%d)
**Version**: 1.2 (Final-Fix)
## 🎯 Finale Zusammenfassung
### Kritische Behebungen:
1. **syslog:adm Fehler** → Komplette Entfernung der problematischen Logik
2. **unbound Benutzer** → Automatische Erstellung mit Fallback
3. **www-data Gruppe** → Graceful Fallback auf APP_USER
4. **$HOME Variable** → Absolute Pfade in systemd-Services
5. **CHROMIUM_BIN** → Globale Variable-Deklaration
### Robustheit-Verbesserungen:
- ✅ Wildcard-Expansion-Probleme behoben
- ✅ Benutzer-Existenz-Prüfungen für alle kritischen Benutzer
- ✅ Graceful Degradation bei fehlenden System-Komponenten
- ✅ Maximale Kompatibilität über alle Linux-Distributionen
### Test-Status:
- ✅ **Ubuntu 22.04 Server** (minimal) - Funktional
- ✅ **Debian 12** (minimal) - Funktional
- ✅ **Raspberry Pi OS Lite** - Funktional
- ✅ **Standard Ubuntu Desktop** - Funktional
**Das Installationsskript ist jetzt produktionsreif und robust!** 🚀

View File

@ -1,249 +0,0 @@
# Behobene Systemfehler - MYP Platform
**Datum:** 30. Mai 2025
**Version:** 2.0.1
**Status:** ✅ BEHOBEN
## Übersicht der behobenen Fehler
### 1. CSRF-Token Fehler (Kritisch)
**Problem:** `400 Bad Request: The CSRF token is missing.` für `/api/session/heartbeat`
**Root Cause:**
- Flask-WTF erwartet `X-CSRFToken` Header, nicht `X-CSRF-Token`
- CSRF-Token wurde nicht im Request-Body mitgesendet
**Lösung:**
```javascript
// Vorher:
headers['X-CSRF-Token'] = csrfToken;
// Nachher:
headers['X-CSRFToken'] = csrfToken;
body: JSON.stringify({
timestamp: new Date().toISOString(),
page: window.location.pathname,
csrf_token: csrfToken // Zusätzlich im Body
})
```
**Datei:** `static/js/session-manager.js`
**Auswirkung:** Session-Heartbeat funktioniert wieder korrekt
---
### 2. SQLAlchemy Legacy-Warnungen (Mittel)
**Problem:** `LegacyAPIWarning: The Query.get() method is considered legacy`
**Root Cause:**
- Verwendung der veralteten `query().get()` Syntax in SQLAlchemy 2.0
- 15+ Stellen im Code betroffen
**Lösung:**
```python
# Vorher:
printer = db_session.query(Printer).get(printer_id)
# Nachher:
printer = db_session.get(Printer, printer_id)
```
**Betroffene Dateien:**
- `app.py` (3 Stellen)
- `utils/job_scheduler.py` (3 Stellen)
- `utils/queue_manager.py` (2 Stellen)
**Auswirkung:** Keine Deprecation-Warnungen mehr im Log
---
### 3. JavaScript PrinterManager-Fehler (Kritisch)
**Problem:** `TypeError: this.setupFilters is not a function`
**Root Cause:**
- Methode `setupFilters()` existierte nicht in der PrinterManager-Klasse
- Wurde in `init()` aufgerufen, aber nie definiert
**Lösung:**
```javascript
// Vorher:
async init() {
await this.loadPrinters();
this.setupFilters(); // ❌ Methode existiert nicht
this.initializePerformanceMonitoring();
}
// Nachher:
async init() {
await this.loadPrinters();
this.populateFilterDropdowns(); // ✅ Existierende Methode verwenden
this.initializePerformanceMonitoring();
}
```
**Datei:** `templates/printers.html`
**Auswirkung:** Drucker-Seite lädt ohne JavaScript-Fehler
---
### 4. PrinterMonitor Object.values() Fehler (Mittel)
**Problem:** `TypeError: Cannot convert undefined or null to object` bei `Object.values()`
**Root Cause:**
- `data.printers` war manchmal `null` oder `undefined`
- Keine Null-Prüfung vor `Object.values()` Aufruf
**Lösung:**
```javascript
// Vorher:
Object.values(data.printers).forEach(printer => {
// ❌ Crash wenn data.printers null ist
});
// Nachher:
if (data && data.printers && typeof data.printers === 'object') {
Object.values(data.printers).forEach(printer => {
// ✅ Sichere Verarbeitung
});
} else {
console.warn('⚠️ Keine gültigen Drucker-Daten erhalten:', data);
this.notifyCallbacks({
type: 'error',
message: 'Ungültige Drucker-Daten erhalten',
data: data
});
return;
}
```
**Datei:** `static/js/printer_monitor.js`
**Auswirkung:** Live-Status-Updates funktionieren zuverlässig
---
### 5. Session-Manager JSON-Parse-Fehler (Mittel)
**Problem:** `SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`
**Root Cause:**
- Server schickte HTML-Fehlerseite statt JSON
- Verursacht durch CSRF-Fehler und unbehandelte 40x-Responses
**Lösung:**
- Durch Behebung des CSRF-Token-Problems automatisch gelöst
- Zusätzliche Error-Handling-Verbesserung im Session-Manager
**Auswirkung:** Session-Status-Checks funktionieren korrekt
---
## Technische Details
### Error-Handling-Verbesserungen
**1. Robuste Null-Checks:**
```javascript
// Defensive Programming Prinzipien angewendet
if (data && data.printers && typeof data.printers === 'object') {
// Sichere Verarbeitung
}
```
**2. CSRF-Token Doppel-Sicherung:**
```javascript
// Header UND Body für maximale Kompatibilität
headers['X-CSRFToken'] = csrfToken;
body: JSON.stringify({ csrf_token: csrfToken });
```
**3. SQLAlchemy 2.0 Kompatibilität:**
```python
# Modern Session API verwenden
entity = session.get(Model, primary_key)
```
### Cascade-Analyse
**Betroffene Module:**
- ✅ Session Management
- ✅ Drucker-Monitor
- ✅ Job-Scheduler
- ✅ Database Layer
- ✅ Frontend-JavaScript
**Getestete Interaktionen:**
- ✅ Login/Logout Flows
- ✅ Drucker-Status-Updates
- ✅ Job-Erstellung
- ✅ Admin-Funktionen
- ✅ Live-Monitoring
### Performance-Impact
**Vorher:**
- 15+ Deprecation-Warnungen pro Request
- JavaScript-Crashes auf Drucker-Seite
- Session-Heartbeat Fehlerrate: ~80%
**Nachher:**
- 0 Deprecation-Warnungen
- Stabile JavaScript-Ausführung
- Session-Heartbeat Fehlerrate: <1%
## Validierung
### Funktionale Tests
- ✅ Session-Management: Heartbeat, Auto-Logout, Verlängerung
- ✅ Drucker-Management: Status-Updates, Live-Monitoring
- ✅ Job-System: Erstellung, Verwaltung, Scheduler
- ✅ Admin-Interface: User/Printer-Verwaltung
### Browser-Kompatibilität
- ✅ Chrome/Edge (Chromium)
- ✅ Firefox
- ✅ Safari (macOS/iOS)
### Performance-Tests
- ✅ Memory-Leaks: Keine erkannt
- ✅ JavaScript-Performance: Stabil
- ✅ Database-Queries: Optimiert
## Deployment-Hinweise
1. **Sofortige Wirkung:** Alle Fixes sind kompatibel mit der bestehenden Infrastruktur
2. **Keine DB-Migration:** Reine Code-Fixes, keine Schema-Änderungen
3. **Cache-Clear:** Browser-Cache leeren empfohlen für JavaScript-Updates
## Monitoring-Empfehlungen
```bash
# Log-Monitoring für verbleibende Fehler
grep -i "csrf\|legacy\|setupFilters\|undefined" logs/app/*.log
# Session-Health-Check
curl -H "X-CSRFToken: test" /api/session/status
# JavaScript-Error-Tracking im Browser
console.error = (originalError => (...args) => {
// Custom error tracking
originalError.apply(console, args);
})(console.error);
```
## Lessons Learned
1. **CSRF-Token-Standards:** Flask-WTF Header-Konventionen beachten
2. **SQLAlchemy-Updates:** Regelmäßige API-Modernisierung erforderlich
3. **JavaScript-Error-Boundaries:** Defensive Programming bei DOM-Manipulation
4. **Null-Safety:** Immer Daten-Validierung vor Object-Operationen
## Nächste Schritte
- [ ] Automatisierte Tests für Error-Scenarios erweitern
- [ ] Monitoring-Dashboard für System-Health implementieren
- [ ] Code-Review-Checkliste um Error-Handling-Patterns erweitern
---
**Bearbeitet von:** Engineering Team
**Review:** System Administrator
**Status:** ✅ Produktiv deployed

View File

@ -1,449 +0,0 @@
# Mercedes-Benz MYP - File Management System
## Übersicht
Das MYP File Management System bietet eine organisierte, sichere und skalierbare Lösung für das Hochladen, Speichern und Verwalten von Dateien in der Mercedes-Benz MYP Platform.
## Verzeichnisstruktur
Das System organisiert alle hochgeladenen Dateien in einer strukturierten Hierarchie:
```
uploads/
├── jobs/ # Druckjob-Dateien
│ ├── 2025/
│ │ ├── 01/
│ │ │ ├── user_1/
│ │ │ ├── user_2/
│ │ │ └── ...
│ │ ├── 02/
│ │ └── ...
│ └── 2024/
├── guests/ # Gastauftrags-Dateien
│ ├── 2025/
│ │ ├── 01/
│ │ └── ...
│ └── 2024/
├── avatars/ # Benutzer-Avatare
│ ├── 2025/
│ │ ├── 01/
│ │ │ ├── user_1/
│ │ │ ├── user_2/
│ │ │ └── ...
│ │ └── ...
│ └── 2024/
├── temp/ # Temporäre Dateien
├── backups/ # Backup-Dateien
├── logs/ # Exportierte Logs
└── assets/ # Statische Assets
```
## Benennungskonventionen
### Dateinamen-Schema
Alle hochgeladenen Dateien erhalten automatisch einen eindeutigen Namen:
```
{prefix}_{original_name}_{timestamp}.{extension}
```
**Beispiele:**
- `job_Druckteil_v2_20250529_143052.stl`
- `guest_Prototyp_20250529_143052.gcode`
- `avatar_profilbild_20250529_143052.jpg`
### Verzeichnis-Organisation
- **Jahr/Monat-Struktur**: `YYYY/MM/`
- **Benutzer-spezifisch**: `user_{user_id}/` für persönliche Dateien
- **Kategorie-basiert**: Trennung nach Dateityp und Verwendungszweck
## API-Endpunkte
### File Upload
#### Job-Datei hochladen
```http
POST /api/upload/job
Content-Type: multipart/form-data
Form Data:
- file: Die hochzuladende Datei
- job_name: Name des Jobs (optional)
```
**Antwort:**
```json
{
"success": true,
"message": "Datei erfolgreich hochgeladen",
"file_path": "jobs/2025/01/user_1/job_Druckteil_20250529_143052.stl",
"filename": "Druckteil.stl",
"unique_filename": "job_Druckteil_20250529_143052.stl",
"file_size": 1048576,
"metadata": {
"original_filename": "Druckteil.stl",
"uploader_id": 1,
"uploader_name": "max.mustermann",
"upload_timestamp": "2025-05-29T14:30:52.123456"
}
}
```
#### Gastauftrag-Datei hochladen
```http
POST /api/upload/guest
Content-Type: multipart/form-data
Form Data:
- file: Die hochzuladende Datei
- guest_name: Name des Gasts (optional)
- guest_email: E-Mail des Gasts (optional)
```
#### Avatar hochladen
```http
POST /api/upload/avatar
Content-Type: multipart/form-data
Form Data:
- file: Das Avatar-Bild (PNG, JPG, JPEG, GIF, WebP)
```
### File Access
#### Datei abrufen
```http
GET /api/files/{file_path}
```
**Zugriffskontrolle:**
- **Job-Dateien**: Nur Besitzer und Administratoren
- **Gast-Dateien**: Nur Administratoren
- **Avatar-Dateien**: Alle angemeldeten Benutzer
- **Andere Dateien**: Nur Administratoren
#### Datei löschen
```http
DELETE /api/files/{file_path}
```
### Admin-Funktionen
#### Datei-Statistiken abrufen
```http
GET /api/admin/files/stats
```
**Antwort:**
```json
{
"success": true,
"categories": {
"jobs": {
"file_count": 45,
"total_size": 52428800,
"total_size_mb": 50.0
},
"guests": {
"file_count": 12,
"total_size": 10485760,
"total_size_mb": 10.0
}
},
"totals": {
"file_count": 57,
"total_size": 62914560,
"total_size_mb": 60.0
}
}
```
#### Temporäre Dateien aufräumen
```http
POST /api/admin/files/cleanup
Content-Type: application/json
{
"max_age_hours": 24
}
```
## Sicherheitsfeatures
### Dateityp-Validierung
Das System erlaubt nur spezifische Dateitypen:
```python
ALLOWED_EXTENSIONS = {
'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif',
'gcode', '3mf', 'stl', 'webp'
}
```
### Dateigrößen-Limits
- **Standard-Maximum**: 16 MB
- **Konfigurierbar** über `MAX_CONTENT_LENGTH`
### Zugriffskontrolle
- **Benutzer-spezifische Isolation**: Benutzer können nur auf ihre eigenen Dateien zugreifen
- **Admin-Privilegien**: Administratoren haben Vollzugriff
- **Kategorie-basierte Beschränkungen**: Verschiedene Regeln für verschiedene Dateitypen
### Sichere Dateinamen
- **Werkzeug.secure_filename()**: Entfernt schädliche Zeichen
- **Eindeutige Timestamps**: Verhindert Namenskonflikte
- **Präfix-System**: Kategorisierung und Identifikation
## Verwendung im Code
### FileManager Klasse
```python
from utils.file_manager import file_manager
# Datei speichern
result = file_manager.save_file(
file=uploaded_file,
category='jobs',
user_id=user.id,
prefix='job',
metadata={'job_name': 'Prototyp v1'}
)
if result:
relative_path, absolute_path, metadata = result
# Pfad in Datenbank speichern
job.file_path = relative_path
```
### Convenience-Funktionen
```python
from utils.file_manager import save_job_file, save_guest_file, save_avatar_file
# Job-Datei speichern
result = save_job_file(file, user_id, metadata)
# Gast-Datei speichern
result = save_guest_file(file, metadata)
# Avatar speichern
result = save_avatar_file(file, user_id)
```
### Datei-Operationen
```python
from utils.file_manager import delete_file, get_file_info
# Datei löschen
success = delete_file('jobs/2025/01/user_1/job_test_20250529_143052.stl')
# Datei-Informationen abrufen
info = get_file_info('jobs/2025/01/user_1/job_test_20250529_143052.stl')
if info:
print(f"Dateigröße: {info['size']} Bytes")
print(f"Erstellt: {info['created']}")
```
## Wartung und Monitoring
### Automatische Bereinigung
Das System bietet automatische Bereinigung von temporären Dateien:
```python
# Dateien älter als 24 Stunden löschen
deleted_count = file_manager.cleanup_temp_files(max_age_hours=24)
```
### Statistiken und Monitoring
```python
# Kategorie-Statistiken abrufen
stats = file_manager.get_category_stats()
for category, info in stats.items():
print(f"{category}: {info['file_count']} Dateien, {info['total_size_mb']} MB")
```
### Datei-Migration
```python
# Datei in andere Kategorie verschieben
new_path = file_manager.move_file(
old_relative_path='temp/file.stl',
new_category='jobs',
new_prefix='job'
)
```
## Error Handling
Das System implementiert umfassendes Error Handling:
### Häufige Fehler
1. **Ungültiger Dateityp**
```json
{"error": "Dateityp nicht erlaubt: example.exe"}
```
2. **Datei zu groß**
```json
{"error": "Datei überschreitet maximale Größe von 16 MB"}
```
3. **Unbekannte Kategorie**
```json
{"error": "Unbekannte Kategorie: invalid_category"}
```
4. **Zugriff verweigert**
```json
{"error": "Zugriff verweigert"}
```
### Logging
Alle Datei-Operationen werden vollständig geloggt:
```
2025-05-29 14:30:52 - [APP] - INFO - Job-Datei hochgeladen: Prototyp.stl von User 1
2025-05-29 14:31:15 - [APP] - INFO - Datei gelöscht: jobs/.../old_file.stl von User 1
2025-05-29 14:32:00 - [APP] - INFO - Temporäre Dateien aufgeräumt: 5 Dateien gelöscht
```
## Performance-Optimierungen
### Async Operations
- **Non-blocking File I/O**: Datei-Operationen blockieren nicht die Hauptanwendung
- **Background Cleanup**: Automatische Bereinigung läuft im Hintergrund
### Storage Efficiency
- **Komprimierung**: Automatische Komprimierung für bestimmte Dateitypen
- **Deduplizierung**: Vermeidung von Duplikaten durch Hash-Vergleich
- **Archivierung**: Alte Dateien werden automatisch archiviert
### Caching
- **Metadata Caching**: Datei-Metadaten werden gecacht
- **Path Resolution**: Schnelle Pfad-Auflösung
## Konfiguration
### Umgebungsvariablen
```env
MYP_UPLOAD_FOLDER=/path/to/uploads
MYP_MAX_FILE_SIZE=16777216 # 16 MB in Bytes
MYP_ALLOWED_EXTENSIONS=stl,gcode,3mf,jpg,png
MYP_AUTO_CLEANUP_HOURS=24
```
### settings.py
```python
UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl'}
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
```
## Integration mit Frontend
### JavaScript Upload
```javascript
async function uploadJobFile(file, jobName) {
const formData = new FormData();
formData.append('file', file);
formData.append('job_name', jobName);
const response = await fetch('/api/upload/job', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken
}
});
return await response.json();
}
```
### Progress Tracking
```javascript
function uploadWithProgress(file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
onProgress(percentComplete);
}
});
xhr.addEventListener('load', () => {
resolve(JSON.parse(xhr.responseText));
});
xhr.open('POST', '/api/upload/job');
xhr.send(formData);
});
}
```
## Best Practices
### Für Entwickler
1. **Immer Dateityp validieren** vor dem Upload
2. **Benutzer-spezifische Pfade verwenden** für persönliche Dateien
3. **Metadaten speichern** für bessere Nachverfolgbarkeit
4. **Error Handling implementieren** für alle Datei-Operationen
5. **Cleanup-Routinen verwenden** für temporäre Dateien
### Für Administratoren
1. **Regelmäßige Backups** der Upload-Verzeichnisse
2. **Monitoring der Speichernutzung**
3. **Periodische Bereinigung** alter Dateien
4. **Sicherheitsscans** auf schädliche Dateien
5. **Access-Log-Überwachung**
## Troubleshooting
### Häufige Probleme
#### Upload schlägt fehl
```bash
# Verzeichnis-Berechtigungen prüfen
ls -la uploads/
chmod 755 uploads/
chown -R www-data:www-data uploads/
```
#### Dateien nicht gefunden
```bash
# FileManager initialisieren
python -c "from utils.file_manager import file_manager; file_manager.ensure_directories()"
```
#### Speicher voll
```bash
# Cleanup ausführen
curl -X POST http://localhost:8443/api/admin/files/cleanup \
-H "Content-Type: application/json" \
-d '{"max_age_hours": 1}'
```
## Changelog
### Version 1.0.0 (2025-05-29)
- ✅ **Grundlegendes File Management System**
- ✅ **Organisierte Verzeichnisstruktur**
- ✅ **Sicherheits-Features**
- ✅ **API-Endpunkte für Upload/Download**
- ✅ **Admin-Tools für Verwaltung**
- ✅ **Umfassende Dokumentation**
## Roadmap
### Version 1.1.0 (geplant)
- 🔄 **Datei-Versionierung**
- 🔄 **Erweiterte Metadaten**
- 🔄 **Automatische Bildoptimierung**
- 🔄 **Virus-Scanning Integration**
### Version 1.2.0 (geplant)
- 🔄 **Cloud Storage Integration**
- 🔄 **CDN Support**
- 🔄 **Advanced Caching**
- 🔄 **Datei-Sharing Features**

View File

@ -1,722 +0,0 @@
# MYP Druckerverwaltung - Installationskorrekturen
## Problembehebung der Raspberry Pi Installation
### Datum: 31.05.2025
### Status: Behoben ✅
## Identifizierte Probleme
### 1. Chromium-Browser Paketname
- **Problem**: `chromium-browser` Paket nicht verfügbar
- **Ursache**: Paketname variiert zwischen Distributionen
- **Lösung**: Dynamische Erkennung verschiedener Chromium-Paketnamen
### 2. useradd Command not found
- **Problem**: `useradd` Befehl nicht gefunden
- **Ursache**: PATH-Variable nicht korrekt gesetzt
- **Lösung**: Explizites Setzen der PATH-Variable für System-Tools
### 3. Fehlende Fehlerbehandlung
- **Problem**: Installation bricht bei ersten Fehlern ab
- **Ursache**: Unzureichende Fehlerbehandlung
- **Lösung**: Robuste Fallback-Mechanismen implementiert
## Implementierte Verbesserungen
### 📦 Paket-Installation
```bash
# Vor der Korrektur
apt-get install -y chromium-browser
# Nach der Korrektur
if apt-get install -y chromium 2>/dev/null; then
log "✅ Chromium erfolgreich installiert"
elif apt-get install -y chromium-browser 2>/dev/null; then
log "✅ Chromium-Browser erfolgreich installiert"
else
warning "⚠️ Chromium konnte nicht automatisch installiert werden"
fi
```
### 👤 Benutzer-Erstellung
```bash
# Vor der Korrektur
useradd -m -s /bin/bash "$APP_USER"
# Nach der Korrektur
if ! useradd -m -s /bin/bash "$APP_USER" 2>/dev/null; then
warning "Fehler bei useradd - versuche adduser..."
if ! adduser --disabled-password --gecos "" "$APP_USER" 2>/dev/null; then
error "Konnte Benutzer '$APP_USER' nicht erstellen. System-Tools prüfen."
fi
fi
```
### 🔧 Chromium-Binary Erkennung
```bash
# Dynamische Erkennung des Chromium-Pfads
CHROMIUM_BIN=""
for chromium_path in "/usr/bin/chromium" "/usr/bin/chromium-browser" "/snap/bin/chromium"; do
if [ -x "$chromium_path" ]; then
CHROMIUM_BIN="$chromium_path"
log "Chromium gefunden: $CHROMIUM_BIN"
break
fi
done
```
### 🔍 System-Tools Validierung
```bash
# Prüfe kritische Befehle vor Verwendung
for cmd in useradd usermod systemctl apt-get; do
if ! command -v "$cmd" &> /dev/null; then
error "Erforderlicher Befehl '$cmd' nicht gefunden. PATH: $PATH"
fi
done
```
## Neue Wartungstools
### 🔧 myp-repair
Automatisches Reparatur-Tool für häufige Probleme:
- Prüft und repariert Services
- Erstellt fehlende Benutzer nach
- Installiert fehlende Pakete
- Korrigiert Berechtigungen
```bash
sudo myp-repair
```
### 🔍 myp-maintenance diagnose
Umfassendes Diagnose-Tool:
- System-Informationen
- Service-Status
- Port-Belegung
- Benutzer-Konfiguration
- Letzte Logs
```bash
myp-maintenance diagnose
```
## Getestete Umgebungen
- ✅ Debian 12 (Bookworm)
- ✅ Ubuntu 22.04 LTS
- ✅ Raspberry Pi OS (64-bit)
- ✅ Systeme mit/ohne vorinstalliertem Chromium
## Backup und Wiederherstellung
### Automatische Backups
- Täglich um 2:00 Uhr
- 30 Tage Aufbewahrung
- Komprimierte Datenbank und Konfiguration
### Notfall-Wiederherstellung
```bash
# Im Schnellstart-Skript verfügbar
sudo myp-notfall-reset
```
## Sicherheitsverbesserungen
1. **Berechtigungen**: Strikte Benutzer-/Gruppentrennung
2. **Firewall**: Automatische UFW-Konfiguration
3. **Services**: Isolation und Überwachung
4. **Backups**: Automatische Datensicherung
## Installation ausführen
```bash
# Vollständige Installation
sudo ./schnellstart_raspberry_pi.sh
# Bei Problemen: Reparatur
sudo myp-repair
# Status prüfen
myp-maintenance status
myp-maintenance diagnose
```
## Troubleshooting
### Problem: Services starten nicht
```bash
sudo myp-repair
sudo myp-maintenance restart
```
### Problem: Kiosk-Modus funktioniert nicht
```bash
# Chromium prüfen
myp-maintenance diagnose
# Kiosk neu starten
myp-maintenance kiosk-restart
```
### Problem: Benutzer fehlen
```bash
sudo myp-repair
```
## Kontakt
Bei anhaltenden Problemen:
1. Diagnose ausführen: `myp-maintenance diagnose`
2. Logs sammeln: `myp-maintenance logs`
3. Reparatur versuchen: `sudo myp-repair`
---
**Dokumentation erstellt**: 31.05.2025
**Letzte Aktualisierung**: 31.05.2025
**Version**: 2.0.0
# Installation Korrekturen - Node.js/NPM-Fehler behoben
## Datum: 31.05.2025
## Problem: npm: command not found
### 🔍 Problem-Analyse
**Symptom**: Installation schlägt fehl mit Fehler `npm: command not found`
**Ursache**:
- Node.js-Installation fehlgeschlagen oder unvollständig
- NodeSource-Repository nicht erreichbar
- Keine Fallback-Mechanismen für alternative Installationsmethoden
- Skript bricht ab, obwohl npm optional ist
### ✅ Implementierte Lösungen
#### 1. Robuste Node.js-Installation mit Multi-Fallback
**Neue Installationsmethoden (in Reihenfolge)**:
1. **NodeSource LTS**: Standard-Repository für aktuelle LTS-Version
2. **NodeSource 18.x**: Stabile Version 18.x als Fallback
3. **Standard-Repository**: Debian/Ubuntu Standard-Pakete
4. **Snap-Installation**: Containerisierte Node.js-Installation
5. **Manuelle Installation**: Download und Installation von nodejs.org
```bash
# Methode 1: NodeSource LTS Repository
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y nodejs
# Methode 2: NodeSource 18.x (stabil)
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
# Methode 3: Standard Repository
apt-get install -y nodejs npm
# Methode 4: Snap Installation
snap install node --classic
# Methode 5: Manuelle Installation
wget "https://nodejs.org/dist/v18.17.0/node-v18.17.0-linux-x64.tar.xz"
tar -xf node-v18.17.0-linux-x64.tar.xz
cp -r node-v18.17.0-linux-x64/* /usr/local/
```
#### 2. Intelligente NPM-Verfügbarkeitsprüfung
**Vor jeder NPM-Nutzung**:
```bash
if command -v npm &> /dev/null && npm --version &> /dev/null; then
# NPM verfügbar - normale Installation
else
# NPM nicht verfügbar - Fallback-Mechanismen
fi
```
#### 3. Dummy-NPM bei Installation-Fehlschlag
**Falls Node.js-Installation komplett fehlschlägt**:
```bash
# Erstelle Dummy-npm-Kommando um Skript-Fehler zu vermeiden
cat > /usr/local/bin/npm << 'EOF'
#!/bin/bash
echo "NPM nicht verfügbar - Node.js-Installation fehlgeschlagen"
echo "Node.js-Abhängigkeiten werden übersprungen"
exit 0
EOF
chmod +x /usr/local/bin/npm
```
#### 4. Erweiterte NPM-Installation mit Fallbacks
**Robuste package.json-Verarbeitung**:
```bash
# Primär: Standard npm install
sudo -u "$APP_USER" npm install
# Fallback 1: Ohne Cache
sudo -u "$APP_USER" npm install --no-cache
# Fallback 2: Forcierte Installation
sudo -u "$APP_USER" npm install --force
# Fallback 3: CSS-Fallback bei Build-Fehlern
```
#### 5. Fallback-CSS-System
**Falls Tailwind-Build fehlschlägt oder NPM nicht verfügbar**:
**Einfaches Fallback-CSS**:
```css
/* Fallback CSS - NPM-Installation fehlgeschlagen */
body { font-family: system-ui, sans-serif; margin: 0; padding: 0; }
```
**Umfangreiches Fallback-CSS** (bei komplettem NPM-Ausfall):
```css
/* Vollständiges Basis-Styling für MYP-Anwendung */
body { font-family: system-ui, -apple-system, sans-serif; ... }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.btn { display: inline-block; padding: 8px 16px; background: #007bff; ... }
.alert { padding: 12px; margin: 10px 0; border-radius: 4px; ... }
.table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.form-control { width: 100%; padding: 8px 12px; border: 1px solid #ced4da; ... }
.card { background: white; border: 1px solid #dee2e6; ... }
.navbar { background: #343a40; color: white; ... }
```
#### 6. NPM Global-Konfiguration
**Bessere Berechtigungen bei erfolgreicher Installation**:
```bash
# NPM Global-Verzeichnis konfigurieren
mkdir -p /usr/local/lib/npm-global
npm config set prefix '/usr/local/lib/npm-global'
echo 'export PATH=/usr/local/lib/npm-global/bin:$PATH' >> /etc/profile
export PATH=/usr/local/lib/npm-global/bin:$PATH
```
### 🔧 Implementierungsdetails
#### install_packages() - Node.js-Installation
**Vorher**:
```bash
# Node.js installieren
progress "Installiere Node.js..."
if ! command -v node &> /dev/null; then
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y nodejs
fi
```
**Nachher**:
```bash
# Node.js installieren - VERBESSERTE VERSION
progress "Installiere Node.js mit mehreren Fallback-Methoden..."
# Prüfe ob Node.js bereits verfügbar ist
if command -v node &> /dev/null && command -v npm &> /dev/null; then
info "Node.js bereits verfügbar: $(node --version)"
info "NPM bereits verfügbar: $(npm --version)"
else
# Methode 1: NodeSource Repository (LTS)
progress "Versuche NodeSource LTS Repository..."
if curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && apt-get install -y nodejs; then
log "✅ Node.js via NodeSource LTS installiert"
else
# ... weitere Fallback-Methoden
fi
# Finale Validierung
if command -v node &> /dev/null && command -v npm &> /dev/null; then
log "✅ Node.js erfolgreich installiert: $(node --version)"
log "✅ NPM erfolgreich installiert: $(npm --version)"
# NPM Global-Konfiguration
else
warning "⚠️ Node.js/NPM-Installation fehlgeschlagen - Features werden übersprungen"
# Dummy-npm erstellen
fi
fi
```
#### install_application() - NPM-Nutzung
**Vorher**:
```bash
# Node.js Dependencies
if [ -f "package.json" ]; then
progress "Installiere Node.js Dependencies..."
sudo -u "$APP_USER" npm install
if [ -f "tailwind.config.js" ]; then
sudo -u "$APP_USER" npm run build:css || true
fi
fi
```
**Nachher**:
```bash
# Node.js Dependencies - VERBESSERTE VERSION
if [ -f "package.json" ]; then
progress "Installiere Node.js Dependencies..."
# Prüfe ob npm verfügbar ist
if command -v npm &> /dev/null && npm --version &> /dev/null; then
info "NPM verfügbar: $(npm --version)"
# Versuche npm install mit verschiedenen Methoden
if sudo -u "$APP_USER" npm install; then
log "✅ Node.js Dependencies installiert"
# Tailwind-Build mit Fallback
else
# Alternative Installationsmethoden
# CSS-Fallback bei Fehlschlag
fi
else
warning "⚠️ NPM nicht verfügbar - Dependencies werden übersprungen"
# Umfangreiches Fallback-CSS erstellen
fi
else
info "Keine package.json gefunden - Node.js-Dependencies werden übersprungen"
fi
```
### 🎯 Resultat
#### Robustheit
- **Installation schlägt nie aufgrund von NPM-Fehlern fehl**
- **Mehrere Fallback-Methoden** für verschiedene Umgebungen
- **Intelligente Fehlerbehandlung** ohne Skript-Abbruch
#### Kompatibilität
- **Raspberry Pi OS**: NodeSource + Standard-Repository
- **Ubuntu Server**: NodeSource + Snap
- **Debian Minimal**: Manuelle Installation + Fallback-CSS
- **Eingeschränkte Umgebungen**: Dummy-NPM + vollständiges CSS
#### Funktionalität
- **Mit NPM**: Vollständige Tailwind-CSS-Kompilation
- **Ohne NPM**: Funktionales Fallback-CSS für alle UI-Komponenten
- **Teilweise NPM**: Robuste Behandlung partieller Installationen
### 📋 Validierung
**Test-Szenarien**:
1. ✅ **Erfolgreiche NodeSource-Installation**: Normale npm-Installation
2. ✅ **NodeSource-Fehlschlag**: Fallback auf Standard-Repository
3. ✅ **Alle Repository-Fehler**: Manuelle Installation via wget
4. ✅ **Kompletter Node.js-Ausfall**: Dummy-npm + CSS-Fallback
5. ✅ **NPM verfügbar, aber defekt**: Alternative Install-Flags
6. ✅ **Tailwind-Build-Fehler**: CSS-Fallback für funktionale UI
**Ergebnis**:
- **Installation funktioniert in allen Szenarien**
- **MYP-Anwendung startet erfolgreich**
- **UI bleibt funktional und ansprechend**
### 🔄 Backup-Plan
Falls weiterhin Node.js-Probleme auftreten:
#### Manuelle Node.js-Installation
```bash
# Vor dem Hauptskript ausführen
wget https://nodejs.org/dist/v18.17.0/node-v18.17.0-linux-x64.tar.xz
tar -xf node-v18.17.0-linux-x64.tar.xz
sudo cp -r node-v18.17.0-linux-x64/* /usr/local/
```
#### NPM komplett deaktivieren
```bash
# In install_raspberry_pi.sh, Zeile nach "Node.js installieren"
echo "NPM deaktiviert" > /usr/local/bin/npm
chmod +x /usr/local/bin/npm
```
#### CSS manuell bereitstellen
```bash
# CSS-Datei direkt in static/css/ platzieren vor Installation
mkdir -p static/css/
cp tailwind-backup.css static/css/tailwind.css
```
---
**Installation korrigiert**: 31.05.2025
**Node.js/NPM-Fehler**: Vollständig behoben ✅
**Getestet auf**: Raspberry Pi OS, Ubuntu Server, Debian
**Status**: Production-Ready
---
# Erweiterte Installation - Version 3.1.0
## Datum: 31.05.2025
## Neue Features: Hostname, Root-Access, Zertifikate, Direkte Python-Installation
### 🚀 Neue Systemkonfiguration
#### 1. Automatische Hostname-Konfiguration
**Gesetzt auf**: `raspberrypi`
```bash
# Hostname in /etc/hostname setzen
echo "raspberrypi" > /etc/hostname
# /etc/hosts aktualisieren
sed -i "s/127.0.1.1.*/127.0.1.1\traspberrypi/" /etc/hosts
# Hostname sofort anwenden
hostnamectl set-hostname "raspberrypi"
```
#### 2. Root-Passwort-Konfiguration
**Root-Passwort**: `744563017196A`
```bash
# Root-Passwort automatisch setzen
echo "root:744563017196A" | chpasswd
# SSH-Root-Zugang aktivieren für Wartung
sed -i 's/#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
```
#### 3. Lokalisierung und Zeitzone
```bash
# Deutsche Zeitzone
timedatectl set-timezone Europe/Berlin
# Deutsche Locales
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
update-locale LANG=de_DE.UTF-8
```
### 🔒 Zertifikat-Management
#### CA-Zertifikate installieren
```bash
# System-CA-Zertifikate aktualisieren
apt-get install -y ca-certificates
update-ca-certificates
# Mozilla CA Bundle hinzufügen
wget -O /usr/local/share/ca-certificates/cacert.pem https://curl.se/ca/cacert.pem
update-ca-certificates
# Python certifi aktualisieren
python3 -m pip install --upgrade certifi --break-system-packages
```
### 📁 Vollständige Verzeichnisstruktur
#### Upload-Ordner mit Jahres-/Monats-Struktur
```bash
# Automatische Erstellung für aktuelles Jahr/Monat
CURRENT_YEAR=$(date +%Y)
CURRENT_MONTH=$(date +%m)
# Upload-Kategorien
for category in assets avatars backups guests jobs logs temp; do
mkdir -p "/opt/myp-druckerverwaltung/uploads/$category/$CURRENT_YEAR/$CURRENT_MONTH"
done
```
#### Log-Verzeichnisse
```bash
# Anwendungs-Logs
for log_cat in app auth errors jobs printers scheduler; do
mkdir -p "/opt/myp-druckerverwaltung/logs/$log_cat"
mkdir -p "/var/log/myp-$log_cat"
done
```
#### Weitere Verzeichnisse
```bash
mkdir -p /opt/myp-druckerverwaltung/database/backups
mkdir -p /opt/myp-druckerverwaltung/config
mkdir -p /opt/myp-druckerverwaltung/static/{css,js,icons}
mkdir -p /opt/myp-druckerverwaltung/certs
```
### 🐍 Python ohne Virtual Environment
#### Direkte System-Installation
**WICHTIGER CHANGE**: Kein Virtual Environment mehr!
```bash
# Direkt ins System installieren mit --break-system-packages
python3 -m pip install --upgrade pip --break-system-packages
# Requirements direkt installieren
python3 -m pip install -r requirements.txt --break-system-packages
# Oder Basis-Pakete
python3 -m pip install --break-system-packages \
flask flask-login flask-wtf flask-limiter \
sqlalchemy werkzeug requests gunicorn \
bcrypt cryptography PyP100 \
python-dotenv Pillow schedule
```
#### Systemd-Service ohne venv
**Neue Service-Konfiguration**:
```ini
[Unit]
Description=MYP Druckerverwaltung Flask Application
After=network.target
[Service]
Type=simple
User=myp
Group=myp
WorkingDirectory=/opt/myp-druckerverwaltung
Environment=PATH=/usr/local/bin:/usr/bin:/bin
Environment=PYTHONPATH=/opt/myp-druckerverwaltung
ExecStart=/usr/bin/python3 /opt/myp-druckerverwaltung/app.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
### 🔧 Engine-Import-Problem behoben
#### models.py Korrekturen
```python
# Automatisch hinzugefügt falls fehlt
from sqlalchemy import create_engine
# Engine-Variable mit Fallback
try:
engine = create_optimized_engine()
except:
from sqlalchemy import create_engine
engine = create_engine('sqlite:///database.db')
```
#### app.py Korrekturen
```python
# Engine-Import sicherstellen
try:
from models import engine
db = engine
except ImportError:
from sqlalchemy import create_engine
db = create_engine('sqlite:///database.db')
```
### 📋 Erweiterte Dateiberechtigungen
#### Systematische Berechtigungs-Konfiguration
```bash
# Basis-Verzeichnisse
chown -R myp:myp /opt/myp-druckerverwaltung
chown -R myp:myp /opt/myp-backups
# Upload-Ordner für Web-Server
chown -R myp:www-data /opt/myp-druckerverwaltung/uploads
chown -R myp:www-data /opt/myp-druckerverwaltung/static
# Verzeichnis-Berechtigungen
find /opt/myp-druckerverwaltung -type d -exec chmod 755 {} \;
# Datei-Berechtigungen
find /opt/myp-druckerverwaltung -type f -exec chmod 644 {} \;
# Ausführbare Dateien
chmod 755 /opt/myp-druckerverwaltung/app.py
# Sichere Config-Dateien
chmod 600 /opt/myp-druckerverwaltung/.env
# System-Logs
for log_cat in app auth errors jobs printers scheduler; do
chown -R syslog:adm "/var/log/myp-$log_cat"
chmod 755 "/var/log/myp-$log_cat"
done
```
### 🚀 Vollständiger System-Update-Prozess
#### Erweiterte Pakete
```bash
# System-Update vor Installation
apt-get update -y
apt-get upgrade -y
apt-get dist-upgrade -y
# Essenzielle Tools
apt-get install -y \
ca-certificates gnupg lsb-release \
software-properties-common \
apt-transport-https \
curl wget git unzip nano htop rsync \
sudo cron logrotate tree zip
```
### 🎯 Neue Phasen-Struktur
**Installation jetzt in erweiterten Phasen**:
- **Phase 0**: System-Grundkonfiguration (Hostname, Root, Zeitzone)
- **Phase 0.5**: System-Update (Pakete, Kernel, Tools)
- **Phase 0.8**: Zertifikat-Installation
- **Phase 1**: System-Bereinigung
- **Phase 1.5**: Verzeichnisstruktur erstellen
- **Phase 2**: Paket-Installation
- **Phase 3**: Chromium-Installation
- **Phase 4**: Benutzer-Erstellung
- **Phase 5**: Anwendungs-Installation (ohne venv)
- **Phase 5.5**: Dateiberechtigungen setzen
- **Phase 6**: Kiosk-Konfiguration
- **Phase 7**: Autostart-Konfiguration
- **Phase 8**: Sicherheits-Konfiguration
- **Phase 9**: Wartungstools
- **Phase 10**: Finalisierung
### 🔗 Integration mit bestehenden Features
- ✅ **7-fache Autostart-Absicherung**: Bleibt erhalten
- ✅ **Node.js Multi-Fallback**: Verbessert mit npm global config
- ✅ **Chromium Multi-Fallback**: APT → Snap → Flatpak
- ✅ **Wartungstools**: myp-maintenance, myp-backup, myp-emergency-reset
- ✅ **Service-Monitoring**: Erweitert mit System-Health-Checks
- ✅ **Umfassende Logging**: Structured Logs in separaten Verzeichnissen
### 📖 Verwendung
```bash
# Einfache Installation (empfohlen)
sudo ./schnellstart_raspberry_pi.sh
# Erweiterte Installation
sudo ./install_raspberry_pi.sh
# Nach Installation: System neustarten
sudo reboot
# Wartung und Status
myp-maintenance status
myp-maintenance check-health
```
### 🎉 Neue Funktionalität
**System ist jetzt**:
- ✅ **Produktions-ready** mit vollem Root-Zugang
- ✅ **SSL/TLS-sicher** mit aktuellen Zertifikaten
- ✅ **Voll strukturiert** mit korrekter Verzeichnishierarchie
- ✅ **Python-optimiert** ohne Virtual Environment Overhead
- ✅ **Import-sicher** mit behobenen Engine-Problemen
- ✅ **Berechtigungs-konform** mit Web-Server-Integration
- ✅ **Monitoring-ready** mit umfassendem Health-System
---
**Erweiterte Installation**: 31.05.2025
**Version**: 3.1.0 - Production-Ready Extended
**Status**: Alle Anforderungen implementiert ✅

View File

@ -1,138 +0,0 @@
# Keymap-Probleme Behoben
## Problem-Beschreibung
Das ursprüngliche Installationsskript hatte Probleme mit der deutschen Tastaturlayout-Konfiguration, insbesondere:
- `localectl` konnte keine Keymaps lesen
- Fehlende deutsche Keymap-Dateien
- Unvollständige keyboard-configuration-Pakete
- Fehlerhafte systemd-localed-Konfiguration
## Implementierte Lösung
### 1. Erweiterte Paket-Installation
```bash
# Vollständige Keyboard-Unterstützung
apt-get install -y \
keyboard-configuration \
console-setup \
console-data \
kbd \
console-common \
xkb-data \
locales
```
### 2. Debconf-Vorkonfiguration
```bash
# Automatische Konfiguration ohne Benutzerinteraktion
echo "keyboard-configuration keyboard-configuration/layout select German" | debconf-set-selections
echo "keyboard-configuration keyboard-configuration/layoutcode string de" | debconf-set-selections
echo "keyboard-configuration keyboard-configuration/model select Generic 105-key (Intl) PC" | debconf-set-selections
```
### 3. Keymap-Verzeichnis-Reparatur
- Erstellt fehlende Keymap-Verzeichnisse
- Prüft auf vorhandene deutsche Keymaps
- Erstellt Fallback-Keymap falls nötig
### 4. localectl-Reparatur
```bash
# Startet systemd-localed Service
systemctl start systemd-localed
systemctl enable systemd-localed
# Testet und repariert localectl-Funktionalität
if localectl status &> /dev/null; then
localectl set-keymap de
localectl set-x11-keymap de
fi
```
### 5. Multiple Fallback-Methoden
1. **Primär**: localectl (systemd)
2. **Sekundär**: /etc/default/keyboard
3. **Tertiär**: /etc/vconsole.conf
4. **Fallback**: Manuelle Keymap-Erstellung
### 6. Console-Setup-Integration
```bash
# Console-Setup konfigurieren
cat > "/etc/default/console-setup" << EOF
ACTIVE_CONSOLES="/dev/tty[1-6]"
CHARMAP="UTF-8"
CODESET="guess"
FONTFACE="Fixed"
FONTSIZE="8x16"
EOF
# Setupcon ausführen
setupcon --force --save
```
## Neue Funktion: `fix_keymap_issues()`
Diese Funktion wird in Phase 0.3 der Installation ausgeführt und:
1. ✅ Installiert alle keyboard-bezogenen Pakete
2. ✅ Generiert deutsche Locales
3. ✅ Prüft und repariert Keymap-Verzeichnisse
4. ✅ Erstellt Fallback-Keymap falls nötig
5. ✅ Testet Keymap-Funktionalität
6. ✅ Repariert localectl-Konfiguration
7. ✅ Konfiguriert vconsole.conf
8. ✅ Aktualisiert initramfs
## Fehlerbehandlung
- **Graceful Degradation**: Bei Fehlern wird auf alternative Methoden zurückgegriffen
- **Umfassende Logging**: Alle Schritte werden protokolliert
- **Fallback-Keymaps**: Manuelle Erstellung wenn Pakete fehlen
- **Service-Recovery**: Automatischer Neustart von systemd-localed
## Getestete Systeme
- ✅ Raspberry Pi OS (Debian-basiert)
- ✅ Ubuntu Server 20.04+
- ✅ Debian 11+ (Bullseye)
- ✅ Systeme ohne vorinstallierte Desktop-Umgebung
## Referenzen
- [Claudios Blog: Missing Keymaps Fix](https://www.claudiokuenzler.com/blog/1257/how-to-fix-missing-keymaps-debian-ubuntu-localectl-failed-read-list)
- [Debian Wiki: Keyboard Configuration](https://wiki.debian.org/Keyboard)
- [systemd.org: localectl](https://www.freedesktop.org/software/systemd/man/localectl.html)
## Wartung
Das Skript erstellt automatisch:
- `/etc/vconsole.conf` für systemd-Systeme
- `/etc/default/keyboard` für X11/Console
- `/etc/default/console-setup` für Console-Setup
- Fallback-Keymap in `/usr/share/keymaps/i386/qwertz/de.kmap.gz`
Bei Problemen nach der Installation:
```bash
# Keymap manuell laden
sudo loadkeys de
# localectl-Status prüfen
sudo localectl status
# Console-Setup neu konfigurieren
sudo dpkg-reconfigure keyboard-configuration
```
---
**Status**: ✅ Behoben
**Datum**: $(date +%Y-%m-%d)
**Version**: 2.0 (Erweiterte Keymap-Unterstützung)

View File

@ -1,456 +0,0 @@
# MYP Druckerverwaltung - Finale Kiosk-Installation
## Vollautomatische Raspberry Pi Kiosk-Lösung
### Datum: 31.05.2025
### Status: Production-Ready ✅
## Übersicht
Die MYP Druckerverwaltung verfügt jetzt über ein vollautomatisches Kiosk-Installationssystem, das ein **echtes, sicheres Kiosk-System ohne Escape-Möglichkeiten** erstellt.
## 🚀 Installation
### Einfacher Start (Empfohlen)
```bash
# Im MYP-Projektverzeichnis
sudo ./schnellstart_raspberry_pi.sh
```
### Erweiterte Installation
```bash
# Für manuelle Kontrolle
sudo ./install_raspberry_pi.sh
```
## 🔒 Sicherheits-Features
### Kiosk-Sicherheit
- **Kein Desktop-Escape**: Alle Tastenkombinationen deaktiviert
- **Vollbild-Zwang**: Chromium startet zwangsweise im Kiosk-Modus
- **Browser-Beschränkungen**: Entwicklertools, Extensions und Menüs deaktiviert
- **Openbox-Lockdown**: Fenstermanager ohne Shortcuts oder Menüs
### System-Sicherheit
- **SSH deaktiviert**: Standardmäßig für maximale Sicherheit
- **Firewall aktiv**: UFW mit Fail2Ban-Integration
- **Desktop-Bereinigung**: Alle unnötigen Desktop-Umgebungen entfernt
- **Benutzer-Isolation**: Separate Benutzer für App und Kiosk
### Auto-Login-Sicherheit
- **LightDM Auto-Login**: Sicherer automatischer Login für Kiosk-Benutzer
- **Session-Isolation**: Kiosk-Benutzer ohne sudo-Berechtigung
- **PAM-Integration**: Sichere Authentifizierung ohne Passwort-Eingabe
- **TTY-Fallback**: Getty Auto-Login als Backup bei LightDM-Fehlern
### 7-fache Autostart-Absicherung
- **1. LightDM Auto-Login**: Primärer Autostart-Mechanismus
- **2. Systemd User-Service**: User-Session-basierter Autostart
- **3. Bashrc Autostart**: Shell-basierter Autostart bei Login
- **4. Profile Autostart**: System-Profile-basierter Autostart
- **5. XDG Desktop Autostart**: Desktop-Environment-Autostart
- **6. Cron Watchdog**: Überwachung und Neustart alle 2 Minuten
- **7. RC.Local Fallback**: System-Boot-Fallback-Mechanismus
### Chromium-Sicherheits-Flags
```bash
--kiosk --no-sandbox --disable-web-security
--disable-extensions --disable-dev-shm-usage
--disable-hang-monitor --disable-popup-blocking
--disable-infobars --disable-session-crashed-bubble
--disable-restore-session-state --noerrdialogs
--no-first-run --no-default-browser-check
--start-fullscreen --window-position=0,0
--app=http://localhost:5000
```
## 🛠️ System-Architektur
### Benutzer-Structure
- **`myp`**: Anwendungsbenutzer (Flask-App)
- **`kiosk`**: Kiosk-Benutzer (X11 + Chromium, Auto-Login)
### Verzeichnis-Structure
- **`/opt/myp-druckerverwaltung`**: Hauptanwendung
- **`/opt/myp-backups`**: Automatische Backups
- **`/home/kiosk/.config/openbox`**: Kiosk-Konfiguration
- **`/home/kiosk/.config/systemd/user`**: User-Service-Autostart
- **`/home/kiosk/.config/autostart`**: XDG-Autostart-Konfiguration
- **`/var/log/myp-kiosk-install.log`**: Installations-Log
### Systemd-Services
- **`myp-druckerverwaltung.service`**: Flask-Anwendung
- **`myp-display.service`**: LightDM-Management und -Überwachung
- **`myp-kiosk-monitor.service`**: Kontinuierliche Kiosk-Überwachung + Recovery
- **`nginx.service`**: Reverse-Proxy
- **`lightdm.service`**: Display Manager mit Auto-Login
- **`kiosk-watchdog.service`**: Service-Überwachung und Neustart
### Auto-Login-System
- **LightDM**: Primärer Display Manager mit Auto-Login für Kiosk-Benutzer
- **Getty Fallback**: TTY1 Auto-Login als Backup bei LightDM-Fehlern
- **PAM-Integration**: Sichere Authentifizierung ohne Passwort-Eingabe
- **Session-Management**: systemd-logind für robuste Session-Verwaltung
### Monitoring & Recovery
- **Health-Checks**: Alle 10 Minuten automatisch
- **Resource-Monitoring**: CPU, RAM, Disk alle 5 Minuten
- **Service-Überwachung**: Kontinuierliche Überwachung aller kritischen Services
- **Auto-Recovery**: Automatischer Neustart bei Service-Fehlern
- **Cron-Watchdog**: Zusätzliche Überwachung alle 2 Minuten
## 🔧 Wartungstools
### myp-maintenance
Haupt-Wartungstool für alle Kiosk-Operationen:
```bash
# Service-Management
myp-maintenance start # Alle Services starten
myp-maintenance stop # Alle Services stoppen
myp-maintenance restart # Services neustarten
myp-maintenance status # Detaillierter Status aller Services
# Einzelne Services
myp-maintenance app-restart # Nur Anwendung neustarten
myp-maintenance kiosk-restart # Nur Kiosk-Display neustarten
myp-maintenance monitor-restart # Nur Kiosk-Monitor neustarten
# Logs und Monitoring
myp-maintenance logs # Live Anwendungs-Logs
myp-maintenance kiosk-logs # Live Kiosk-Logs (Monitor + LightDM + Session)
myp-maintenance check-health # System-Gesundheitscheck
myp-maintenance auto-fix # Automatische Problemreparatur
# Kiosk-Kontrolle
myp-maintenance exit-kiosk # Kiosk beenden (Passwort: 744563017196A)
myp-maintenance enter-kiosk # Kiosk-Modus aktivieren
# Remote-Zugang
myp-maintenance enable-ssh # SSH für Wartung aktivieren
myp-maintenance disable-ssh # SSH wieder deaktivieren
```
### myp-backup
Automatisches Backup-System:
```bash
myp-backup # Manuelles Backup erstellen
```
**Automatische Backups:**
- **Zeitplan**: Täglich um 2:00 Uhr
- **Aufbewahrung**: 30 Tage
- **Inhalt**: Datenbank, Konfiguration, Uploads
### myp-emergency-reset
Notfall-Tool für Problemsituationen:
```bash
myp-emergency-reset # Stoppt Kiosk, aktiviert SSH
```
**Verwendung bei Problemen:**
1. Console-Zugang: `Strg+Alt+F1` bis `F6`
2. Login als Root oder mit sudo-Berechtigung
3. `myp-emergency-reset` ausführen
4. Bestätigung mit "RESET" eingeben
5. SSH ist dann für Remote-Wartung verfügbar
## 📋 Installations-Prozess
### Phase 1: System-Bereinigung
- Entfernung aller Desktop-Umgebungen (GNOME, KDE, XFCE, etc.)
- Deinstallation unnötiger Software (Firefox, LibreOffice, etc.)
- Service-Bereinigung (GDM, LightDM, etc.)
### Phase 2: Paket-Installation
- Basis-System: Python3, Node.js, Git, Build-Tools
- X11-System: Xorg, Openbox, Audio-Support
- Sicherheit: UFW, Fail2Ban, Unattended-Upgrades
### Phase 3: Chromium-Installation
Robuste Multi-Fallback-Installation:
1. **APT**: `chromium` oder `chromium-browser`
2. **Snap**: `snap install chromium`
3. **Flatpak**: `flatpak install org.chromium.Chromium`
### Phase 4: Benutzer-Erstellung
- App-Benutzer (`myp`) mit sudo-Berechtigung
- Kiosk-Benutzer (`kiosk`) ohne sudo, aber mit Audio/Video-Gruppen
### Phase 5: Anwendungs-Installation
- Python Virtual Environment
- Dependencies (Flask, SQLAlchemy, PyP100, etc.)
- Node.js Dependencies (falls vorhanden)
- Datenbank-Initialisierung
### Phase 6: Kiosk-Konfiguration
- Openbox-Konfiguration ohne Shortcuts/Menüs
- Chromium-Startskript mit Sicherheits-Flags
- Autostart-Mechanismen
### Phase 7: Autostart-Konfiguration
- Systemd-Services für App und Kiosk
- Nginx-Reverse-Proxy mit Security-Headers
- Graphical-Target als Standard
### Phase 8: Sicherheits-Konfiguration
- Firewall-Regeln (SSH + HTTP)
- Fail2Ban für Brute-Force-Schutz
- Automatische Updates
- Benutzer-Einschränkungen
### Phase 9: Wartungstools
- myp-maintenance Haupt-Tool
- myp-backup Backup-System
- myp-emergency-reset Notfall-Tool
- Cron-Jobs für automatische Backups
### Phase 10: Finalisierung
- Service-Tests und -Validierung
- Chromium-Funktionstest
- Berechtigungs-Finalisierung
## 🖥️ Nach der Installation
### Automatischer Boot-Prozess
1. **System-Boot**: Debian/Ubuntu startet normal
2. **Systemd-Target**: Wechselt zu `graphical.target`
3. **Service-Start**: `myp-druckerverwaltung.service` startet Flask-App
4. **Kiosk-Start**: `myp-kiosk.service` startet X11 + Chromium
5. **Vollbild-Kiosk**: Chromium öffnet MYP-App im Vollbild
### Benutzer-Erfahrung
- **Boot-to-App**: Direkter Start der MYP-Anwendung
- **Kein Desktop**: Nutzer sehen nur die MYP-Oberfläche
- **Keine Escape-Möglichkeit**: Tastenkombinationen sind deaktiviert
- **Automatische Wiederherstellung**: Bei Crashes automatischer Neustart
## 🔍 Troubleshooting
### Häufige Probleme
#### System hängt beim Login-Screen
```bash
# Auto-Login-Konfiguration prüfen
cat /etc/lightdm/lightdm.conf | grep autologin
# LightDM-Status prüfen
systemctl status lightdm
# Getty-Fallback testen
systemctl status getty@tty1
# Display-Manager neustarten
myp-maintenance kiosk-restart
# Notfall: Getty Auto-Login aktivieren
systemctl enable getty@tty1
```
#### Auto-Login funktioniert nicht
```bash
# Kiosk-Benutzer prüfen
id kiosk
groups kiosk
# LightDM-Konfiguration validieren
lightdm --test-mode --debug
# PAM-Konfiguration prüfen
cat /etc/pam.d/lightdm-autologin
# Session-Logs prüfen
tail -f /var/log/kiosk-session.log
# Getty-Fallback aktivieren
systemctl enable getty@tty1
systemctl start getty@tty1
```
#### Kiosk startet nicht
```bash
# Umfassender Status-Check
myp-maintenance status
# Gesundheitscheck mit Auto-Fix
myp-maintenance check-health
myp-maintenance auto-fix
# Einzelne Services prüfen
systemctl status myp-druckerverwaltung
systemctl status lightdm
systemctl status myp-kiosk-monitor
# Logs analysieren
myp-maintenance kiosk-logs
# Service manuell starten
systemctl start myp-display
```
#### Service-Monitoring-Probleme
```bash
# Monitor-Service prüfen
systemctl status myp-kiosk-monitor
# Health-Check manuell ausführen
myp-maintenance check-health
# Cron-Jobs prüfen
crontab -l -u root | grep myp
# Resource-Logs prüfen
tail -f /var/log/system-resources.log
# Watchdog-Logs prüfen
tail -f /var/log/kiosk-watchdog.log
```
#### Anwendung nicht erreichbar
```bash
# Netzwerk-Status prüfen
myp-maintenance check-health
# Anwendung direkt testen
curl -I http://localhost:5000
# Services-Status
systemctl status myp-druckerverwaltung
systemctl status nginx
# Ports prüfen
netstat -tulpn | grep ":80\|:5000"
# Automatische Reparatur
myp-maintenance auto-fix
```
#### Chromium-Probleme
```bash
# Chromium-Installation prüfen
which chromium chromium-browser
ls -la /snap/bin/chromium
flatpak list | grep -i chromium
# Kiosk-Benutzer-Test
sudo -u kiosk chromium --version
# Session-Umgebung prüfen
sudo -u kiosk env | grep DISPLAY
# Autostart-Mechanismen testen
sudo -u kiosk ~/.config/openbox/autostart
sudo -u kiosk ~/start-kiosk.sh
```
### Console-Zugang
Falls der Kiosk nicht reagiert:
1. **TTY wechseln**: `Strg+Alt+F1` bis `F6`
2. **Einloggen**: Als Root oder sudo-User
3. **Services prüfen**: `myp-maintenance status`
4. **Notfall-Reset**: `myp-emergency-reset`
### Remote-Wartung
```bash
# SSH aktivieren
myp-maintenance enable-ssh
# Remote verbinden
ssh user@raspberry-pi-ip
# Nach Wartung SSH wieder deaktivieren
myp-maintenance disable-ssh
```
## 📊 Monitoring
### Service-Überwachung
```bash
# Alle Services
myp-maintenance status
# Einzelne Services
systemctl status myp-druckerverwaltung
systemctl status myp-kiosk
systemctl status nginx
```
### Log-Monitoring
```bash
# Live Anwendungs-Logs
myp-maintenance logs
# Live Kiosk-Logs
myp-maintenance kiosk-logs
# System-Logs
journalctl -f
```
### Resource-Monitoring
```bash
# System-Ressourcen
htop
# Festplatte
df -h
# Speicher
free -h
```
## 🔐 Sicherheits-Best-Practices
### Standard-Konfiguration
- SSH ist **deaktiviert** (aktivieren nur für Wartung)
- Firewall ist **aktiv** mit Fail2Ban
- Kiosk-Benutzer hat **keine sudo-Berechtigung**
- Alle Desktop-Umgebungen sind **entfernt**
### Wartungs-Zugang
- **Console**: Immer verfügbar über TTY1-6
- **SSH**: Nur bei Bedarf aktivieren
- **Notfall-Reset**: Bei kritischen Problemen
### Backup-Strategie
- **Automatisch**: Täglich um 2:00 Uhr
- **Manuell**: `myp-backup` bei Bedarf
- **Aufbewahrung**: 30 Tage automatisch
## 📈 Performance-Optimierung
### Systemd-Konfiguration
- **Restart-Policy**: Automatischer Neustart bei Fehlern
- **Abhängigkeiten**: Kiosk wartet auf Anwendung
- **Resource-Limits**: Optimiert für Raspberry Pi
### Chromium-Optimierung
- **Hardware-Beschleunigung**: GPU-Support aktiviert
- **Memory-Management**: Optimierte Flags
- **Cache-Konfiguration**: User-Data-Directory isoliert
### Nginx-Optimierung
- **Proxy-Buffering**: Optimiert für lokale Verbindungen
- **Static-File-Serving**: Direkt vom Filesystem
- **Security-Headers**: Umfassende Sicherheits-Konfiguration
## 🎯 Fazit
Das finale Kiosk-Installationssystem bietet:
**Vollautomatische Installation** von Grund auf
**Maximale Sicherheit** ohne Escape-Möglichkeiten
**Robuste Chromium-Installation** mit Multi-Fallbacks
**Umfassende Wartungstools** für Remote-Management
**Production-Ready** für echten Kiosk-Einsatz
**Automatische Backups** und Monitoring
**Notfall-Recovery** für kritische Situationen
**Das System ist jetzt bereit für den Produktionseinsatz!** 🚀
---
**Dokumentation erstellt**: 31.05.2025
**Letzte Aktualisierung**: 31.05.2025
**Version**: 3.0.0 (Production-Ready)

View File

@ -1,322 +0,0 @@
# 📊 MYP Logging & Debug System
## 🚀 Übersicht
Das MYP (Manage Your Printers) System verfügt über ein umfassendes, verbessertes Logging- und Debug-System mit modernen Features wie:
- 🎨 **Farbige Konsolen-Ausgaben** mit ANSI-Unterstützung
- 😀 **Emoji-Integration** für bessere Lesbarkeit
- ⏱️ **Performance-Monitoring** mit Ausführungszeit-Messung
- 🌐 **HTTP-Request/Response-Logging** für API-Debugging
- 💻 **Cross-Platform-Unterstützung** (Windows/Unix/Linux)
- 🔍 **Strukturierte Debug-Informationen** mit erweiterten Metadaten
## 📁 Struktur
```
utils/
├── logging_config.py # Haupt-Logging-Konfiguration
├── debug_utils.py # Debug-Hilfsfunktionen
debug_cli.py # Kommandozeilen-Debug-Tool
```
## 🎨 Features im Detail
### 1. Farbige Log-Ausgaben
Das System verwendet ANSI-Farbcodes für verschiedene Log-Level:
- 🔍 **DEBUG**: Cyan
- **INFO**: Grün
- ⚠️ **WARNING**: Gelb
- ❌ **ERROR**: Rot
- 🔥 **CRITICAL**: Roter Hintergrund mit weißem Text
### 2. Emoji-Integration
Emojis werden automatisch basierend auf Log-Level und Kategorie hinzugefügt:
**Log-Level:**
- 🔍 DEBUG
- INFO
- ⚠️ WARNING
- ❌ ERROR
- 🔥 CRITICAL
**Kategorien:**
- 🖥️ app
- ⏱️ scheduler
- 🔐 auth
- 🖨️ jobs
- 🔧 printers
- 💥 errors
- 👤 user
- 📺 kiosk
### 3. Performance-Monitoring
```python
from utils.logging_config import measure_execution_time, get_logger
# Als Dekorator verwenden
@measure_execution_time(logger=get_logger("app"), task_name="Drucker-Scan")
def scan_printer():
# Ihre Funktion hier
pass
# Als Context-Manager verwenden
from utils.debug_utils import debug_timer
with debug_timer("Datenbankabfrage"):
# Ihr Code hier
pass
```
### 4. HTTP-Request/Response-Logging
Automatisches Logging aller HTTP-Anfragen und -Antworten:
```python
# Wird automatisch für alle API-Endpunkte (/api/*) aktiviert
# Loggt:
# - Request-Method, URL, Headers, Parameter
# - Response-Status, Größe, Ausführungszeit
# - Client-IP und User-Agent
```
## 🛠️ Verwendung
### Basis-Logging
```python
from utils.logging_config import get_logger
# Logger für verschiedene Komponenten erstellen
app_logger = get_logger("app")
auth_logger = get_logger("auth")
jobs_logger = get_logger("jobs")
# Verwenden
app_logger.info("🚀 Anwendung gestartet")
auth_logger.warning("⚠️ Fehlgeschlagener Login-Versuch")
```
### Debug-Funktionen
```python
from utils.debug_utils import debug_dump, debug_trace, debug_function
# Objekt-Debugging
debug_dump(my_object, "Drucker-Konfiguration")
# Stack-Trace ausgeben
debug_trace("Checkpoint erreicht")
# Funktion automatisch debuggen
@debug_function(level=DebugLevel.VERBOSE)
def my_function():
pass
```
### Memory-Monitoring
```python
from utils.debug_utils import memory_usage, log_memory_usage
# Speicherverbrauch messen
memory_info = memory_usage()
print(f"RAM: {memory_info['rss']:.2f} MB")
# Automatisch loggen
log_memory_usage("Meine Anwendung")
```
## 🖥️ Debug-CLI
Das erweiterte Debug-CLI bietet mehrere Befehle:
```bash
# Vollständige Diagnose
python debug_cli.py diagnose
# Drucker scannen
python debug_cli.py scan
# API-Routen anzeigen
python debug_cli.py routes
# Systeminformationen
python debug_cli.py sysinfo
# Log-Dateien analysieren
python debug_cli.py logs
# Logging-System testen
python debug_cli.py test-logging
```
### Interaktives Menü
Starten Sie das CLI ohne Parameter für ein interaktives Menü:
```bash
python debug_cli.py
```
## ⚙️ Konfiguration
### Log-Level setzen
```python
from utils.logging_config import setup_logging
# Debug-Modus aktivieren
setup_logging(debug_mode=True)
# Standard-Logging
setup_logging(debug_mode=False)
```
### Debug-Level für Debug-Utils
```python
from utils.debug_utils import set_debug_level, DebugLevel
# Debug-Level setzen
set_debug_level(DebugLevel.VERBOSE) # 0=MINIMAL, 1=NORMAL, 2=VERBOSE, 3=TRACE
```
### Windows-Unterstützung
Das System aktiviert automatisch VT100-Unterstützung unter Windows für Farbausgaben:
```python
# Automatische Aktivierung in logging_config.py
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
```
## 📊 Ausgabe-Beispiele
### Startup-Logs
```
🚀 =================== MYP WIRD GESTARTET ===================
🖥️ [INFO] 📂 Log-Verzeichnis: ./logs
🖥️ [INFO] 📊 Log-Level: INFO
🖥️ [INFO] 💻 Betriebssystem: Windows 10
🖥️ [INFO] 🌐 Hostname: MYP-SERVER
🖥️ [INFO] 📅 Startzeit: 15.12.2024 14:30:00
🖥️ ========================================================
```
### HTTP-Request-Logs
```
🔍 [DEBUG] 🌐 HTTP-Anfrage: GET /api/printers
🔍 [DEBUG] 📡 Remote-Adresse: 192.168.1.100
🔍 [DEBUG] 🧩 Inhaltstyp: application/json
✅ [DEBUG] ✅ HTTP-Antwort: 200
🔍 [DEBUG] ⏱️ Verarbeitungsdauer: 45.23 ms
🔍 [DEBUG] 📦 Antwortgröße: 2.1 KB
```
### Performance-Monitoring
```
🔍 [DEBUG] ⏱️ Ausführungszeit: Drucker-Status-Prüfung - 234.56 ms
⚠️ [WARNING] ⏱️ Langsame Ausführung: API-Live-Drucker-Status - 1234.56 ms
```
## 🔧 Erweiterte Features
### Error-Handling mit automatischem Logging
```python
from utils.debug_utils import debug_exception_handler
@debug_exception_handler(logger=get_logger("app"))
def risky_function():
# Code der Exceptions werfen könnte
pass
```
### Profiling
```python
from utils.debug_utils import profile_function
@profile_function
def performance_critical_function():
# Wird automatisch profiliert
pass
```
### Cache-Clearing
```python
# API-Endpunkte zum Cache-Clearing
POST /api/printers/cache/clear
POST /api/admin/cache/clear
```
## 📈 Monitoring & Wartung
### Log-Rotation
- Automatische Rotation bei 10 MB Dateigröße
- 5 Backup-Dateien werden behalten
- Separate Log-Dateien für verschiedene Komponenten
### Backup & Cleanup
```python
# Automatische Backups über backup_manager
# Cleanup über maintenance_scheduler
```
## 🔍 Troubleshooting
### Farben funktionieren nicht
1. Prüfen Sie die Terminal-Unterstützung:
```python
from utils.logging_config import supports_color
print(supports_color())
```
2. Windows: Stellen Sie sicher, dass VT100-Modus aktiviert ist
### Performance-Issues
1. Debug-Level reduzieren:
```python
set_debug_level(DebugLevel.MINIMAL)
```
2. HTTP-Logging für Produktion deaktivieren:
```python
# In app.py die before_request/after_request Handler modifizieren
```
### Memory-Leaks
1. Memory-Monitoring aktivieren:
```python
log_memory_usage("Komponente")
```
2. Debug-Utils für Speicher-Profiling nutzen
## 📞 Support
Bei Problemen oder Fragen:
1. Debug-CLI verwenden: `python debug_cli.py test-logging`
2. Log-Dateien prüfen: `python debug_cli.py logs`
3. Vollständige Diagnose: `python debug_cli.py diagnose`
---
*Erstellt für MYP v1.0.0 - Manage Your Printers* 🖨️

View File

@ -1,157 +0,0 @@
# MYP Platform - Optimierungen und Fehlerbehebungen
## Durchgeführte Optimierungen (Stand: 15.06.2024)
### 1. Drucker-Seite Performance-Optimierung
**Problem**: Die Drucker-Seite lud ewig, da sie versuchte, den Status jedes Druckers über das Netzwerk zu überprüfen.
**Lösung**:
- **Schnelle Status-Bestimmung**: Drucker-Status wird jetzt basierend auf der hardkodierten `PRINTERS`-Konfiguration bestimmt
- **Optimierte API-Endpunkte**:
- `/api/printers` - Lädt Drucker mit sofortiger Status-Bestimmung
- `/api/printers/status` - Schnelle Status-Abfrage ohne Netzwerk-Timeouts
- **Hardkodierte Drucker-Logik**:
- Drucker in `PRINTERS`-Konfiguration → Status: `available`
- Drucker nicht in Konfiguration → Status: `offline`
**Implementierung**:
```python
# In app.py - Optimierte Drucker-Abfrage
printer_config = PRINTERS.get(printer.name)
if printer_config:
status = "available"
active = True
else:
status = "offline"
active = False
```
### 2. Hardkodierte Drucker-Synchronisation
**Skripte erstellt**:
- `add_hardcoded_printers.py` - Fügt die 6 hardkodierten Drucker in die Datenbank ein
- `update_printers.py` - Synchronisiert Drucker-Status mit der Konfiguration
**Hardkodierte Drucker**:
```
Printer 1: 192.168.0.100
Printer 2: 192.168.0.101
Printer 3: 192.168.0.102
Printer 4: 192.168.0.103
Printer 5: 192.168.0.104
Printer 6: 192.168.0.106
```
### 3. Settings-Funktionalität vollständig implementiert
**Problem**: Settings-Seite hatte nicht-funktionale UI-Elemente.
**Lösung**:
- **Vollständige API-Integration**: Alle Settings-Optionen sind jetzt funktional
- **Neue Routen hinzugefügt**:
- `/user/update-settings` (POST) - Einstellungen speichern
- `/user/api/update-settings` (POST) - JSON-API für Einstellungen
- **Funktionale Einstellungen**:
- ✅ Theme-Auswahl (Hell/Dunkel/System)
- ✅ Reduzierte Bewegungen
- ✅ Kontrast-Einstellungen
- ✅ Benachrichtigungseinstellungen
- ✅ Datenschutz & Sicherheitseinstellungen
- ✅ Automatische Abmeldung
### 4. Terms & Privacy Seiten funktionsfähig
**Implementiert**:
- `/terms` - Vollständige Nutzungsbedingungen
- `/privacy` - Umfassende Datenschutzerklärung
- **Beide Seiten enthalten**:
- Mercedes-Benz spezifische Inhalte
- DSGVO-konforme Datenschutzinformationen
- Kontaktinformationen für Support
### 5. API-Routen-Optimierung
**Neue/Korrigierte Routen**:
```python
# Settings-API
@app.route("/user/update-settings", methods=["POST"])
@user_bp.route("/api/update-settings", methods=["POST"])
# Drucker-Optimierung
@app.route("/api/printers", methods=["GET"]) # Optimiert
@app.route("/api/printers/status", methods=["GET"]) # Optimiert
# Weiterleitungen für Kompatibilität
@app.route("/api/user/export", methods=["GET"])
@app.route("/api/user/profile", methods=["PUT"])
```
### 6. JavaScript-Integration
**Globale Funktionen verfügbar**:
- `window.apiCall()` - Für API-Aufrufe mit CSRF-Schutz
- `window.showToast()` - Für Benachrichtigungen
- `window.showFlashMessage()` - Für Flash-Nachrichten
**Settings-JavaScript**:
- Auto-Save bei Toggle-Änderungen
- Vollständige Einstellungs-Serialisierung
- Fehlerbehandlung mit Benutzer-Feedback
### 7. Datenbank-Optimierungen
**Drucker-Status-Management**:
- Automatische Status-Updates basierend auf Konfiguration
- Konsistente IP-Adressen-Synchronisation
- Aktive/Inaktive Drucker-Kennzeichnung
### 8. Hardkodierte Konfiguration
**Pfade korrigiert** (settings.py):
```python
DATABASE_PATH = "C:/Users/TTOMCZA.EMEA/Dev/Projektarbeit-MYP/database/myp.db"
LOG_DIR = "C:/Users/TTOMCZA.EMEA/Dev/Projektarbeit-MYP/backend/app/logs"
SSL_CERT_PATH = "C:/Users/TTOMCZA.EMEA/Dev/Projektarbeit-MYP/backend/app/certs/myp.crt"
SSL_KEY_PATH = "C:/Users/TTOMCZA.EMEA/Dev/Projektarbeit-MYP/backend/app/certs/myp.key"
```
## Ergebnis
### ✅ Behobene Probleme:
1. **Drucker-Seite lädt nicht mehr ewig** - Sofortige Anzeige
2. **Alle Settings-Optionen funktional** - Keine Dummy-Optionen mehr
3. **Terms & Privacy vollständig implementiert** - Rechtskonforme Inhalte
4. **Hardkodierte Drucker verfügbar** - 6 Drucker mit korrektem Status
5. **API-Routen vollständig** - Alle UI-Funktionen haben Backend-Support
### 🚀 Performance-Verbesserungen:
- Drucker-Status-Abfrage: ~3000ms → ~50ms
- Settings-Speicherung: Vollständig funktional
- API-Antwortzeiten: Deutlich verbessert
### 📋 Nächste Schritte:
1. Testen der Drucker-Reservierung
2. Validierung der Job-Verwaltung
3. Überprüfung der Admin-Panel-Funktionen
4. SSL-Zertifikat-Generierung testen
---
**Dokumentiert von**: Claude Sonnet 4
**Datum**: 15.06.2024
**Version**: 3.0.0

View File

@ -1,337 +0,0 @@
# Raspberry Pi Kiosk-Optimierungen
## Übersicht
Das MYP Installationsskript wurde mit umfassenden Raspberry Pi spezifischen Optimierungen erweitert, basierend auf bewährten Praktiken aus der Community.
## Quellen und Referenzen
- [Marco Pascucci - rPI Kiosk Tutorial](https://mpascucci.github.io/tutorial/rpi/)
- [Thomas Krampe - Raspberry Pi Web-Kiosk](https://blog.kngstn.eu/article/2023-09-22-raspberrypi-als-web-kiosk/)
- Raspberry Pi Foundation Best Practices
- Community-erprobte Kiosk-Konfigurationen
## Implementierte Optimierungen
### 1. Boot-Konfiguration (`/boot/config.txt`)
```bash
# GPU Memory Split für bessere Browser-Performance
gpu_mem=128
# Disable Rainbow Splash für professionelles Erscheinungsbild
disable_splash=1
# HDMI Force Hotplug für bessere Display-Kompatibilität
hdmi_force_hotplug=1
# Disable Overscan für Kiosk-Displays
disable_overscan=1
# Audio über HDMI aktivieren
hdmi_drive=2
```
**Vorteile:**
- ✅ Bessere Chromium-Performance durch mehr GPU-Speicher
- ✅ Professioneller Boot ohne Raspberry Pi Logo
- ✅ Zuverlässige HDMI-Erkennung
- ✅ Vollbild-Nutzung ohne schwarze Ränder
### 2. Kernel-Parameter (`/boot/cmdline.txt`)
```bash
# Console Blanking deaktivieren
consoleblank=0
# Logo deaktivieren für schnelleren Boot
logo.nologo
# Quiet Boot für saubere Kiosk-Erfahrung
quiet
```
**Vorteile:**
- ✅ Bildschirm bleibt immer aktiv
- ✅ Schnellerer Boot-Prozess
- ✅ Keine störenden Boot-Meldungen
### 3. WLAN Power Management
#### Systemd-Service
```bash
# Automatische Deaktivierung bei jedem Boot
systemctl enable disable-wifi-power-management.service
```
#### NetworkManager-Konfiguration
```bash
# Globale WLAN Power Save Deaktivierung
wifi.powersave = 2
```
**Problem gelöst:**
- ❌ `wlan0: carrier lost` Fehler
- ❌ Intermittierende Netzwerkverbindung
- ❌ Kiosk-Unterbrechungen durch WLAN-Standby
### 4. Erweiterte Chromium-Optimierungen
#### Raspberry Pi spezifische Flags
```bash
--disable-gpu-compositing
--enable-gpu-rasterization
--disable-smooth-scrolling
--disable-2d-canvas-image-chromium
--disable-accelerated-2d-canvas
--num-raster-threads=2
--enable-zero-copy
--force-device-scale-factor=1.0
--disable-pinch
--overscroll-history-navigation=0
```
#### Chromium-Richtlinien (`/etc/chromium-browser/policies/managed/`)
```json
{
"DefaultBrowserSettingEnabled": false,
"BackgroundModeEnabled": false,
"BookmarkBarEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"PasswordManagerEnabled": false,
"TranslateEnabled": false,
"MetricsReportingEnabled": false
}
```
**Vorteile:**
- ✅ Optimierte Performance auf ARM-Hardware
- ✅ Reduzierte CPU/GPU-Last
- ✅ Deaktivierte störende Browser-Features
- ✅ Kiosk-optimierte Benutzeroberfläche
### 5. Crash-Recovery-System
#### Chromium Restart-Loop
```bash
while true; do
chromium-browser [flags] "$KIOSK_URL"
EXIT_CODE=$?
# Bei normalem Exit nicht neustarten
if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 15 ]; then
break
fi
# Bei Crash: Neustart nach 3 Sekunden
sleep 3
pkill -f chromium
done
```
#### Chromium Preferences Bereinigung
```bash
# Crash-Flags vor jedem Start bereinigen
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' Preferences
sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' Preferences
```
**Vorteile:**
- ✅ Automatischer Neustart bei Browser-Crashes
- ✅ Keine "Chromium didn't shut down correctly" Meldungen
- ✅ Unterbrechungsfreier Kiosk-Betrieb
### 6. Temperatur-Monitoring
#### Automatisches Monitoring
```bash
# Alle 5 Minuten Temperatur-Check
*/5 * * * * root /usr/local/bin/pi-temp-check
```
#### Warnungen und Logging
- **70°C+**: Warnung in Logs
- **80°C+**: Kritische Warnung + Syslog
- Kontinuierliche Aufzeichnung in `/var/log/pi-temperature.log`
**Vorteile:**
- ✅ Frühwarnung bei Überhitzung
- ✅ Präventive Wartung möglich
- ✅ Langzeit-Temperaturverlauf
### 7. Performance-Optimierungen
#### Kernel-Parameter
```bash
# Swappiness reduzieren
vm.swappiness=10
# Dirty Ratio optimieren
vm.dirty_ratio=15
vm.dirty_background_ratio=5
```
#### Hardware-Erkennung
```bash
# Automatische Pi-Erkennung
if grep -q "Raspberry Pi" /proc/cpuinfo; then
# Pi-spezifische Optimierungen aktivieren
fi
```
**Vorteile:**
- ✅ Bessere I/O-Performance
- ✅ Reduzierte SD-Karten-Belastung
- ✅ Optimierte Speicherverwaltung
### 8. Multiple Autostart-Methoden
#### 1. LXDE Autostart (Klassisch)
```bash
# ~/.config/lxsession/LXDE-pi/autostart
@bash /home/kiosk/start-kiosk.sh
```
#### 2. Desktop Autostart (Modern)
```bash
# ~/.config/autostart/myp-kiosk.desktop
[Desktop Entry]
Type=Application
Exec=/bin/bash /home/kiosk/start-kiosk.sh
```
#### 3. Systemd Service (Robust)
```bash
# /lib/systemd/system/kiosk.service
[Service]
ExecStart=/bin/bash /home/kiosk/start-kiosk.sh
```
**Vorteile:**
- ✅ Mehrfache Absicherung
- ✅ Kompatibilität mit verschiedenen Desktop-Umgebungen
- ✅ Fallback-Mechanismen
### 9. Energiesparmodus-Deaktivierung
#### X-Server Level
```bash
# LightDM Konfiguration
xserver-command=X -s 0 -dpms
```
#### systemd-logind Level
```bash
# Alle Power-Events ignorieren
HandlePowerKey=ignore
HandleSuspendKey=ignore
HandleLidSwitch=ignore
IdleAction=ignore
```
#### Application Level
```bash
# In Kiosk-Skript
xset s off
xset s noblank
xset -dpms
```
**Vorteile:**
- ✅ Bildschirm bleibt permanent aktiv
- ✅ Keine ungewollten Standby-Modi
- ✅ 24/7 Kiosk-Betrieb möglich
## Wartung und Monitoring
### Neue Wartungstools
```bash
# Raspberry Pi spezifische Checks
myp-maintenance check-health
# Temperatur-Monitoring
tail -f /var/log/pi-temperature.log
# WLAN Power Management Status
iwconfig wlan0 | grep "Power Management"
```
### Troubleshooting
#### WLAN-Probleme
```bash
# WLAN Power Save manuell deaktivieren
sudo iwconfig wlan0 power off
# NetworkManager neu starten
sudo systemctl restart NetworkManager
```
#### Performance-Probleme
```bash
# GPU Memory Check
vcgencmd get_mem gpu
# Temperatur Check
vcgencmd measure_temp
# Chromium-Prozesse prüfen
ps aux | grep chromium
```
#### Display-Probleme
```bash
# HDMI-Status prüfen
tvservice -s
# X-Server neu starten
sudo systemctl restart lightdm
```
## Kompatibilität
### Getestete Raspberry Pi Modelle
- ✅ Raspberry Pi 4 (empfohlen)
- ✅ Raspberry Pi 3B+
- ✅ Raspberry Pi 3B
- ⚠️ Raspberry Pi 2 (eingeschränkt)
- ❌ Raspberry Pi 1/Zero (nicht empfohlen)
### Getestete Betriebssysteme
- ✅ Raspberry Pi OS (Debian Bullseye/Bookworm)
- ✅ Ubuntu Server 20.04+ für ARM
- ✅ Debian 11+ ARM64
## Best Practices
### Hardware-Empfehlungen
- **RAM**: Mindestens 2GB (4GB empfohlen)
- **SD-Karte**: Class 10, mindestens 16GB
- **Kühlung**: Aktive Kühlung bei Dauerbetrieb
- **Netzteil**: Offizielles Pi-Netzteil verwenden
### Konfiguration-Tipps
- GPU Memory auf 128MB+ setzen
- Hochwertige SD-Karte verwenden
- Regelmäßige Temperatur-Überwachung
- Backup der Boot-Konfiguration
### Wartung
- Monatliche Temperatur-Log-Auswertung
- Quartalsweise SD-Karten-Gesundheitscheck
- Jährliche Neuinstallation bei Dauerbetrieb
---
**Status**: ✅ Produktionsreif
**Letzte Aktualisierung**: $(date +%Y-%m-%d)
**Version**: 3.0 (Raspberry Pi Optimiert)
## Referenzen
- [Marco Pascucci Tutorial](https://mpascucci.github.io/tutorial/rpi/)
- [Thomas Krampe Blog](https://blog.kngstn.eu/article/2023-09-22-raspberrypi-als-web-kiosk/)
- [Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/)
- [Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches/)

View File

@ -1,247 +0,0 @@
# Auto-Optimierung und Batch-Planung - MYP Platform
## Übersicht der implementierten Funktionalitäten
Die MYP Platform wurde um leistungsstarke Auto-Optimierungs- und Batch-Planungsfunktionen erweitert, die eine intelligente Verwaltung von 3D-Druckaufträgen ermöglichen.
## 🚀 Auto-Optimierung
### Was ist Auto-Optimierung?
Die Auto-Optimierung ist ein intelligentes System, das automatisch die optimale Verteilung von Druckaufträgen auf verfügbare 3D-Drucker berechnet und anwendet.
### Verfügbare Algorithmen
#### 1. Round Robin - Gleichmäßige Verteilung
- **Zweck**: Gleichmäßige Auslastung aller verfügbaren Drucker
- **Funktionsweise**: Jobs werden zyklisch auf alle Drucker verteilt
- **Ideal für**: Standardproduktion mit gleichwertigen Druckern
#### 2. Load Balancing - Auslastungsoptimierung
- **Zweck**: Optimale Auslastung basierend auf aktueller Druckerbelastung
- **Funktionsweise**: Jobs werden dem Drucker mit der geringsten aktuellen Auslastung zugewiesen
- **Ideal für**: Gemischte Arbeitslasten mit unterschiedlichen Druckzeiten
#### 3. Prioritätsbasiert - Wichtige Jobs zuerst
- **Zweck**: Hochpriorisierte Jobs erhalten bevorzugte Druckerzuweisungen
- **Funktionsweise**: Jobs werden nach Priorität sortiert und den besten verfügbaren Druckern zugewiesen
- **Ideal für**: Produktionsumgebungen mit unterschiedlichen Projektprioritäten
### Zusätzliche Optimierungsparameter
- **Druckerentfernung berücksichtigen**: Minimiert Transportwege zwischen Druckern
- **Rüstzeiten minimieren**: Reduziert Umrüstzeiten durch intelligente Gruppierung ähnlicher Jobs
- **Max. Batch-Größe**: Begrenzt die Anzahl der Jobs pro Optimierungsvorgang
- **Planungshorizont**: Definiert den Zeitraum für die Optimierung (1-168 Stunden)
### Aktivierung der Auto-Optimierung
#### Im Schichtplan (Calendar):
1. Navigieren Sie zum Schichtplan (`/calendar`)
2. Klicken Sie auf den "Auto-Optimierung" Button
3. Das System aktiviert die automatische Optimierung
4. Tastenkürzel: `Ctrl+Alt+O`
#### Konfiguration:
- Klicken Sie auf "Auto-Optimierung" für erweiterte Einstellungen
- Wählen Sie den gewünschten Algorithmus
- Konfigurieren Sie zusätzliche Parameter
- Speichern Sie die Einstellungen
## 📦 Batch-Planung (Mehrfachauswahl)
### Was ist Batch-Planung?
Die Batch-Planung ermöglicht die gleichzeitige Verwaltung mehrerer Druckaufträge durch Mehrfachauswahl und Batch-Operationen.
### Verfügbare Batch-Operationen
#### 1. Mehrfachauswahl
- **Aktivierung**: Klick auf "Mehrfachauswahl" Button
- **Verwendung**: Checkboxen erscheinen auf allen Job-Karten
- **Auswahl**: Jobs durch Anklicken der Checkboxen markieren
#### 2. Batch-Operationen
- **Starten**: Mehrere Jobs gleichzeitig starten
- **Pausieren**: Laufende Jobs pausieren
- **Abbrechen**: Jobs abbrechen und stoppen
- **Löschen**: Abgeschlossene Jobs löschen
- **Priorität setzen**: Hoch/Normal für ausgewählte Jobs
#### 3. Intelligente Reihenfolge-Anpassung
- Automatische Optimierung der Ausführungsreihenfolge
- Berücksichtigung von Druckzeiten und Prioritäten
- Minimierung von Wartezeiten
### Aktivierung der Batch-Planung
#### In der Druckaufträge-Ansicht:
1. Navigieren Sie zu "Druckaufträge" (`/jobs`)
2. Klicken Sie auf "Mehrfachauswahl"
3. Markieren Sie gewünschte Jobs mit den Checkboxen
4. Wählen Sie eine Batch-Operation
5. Tastenkürzel: `Ctrl+Alt+B`
## 🔧 Technische Implementierung
### Backend API-Endpunkte
```python
# Auto-Optimierung
POST /api/optimization/auto-optimize
- Führt automatische Optimierung durch
- Parameter: settings, enabled
- Rückgabe: optimized_jobs, algorithm, message
# Batch-Operationen
POST /api/jobs/batch-operation
- Führt Batch-Operationen auf mehrere Jobs aus
- Parameter: job_ids[], operation
- Rückgabe: processed_jobs, error_count, operation
# Optimierungseinstellungen
GET/POST /api/optimization/settings
- Lädt/Speichert Optimierungseinstellungen
- Parameter: algorithm, consider_distance, minimize_changeover, max_batch_size, time_window
```
### Frontend JavaScript-Funktionen
```javascript
// Auto-Optimierung umschalten
toggleAutoOptimization()
// Batch-Modus umschalten
toggleBatchMode()
// Batch-Planung Modal öffnen
openBatchPlanningModal()
// Optimierungseinstellungen anzeigen
showOptimizationSettings()
```
### CSS-Selektoren für Selenium-Tests
```css
#auto-opt-toggle /* Auto-Optimierung Button */
#batch-toggle /* Batch-Modus Button */
#refresh-button /* Aktualisierung Button */
.nav-item:nth-child(5) /* Schichtplan Navigation */
.nav-item:nth-child(6) /* Gastanfrage Navigation */
```
## 🎯 Selenium-Test Unterstützung
Die implementierten Funktionen unterstützen vollständig den bereitgestellten Selenium-Test:
### Test-Schritte Abdeckung:
1. ✅ Dashboard öffnen (`/dashboard`)
2. ✅ Refresh Dashboard Button (`#refreshDashboard`)
3. ✅ Navigation zu verschiedenen Seiten (`.nav-item`)
4. ✅ Auto-Optimierung Toggle (`#auto-opt-toggle`)
5. ✅ Batch-Modus Toggle (`#batch-toggle`)
6. ✅ Refresh-Funktionen (`#refresh-button`)
7. ✅ Druckaufträge Navigation (`linkText=Druckaufträge`)
### Erweiterte Funktionen:
- Keyboard-Shortcuts für alle Hauptfunktionen
- Responsive Design für mobile Geräte
- Dark Mode Unterstützung
- Toast-Benachrichtigungen für Benutzer-Feedback
- Persistente Einstellungen im Browser LocalStorage
## 📱 Benutzeroberfläche
### Visual Feedback
- **Hover-Tooltips**: Detaillierte Erklärungen bei Mouse-Over
- **Button-Animationen**: Visuelle Bestätigung von Aktionen
- **Status-Indikatoren**: Farbcodierte Status-Anzeigen
- **Progress-Bars**: Echtzeit-Fortschrittsanzeigen
### Accessibility Features
- **Keyboard Navigation**: Vollständige Tastatur-Unterstützung
- **Screen Reader**: ARIA-Labels für Barrierefreiheit
- **High Contrast**: Unterstützung für hohen Kontrast
- **Responsive Design**: Optimiert für alle Bildschirmgrößen
## 🔄 Auto-Refresh und Live-Updates
### Implementierte Refresh-Funktionen:
- `refreshDashboard()` - Dashboard-Statistiken aktualisieren
- `refreshJobs()` - Druckaufträge neu laden
- `refreshCalendar()` - Kalender-Events aktualisieren
- `refreshPrinters()` - Drucker-Status aktualisieren
### Auto-Refresh Manager:
- Automatische Aktualisierung alle 30 Sekunden
- Pausiert bei inaktiven Browser-Tabs
- Tastenkürzel: `Ctrl+Shift+R` zum Ein-/Ausschalten
## 🛡️ Sicherheit und Berechtigungen
### Zugriffskontrolle:
- **Batch-Operationen**: Nur auf eigene Jobs oder Admin-Rechte
- **Auto-Optimierung**: Eingeschränkt auf autorisierte Benutzer
- **CSRF-Schutz**: Alle API-Aufrufe sind CSRF-geschützt
- **Input-Validierung**: Vollständige Validierung aller Eingaben
### Logging und Monitoring:
- Alle Optimierungsaktivitäten werden geloggt
- Batch-Operationen werden im System-Log erfasst
- Performance-Metriken für Optimierungsalgorithmen
- Fehlerbehandlung mit detailliertem Logging
## 📈 Performance und Skalierung
### Optimierungsperformance:
- **Round Robin**: O(n) - Linear mit Anzahl Jobs
- **Load Balancing**: O(n × m) - Linear mit Jobs × Drucker
- **Prioritätsbasiert**: O(n log n) - Logarithmisch durch Sortierung
### Caching und Speicher:
- Browser LocalStorage für Benutzereinstellungen
- Session-basierte Optimierungsparameter
- Effiziente DOM-Updates ohne vollständige Neuladeoperationen
## 🚀 Zukünftige Erweiterungen
### Geplante Features:
- **Machine Learning**: KI-basierte Optimierungsvorhersagen
- **Multi-Material**: Unterstützung für verschiedene Druckmaterialien
- **Wartungsplanung**: Integration von Drucker-Wartungszyklen
- **Energieoptimierung**: Berücksichtigung von Energieverbrauch
- **Qualitätskontrolle**: Automatische Qualitätsbewertung
### API-Erweiterungen:
- RESTful API für externe Systeme
- Webhook-Unterstützung für Echtzeit-Benachrichtigungen
- GraphQL-Unterstützung für flexible Datenabfragen
## 📚 Verwendung und Best Practices
### Empfohlene Workflows:
1. **Tägliche Produktion**:
- Auto-Optimierung mit Load Balancing aktivieren
- Batch-Planung für morgendliche Job-Starts verwenden
- Regelmäßige Refresh-Zyklen für aktuelle Statusupdates
2. **Hochpriorisierte Projekte**:
- Prioritätsbasierten Algorithmus verwenden
- Manuelle Druckerzuweisung für kritische Jobs
- Echtzeit-Monitoring aktivieren
3. **Wartungszeiten**:
- Batch-Operationen zum pausieren aller Jobs
- Auto-Optimierung temporär deaktivieren
- Drucker-Status entsprechend aktualisieren
## 🎯 Zusammenfassung
Die MYP Platform bietet jetzt eine vollständig integrierte Lösung für:
- **Intelligente Auto-Optimierung** mit drei leistungsstarken Algorithmen
- **Flexible Batch-Planung** für effiziente Massenverwaltung
- **Benutzerfreundliche UI** mit umfassenden Erklärungen und Tooltips
- **Vollständige Selenium-Test-Unterstützung** für alle implementierten Funktionen
- **Enterprise-grade Sicherheit** und Performance-Optimierung
Alle Funktionen sind sofort einsatzbereit und entsprechen den Mercedes-Benz Qualitätsstandards für professionelle 3D-Druck-Management-Systeme.

View File

@ -1,205 +0,0 @@
# 🔘 Button-Funktionalitäten Implementierung
## 📋 **Übersicht**
Dieses Dokument beschreibt die umfassende Implementierung aller Button-Funktionalitäten für die Mercedes-Benz MYP Platform basierend auf dem bereitgestellten Selenium-Test-Skript.
## ❌ **Ursprüngliches Problem**
Viele Buttons in der Webanwendung hatten keine echten Funktionalitäten:
- Buttons wurden zwar geklickt, zeigten aber keine Reaktion
- Fehlende Backend-API-Routen
- Keine visuellen oder funktionalen Rückmeldungen
- Besonders Admin-Buttons waren nicht implementiert
## ✅ **Implementierte Lösung**
### **1. Dashboard-Buttons**
**Status:** ✅ Bereits funktional
| Button ID | Funktionalität | Implementiert |
|-----------|----------------|---------------|
| `#refreshDashboard` | Lädt Dashboard-Daten neu mit Animation | ✅ |
### **2. Drucker-Buttons**
**Status:** ✅ Bereits funktional
| Button ID | Funktionalität | Implementiert |
|-----------|----------------|---------------|
| `#refresh-button` | Lädt Drucker-Status neu mit Spinner | ✅ |
| `#maintenance-toggle` | Schaltet Wartungsmodus um | ✅ |
### **3. Jobs-Buttons**
**Status:** ✅ Bereits funktional
| Button ID | Funktionalität | Implementiert |
|-----------|----------------|---------------|
| `#batch-toggle` | Aktiviert/Deaktiviert Mehrfachauswahl | ✅ |
### **4. Admin-Buttons**
**Status:** ✅ NEU IMPLEMENTIERT
| Button ID | Funktionalität | Backend-Route | Implementiert |
|-----------|----------------|---------------|---------------|
| `#system-status-btn` | System-Status Modal | `/api/admin/system/status` | ✅ |
| `#analytics-btn` | Analytics-Seite | `/analytics` | ✅ |
| `#maintenance-btn` | Wartungsmodus Modal | `/api/admin/maintenance/*` | ✅ |
| `#add-user-btn` | Benutzer hinzufügen | `/admin/users/add` | ✅ |
## 🔧 **Technische Details**
### **Frontend-Verbesserungen**
**JavaScript-Funktionalitäten hinzugefügt:**
```javascript
// System Status mit echter CPU/RAM/Disk Anzeige
function loadSystemStatus()
// Wartungsmodus mit Aktivierung/Deaktivierung
function showMaintenanceModal()
// Toast-Benachrichtigungen für Benutzer-Feedback
function showNotification(message, type)
// Live-Updates für Dashboard-Statistiken
function startLiveUpdates()
```
**Visuelle Verbesserungen:**
- Loading-Spinner bei Button-Klicks
- Toast-Benachrichtigungen für Erfolg/Fehler
- Modal-Dialoge für komplexe Aktionen
- Hover-Effekte und Animationen
- Button-Status-Änderungen
### **Backend-API-Routen**
**Neue funktionale Endpunkte:**
```python
@app.route('/api/admin/maintenance/activate', methods=['POST'])
@app.route('/api/admin/maintenance/deactivate', methods=['POST'])
@app.route('/api/admin/stats/live', methods=['GET'])
@app.route('/api/admin/system/status', methods=['GET'])
@app.route('/api/dashboard/stats', methods=['GET'])
@app.route('/api/dashboard/active-jobs', methods=['GET'])
@app.route('/api/dashboard/printers', methods=['GET'])
@app.route('/api/dashboard/activities', methods=['GET'])
@app.route('/admin/settings', methods=['GET'])
@app.route('/analytics', methods=['GET'])
```
## 🧪 **Testen der Implementierung**
### **Automatisierter Test**
```bash
# Test-Skript ausführen
python test_button_functionality.py
```
### **Manueller Test**
1. Anwendung starten: `python app.py --debug`
2. Browser öffnen: `http://127.0.0.1:5000`
3. Als Admin anmelden: `admin / admin`
4. Alle Buttons aus dem Selenium-Test durchklicken
### **Erwartete Reaktionen**
**Dashboard (`#refreshDashboard`):**
- ✅ Button zeigt Spinner-Animation
- ✅ Seite wird neu geladen
- ✅ Statistiken werden aktualisiert
**Drucker (`#refresh-button`, `#maintenance-toggle`):**
- ✅ Refresh: Drucker-Liste wird neu geladen
- ✅ Wartung: Button-Text ändert sich, Modus wird umgeschaltet
**Jobs (`#batch-toggle`):**
- ✅ Mehrfachauswahl-UI wird ein-/ausgeblendet
- ✅ Button-Text ändert sich entsprechend
**Admin-Buttons:**
- ✅ `#system-status-btn`: Modal mit CPU/RAM/Disk Informationen
- ✅ `#analytics-btn`: Weiterleitung zur Analytics-Seite
- ✅ `#maintenance-btn`: Wartungsmodus-Modal mit Optionen
- ✅ `#add-user-btn`: Weiterleitung zur Benutzer-Erstellung
## 📊 **Qualitätssicherung**
### **Funktionalitäts-Checkliste**
- [x] Alle Buttons reagieren auf Klicks
- [x] Visuelle Rückmeldung (Hover, Animation, Loading)
- [x] Backend-API-Aufrufe funktionieren
- [x] Error-Handling implementiert
- [x] Toast-Benachrichtigungen für Benutzer-Feedback
- [x] Modal-Dialoge für komplexe Aktionen
- [x] Responsive Design beibehalten
### **Browser-Kompatibilität**
- ✅ Chrome/Chromium
- ✅ Firefox
- ✅ Safari
- ✅ Edge
### **Error-Handling**
```javascript
// Beispiel: Graceful Error-Handling
async function clearCache() {
try {
showLoadingOverlay(true);
const response = await fetch('/api/admin/cache/clear', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
showNotification('Cache erfolgreich geleert', 'success');
} else {
showNotification('Fehler beim Leeren des Cache', 'error');
}
} catch (error) {
showNotification('Verbindungsfehler', 'error');
} finally {
showLoadingOverlay(false);
}
}
```
## 🚀 **Deployment**
### **Produktionsbereitschaft**
1. **Frontend**: Alle JavaScript-Funktionen sind in bestehende Templates integriert
2. **Backend**: Neue API-Routen sind in `app.py` implementiert
3. **Dependencies**: Keine zusätzlichen Abhängigkeiten erforderlich
4. **Testing**: Umfassende Test-Suite bereitgestellt
### **Rollout-Checklist**
- [x] Code in Templates integriert (`admin.html`, etc.)
- [x] API-Routen in `app.py` hinzugefügt
- [x] Error-Handling implementiert
- [x] Test-Skript erstellt
- [x] Dokumentation bereitgestellt
## 📈 **Ergebnis**
**Vor der Implementierung:**
- ❌ Viele Buttons ohne Funktionalität
- ❌ Keine visuellen Rückmeldungen
- ❌ Fehlende Backend-APIs
- ❌ Schlechte Benutzererfahrung
**Nach der Implementierung:**
- ✅ Alle Buttons haben echte Funktionalitäten
- ✅ Umfassende visuelle Rückmeldungen
- ✅ Vollständige Backend-API-Abdeckung
- ✅ Professionelle Benutzererfahrung
- ✅ Mercedes-Benz Qualitätsstandards erfüllt
## 🎯 **Fazit**
Die Mercedes-Benz MYP Platform verfügt jetzt über vollständig funktionale Buttons mit:
- **Echter Funktionalität** statt nur visueller Elemente
- **Professioneller UX** mit Feedback und Animationen
- **Robuster Backend-Integration** mit Error-Handling
- **Mercedes-Benz Qualitätsstandards** in Design und Funktion
Alle 34 Schritte aus dem ursprünglichen Selenium-Test zeigen jetzt echte, sichtbare Reaktionen! 🎉

View File

@ -1,321 +0,0 @@
# Content Security Policy (CSP) Problembehebung - MYP Platform
## 🛡️ Übersicht der behobenen CSP-Probleme
Die Mercedes-Benz MYP Platform hatte mehrere Content Security Policy (CSP) Probleme, die systematisch behoben wurden.
## 🚨 Ursprüngliche Probleme
### 1. **Inline Script Violations**
```
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'"
```
### 2. **Connect-src Violations**
```
Refused to connect to 'https://127.0.0.1/api/...' because it violates the document's Content Security Policy
```
### 3. **PWA Icon Loading Errors**
```
Error while trying to use the following icon from the Manifest: http://127.0.0.1:5000/static/icons/icon-144x144.png
```
## ✅ Implementierte Lösungen
### 1. **CSP-Konfiguration optimiert** (`utils/security.py`)
#### Vor der Behebung:
- Restriktive CSP-Regeln blockierten lokale Entwicklung
- Nonce-System verursachte Konflikte mit 'unsafe-inline'
- Connect-src erlaubte keine lokalen API-Calls
#### Nach der Behebung:
```python
CSP_POLICY = {
'default-src': ["'self'"],
'script-src': [
"'self'",
"'unsafe-inline'", # Für Entwicklung aktiviert
"https://cdn.jsdelivr.net",
"https://unpkg.com"
],
'connect-src': [
"'self'",
"ws:", "wss:", # WebSockets
"http://localhost:*", # Lokale Entwicklung
"http://127.0.0.1:*",
"https://localhost:*",
"https://127.0.0.1:*"
],
# ... weitere Regeln
}
```
#### Intelligente Nonce-Behandlung:
```python
def build_csp_header(self, nonce=None, use_nonce=False):
# In Entwicklung: use_nonce = False für 'unsafe-inline'
# In Produktion: use_nonce = True für bessere Sicherheit
```
### 2. **API-URL-Erkennung korrigiert** (`static/js/admin-guest-requests.js`)
#### Vor der Behebung:
```javascript
function detectApiBaseUrl() {
return 'https://127.0.0.1'; // CSP-Violation!
}
```
#### Nach der Behebung:
```javascript
function detectApiBaseUrl() {
// Für CSP-Kompatibilität immer relative URLs verwenden
return ''; // Leerer String für relative URLs
}
```
### 3. **PWA-Icons erstellt** (`static/icons/`)
#### Automatische Icon-Generierung:
```python
# generate_icons.py
def create_mercedes_icon(size, output_path):
"""Erstellt Mercedes-Benz-Logo-Icons in verschiedenen Größen"""
img = Image.new('RGB', (size, size), color='#000000')
# Mercedes-Stern zeichnen
# ... Icon-Generierung
```
#### Generierte Icon-Größen:
- 72x72, 96x96, 128x128, 144x144
- 152x152, 192x192, 384x384, 512x512
- Apple Touch Icon, Favicons
### 4. **Event-Handler-System (CSP-konform)** (`static/js/event-handlers.js`)
#### Problem: Inline onclick-Handler
```html
<!-- CSP-Problem -->
<button onclick="refreshDashboard()">Aktualisieren</button>
```
#### Lösung: Data-Action-Attribute
```html
<!-- CSP-konform -->
<button data-action="refresh-dashboard">Aktualisieren</button>
```
#### Zentrale Event-Delegation:
```javascript
class GlobalEventManager {
handleClick(event) {
const target = event.target.closest('[data-action]');
if (!target) return;
const action = target.getAttribute('data-action');
this.executeAction(action, params, target);
}
}
```
### 5. **CSP-Violation-Debugging** (`static/js/csp-violation-handler.js`)
#### Automatische CSP-Verletzungserkennung:
```javascript
document.addEventListener('securitypolicyviolation', this.handleViolation.bind(this));
```
#### Entwickler-Tools:
- **Debug-Panel**: `Ctrl+Shift+C` zum Anzeigen
- **Konsolen-Befehle**: `cspHandler.getViolations()`
- **Export-Funktion**: Verletzungen als JSON exportieren
- **Lösungsvorschläge**: Automatische Fix-Empfehlungen
## 🔧 Migration bestehender onclick-Handler
### Schritt 1: Handler identifizieren
```bash
grep -r "onclick=" templates/ --include="*.html"
```
### Schritt 2: Data-Action-Attribute verwenden
```html
<!-- Alt -->
<button onclick="jobManager.startJob('123')">Start</button>
<!-- Neu -->
<button data-action="start-job" data-action-id="123">Start</button>
```
### Schritt 3: Funktionalität testen
```javascript
// Event wird automatisch vom GlobalEventManager behandelt
case 'start-job':
if (typeof jobManager !== 'undefined' && params.id) {
jobManager.startJob(params.id);
}
break;
```
## 🚀 Verwendung der neuen Event-Handler
### Standard-Aktionen:
```html
<!-- Navigation -->
<button data-action="go-back">Zurück</button>
<button data-action="reload-page">Neu laden</button>
<button data-action="logout">Abmelden</button>
<!-- Dashboard -->
<button data-action="refresh-dashboard">Dashboard aktualisieren</button>
<!-- Jobs -->
<button data-action="refresh-jobs">Jobs aktualisieren</button>
<button data-action="toggle-batch-mode">Batch-Modus</button>
<button data-action="start-job" data-action-id="123">Job starten</button>
<!-- Modals -->
<button data-action="close-modal">Modal schließen</button>
<button data-action="close-job-modal">Job-Modal schließen</button>
<!-- Formulare -->
<button data-action="reset-form">Formular zurücksetzen</button>
<button data-action="clear-file">Datei löschen</button>
```
### Parametrisierte Aktionen:
```html
<button data-action="edit-printer"
data-action-id="printer-001">
Drucker bearbeiten
</button>
<button data-action="remove-element"
data-action-selector=".notification">
Benachrichtigung schließen
</button>
```
## 🔍 Debugging und Monitoring
### CSP-Debug-Panel aktivieren:
1. Öffne Entwicklertools (F12)
2. Drücke `Ctrl+Shift+C` für CSP-Debug-Panel
3. Oder verwende Konsolen-Befehle:
```javascript
// Alle CSP-Verletzungen anzeigen
cspHandler.getViolations()
// Statistiken abrufen
cspHandler.getStats()
// Verletzungen exportieren
cspHandler.exportViolations()
// Debug-Modus aktivieren
cspHandler.enableDebugMode()
```
### Konsolen-Ausgabe verstehen:
```
🚨 CSP Violation detected
Blocked URI: inline
Violated Directive: script-src 'self' 'unsafe-inline'
💡 Lösungsvorschlag: Script in externe .js-Datei auslagern
```
## 📊 Leistungsverbesserungen
### Vor der CSP-Optimierung:
- ❌ Blockierte inline Scripts
- ❌ Fehlerhafte API-Verbindungen
- ❌ Fehlende PWA-Icons
- ❌ Keine CSP-Violation-Behandlung
### Nach der CSP-Optimierung:
- ✅ Funktionale inline Scripts (Entwicklung)
- ✅ Erfolgreiche API-Verbindungen
- ✅ Vollständige PWA-Unterstützung
- ✅ Proaktive CSP-Debugging-Tools
- ✅ Produktionsbereite Sicherheitskonfiguration
## 🛠️ Wartung und Updates
### CSP-Regeln für neue Features hinzufügen:
1. Öffne `utils/security.py`
2. Erweitere `CSP_POLICY` entsprechend
3. Teste mit CSP-Debug-Tools
4. Dokumentiere Änderungen
### Neue Event-Handler hinzufügen:
1. Öffne `static/js/event-handlers.js`
2. Ergänze `executeAction()` switch-case
3. Teste mit data-action-Attributen
4. Dokumentiere neue Aktionen
## ⚠️ Wichtige Hinweise
### Entwicklung vs. Produktion:
- **Entwicklung**: `use_nonce = False` für 'unsafe-inline'
- **Produktion**: `use_nonce = True` für bessere Sicherheit
### Browser-Kompatibilität:
- CSP-Violation-Handler funktioniert in modernen Browsern
- Fallback für ältere Browser vorhanden
- PWA-Icons sind für alle Geräte optimiert
### Performance:
- Event-Delegation reduziert Memory-Usage
- Zentrale Event-Handler verbessern Maintainability
- CSP-Debugging nur in Entwicklung aktiv
## 🎯 Nächste Schritte
1. **Migration aller onclick-Handler**: Systematisches Ersetzen durch data-action
2. **CSP-Reporting**: Server-seitige Violation-Protokollierung implementieren
3. **Nonce-System**: Für Produktion aktivieren
4. **Performance-Monitoring**: CSP-Impact messen
## 📖 Zusätzliche Ressourcen
- [MDN CSP Guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
- [CSP Evaluator](https://csp-evaluator.withgoogle.com/)
- [PWA Best Practices](https://web.dev/pwa-checklist/)
---
**Dokumentation erstellt**: 29.05.2025
**Letzte Aktualisierung**: 29.05.2025
**Version**: 1.0.0

View File

@ -1 +0,0 @@

View File

@ -1,175 +0,0 @@
# MYP Platform - Requirements Verwaltung
## Übersicht
Die MYP Platform verwendet verschiedene Requirements-Dateien für unterschiedliche Umgebungen:
- `requirements.txt` - **Basis-Abhängigkeiten basierend auf tatsächlich verwendeten Imports in app.py**
- `requirements-dev.txt` - Entwicklungsabhängigkeiten (Testing, Debugging, etc.)
- `requirements-prod.txt` - Produktionsabhängigkeiten (optimiert für Performance)
## Installation
### Basis-Installation (Empfohlen)
```bash
# Nur die wirklich benötigten Abhängigkeiten installieren
pip install -r requirements.txt
```
### Entwicklungsumgebung
```bash
# Alle Entwicklungsabhängigkeiten installieren
pip install -r requirements-dev.txt
```
### Produktionsumgebung
```bash
# Produktionsabhängigkeiten installieren
pip install -r requirements-prod.txt
```
## Aktualisierte Requirements (Januar 2025)
### Basis-Requirements (requirements.txt)
**Basierend auf tatsächlich verwendeten Imports in app.py:**
#### Core Flask Framework
- **Flask 3.1.1** - Neueste stabile Version
- **Flask-Login 0.6.3** - Benutzer-Authentifizierung
- **Flask-WTF 1.2.1** - CSRF-Schutz und Formulare
#### Datenbank
- **SQLAlchemy 2.0.36** - ORM für Datenbankoperationen
#### Sicherheit
- **Werkzeug 3.1.3** - WSGI-Utilities und Passwort-Hashing
- **bcrypt 4.2.1** - Passwort-Hashing
- **cryptography 44.0.0** - SSL-Zertifikate
#### Smart Plug Steuerung
- **PyP100 0.1.2** - TP-Link Tapo Smart Plugs (neueste verfügbare Version)
#### System & Monitoring
- **psutil 6.1.1** - System-Monitoring
- **redis 5.2.1** - Rate Limiting und Caching
- **requests 2.32.3** - HTTP-Anfragen
#### Template Engine
- **Jinja2 3.1.5** - Template-Rendering
- **MarkupSafe 3.0.2** - Sichere String-Verarbeitung
- **itsdangerous 2.2.0** - Sichere Daten-Serialisierung
#### Zusätzliche Core-Abhängigkeiten
- **click 8.1.8** - CLI-Kommandos
- **blinker 1.9.0** - Flask-Signaling
#### Windows-spezifisch
- **pywin32 308** - Windows-APIs (nur auf Windows)
## Kompatibilität
### Python-Versionen
- **Mindestversion**: Python 3.9
- **Empfohlen**: Python 3.11 oder 3.12
- **Getestet mit**: Python 3.13.3
### Betriebssysteme
- **Windows**: Vollständig unterstützt (mit pywin32)
- **Linux**: Vollständig unterstützt
- **macOS**: Vollständig unterstützt
## Wichtige Änderungen
### Was wurde entfernt
- **Nicht verwendete Pakete**: pandas, numpy, orjson, ujson, structlog
- **Entwicklungstools**: black, flake8, mypy (verschoben nach requirements-dev.txt)
- **Optionale Pakete**: watchdog, python-dotenv, sphinx
### Was wurde beibehalten
- **Nur tatsächlich verwendete Imports** aus app.py und Core-Modulen
- **Kritische Sicherheitspakete** für Produktionsbetrieb
- **Windows-Kompatibilität** für die Zielumgebung
## Installation mit Extras
### Entwicklungstools installieren
```bash
pip install -r requirements.txt
pip install pytest==8.3.4 pytest-cov==6.0.0
```
### Produktionsserver installieren
```bash
pip install -r requirements.txt
pip install gunicorn==23.0.0
```
## Wartung
### Requirements prüfen
```bash
# Installierte Pakete anzeigen
pip list
# Veraltete Pakete prüfen
pip list --outdated
# Abhängigkeitsbaum anzeigen
pip show --verbose Flask
```
### Sicherheitsupdates
```bash
# Sicherheitslücken prüfen (falls safety installiert)
pip install safety
safety check
# Alle Pakete aktualisieren
pip install --upgrade -r requirements.txt
```
## Troubleshooting
### Häufige Probleme
1. **PyP100 Installation**
- Version 0.1.2 ist die neueste verfügbare Version
- Bei Problemen: `pip install --no-deps PyP100==0.1.2`
2. **Dependency-Konflikte**
- flask-caching erfordert Flask<3, aber Flask 3.1.1 ist installiert
- Lösung: `pip install flask-caching --upgrade` oder entfernen
3. **Windows pywin32**
- Wird automatisch nur auf Windows installiert
- Bei Problemen: `pip install --force-reinstall pywin32`
4. **PATH-Warnungen**
- Scripts werden in User-Verzeichnis installiert
- Lösung: `--no-warn-script-location` verwenden
### Performance-Tipps
1. **Schnellere Installation**
```bash
pip install --upgrade pip
pip install -r requirements.txt --no-deps
```
2. **Cache nutzen**
```bash
pip install --cache-dir ~/.pip/cache -r requirements.txt
```
## Lizenz-Informationen
Alle verwendeten Pakete sind mit der MIT/BSD/Apache-Lizenz kompatibel.
---
**Letzte Aktualisierung**: Januar 2025
**Basis-Requirements**: Nur tatsächlich verwendete Imports
**Nächste Überprüfung**: April 2025

View File

@ -1,173 +0,0 @@
# MYP Platform - Entwicklungs-Roadmap
Dieses Dokument beschreibt die geplanten Entwicklungsschritte und zukünftigen Features für das MYP 3D-Drucker Reservierungssystem.
## Aktuelle Version: 1.1
Die aktuelle Version umfasst die Grundfunktionalitäten:
- Benutzerauthentifizierung und -verwaltung
- Druckerverwaltung
- Job-Scheduling und -Überwachung
- Smart Plug Integration
- **✅ Vollständiges UI-Komponenten-System mit Tailwind CSS**
- **✅ Template-Helper für einfache UI-Entwicklung**
- **✅ JavaScript-Utilities für interaktive Komponenten**
- **✅ Dark Mode Support**
- **✅ Responsive Design**
- **✅ Umfassende UI-Dokumentation**
## Kürzlich Abgeschlossen (Version 1.2) ✅
### Sicherheits-Features ✅
- ✅ **Rate Limiting**: Schutz vor API-Missbrauch und DDoS-Attacken
- ✅ **Content Security Policy (CSP)**: Schutz vor XSS-Angriffen
- ✅ **Erweiterte Security Headers**: Comprehensive security headers für alle Responses
- ✅ **Verdächtige Aktivitäts-Erkennung**: Automatische Erkennung von SQL-Injection und anderen Bedrohungen
- ✅ **Client-Fingerprinting**: Erweiterte Sicherheit durch Client-Identifikation
### Erweiterte Berechtigungen ✅
- ✅ **Granulare Berechtigungen**: 7 detaillierte Rollen (Guest bis Super Admin)
- ✅ **Ressourcen-spezifische Zugriffskontrolle**: Job-, Drucker- und Benutzer-spezifische Berechtigungen
- ✅ **Temporäre Berechtigungen**: Zeitlich begrenzte Berechtigungsüberschreibungen
- ✅ **Permission Caching**: Performance-optimierte Berechtigungsprüfung
- ✅ **Template-Integration**: Template-Helper für berechtigungsbasierte UI-Anzeige
### Erweiterte UI-Komponenten ✅
- ✅ **Progress-Bars**: Animierte, konfigurabr progress indicators mit verschiedenen Styles
- ✅ **Advanced File-Upload**: Drag & Drop, Preview, Chunk-Upload, Validierung
- ✅ **DatePicker**: Deutscher Kalender mit Validierung und Custom Events
- ✅ **Auto-Initialisierung**: Data-Attribute-basierte Komponenten-Initialisierung
### Analytics & Statistiken ✅
- ✅ **Umfassende Analytics-Engine**: Drucker-, Job- und Benutzer-Statistiken
- ✅ **KPI-Dashboard**: Key Performance Indicators mit Trend-Analyse
- ✅ **Report-Generierung**: Verschiedene Report-Typen und Zeiträume
- ✅ **Interaktive Charts**: Chart.js-basierte Visualisierungen
- ✅ **Export-Funktionalität**: JSON, CSV, PDF, Excel-Export (Framework bereit)
## Geplante Features
### Version 1.3 (Kurzfristig)
- [ ] **E-Mail-Benachrichtigungen**: Bei Job-Status-Änderungen und System-Events
- [ ] **Erweiterte Formular-Validierung**: Client- und serverseitige Validierung mit UI-Feedback
- [ ] **Multi-Format-Export**: Vollständige PDF- und Excel-Report-Generierung
- [ ] **Zwei-Faktor-Authentifizierung**: TOTP-basierte 2FA-Implementierung
### Version 1.3 (Mittelfristig)
- [ ] Druckerprofile mit spezifischen Eigenschaften (Druckvolumen, Materialien, etc.)
- [ ] Materialverwaltung und -tracking
- [ ] Verbessertes Dashboard mit Echtzeit-Updates
- [ ] **HTMX-Integration**: Für bessere Interaktivität ohne JavaScript-Framework
- [ ] **Drag & Drop**: Für Job-Reihenfolge und Datei-Uploads
- [ ] **Erweiterte Tabellen**: Sortierung, Filterung, Pagination
### Version 2.0 (Langfristig)
- [ ] OctoPrint Integration für direkte Druckersteuerung
- [ ] Mobile App mit Push-Benachrichtigungen
- [ ] Wartungsplanung und -tracking
- [ ] Multi-Standort-Unterstützung
- [ ] **Progressive Web App (PWA)**: Offline-Funktionalität und App-Installation
- [ ] **Erweiterte Themes**: Anpassbare Farbschemata und Layouts
## Technische Verbesserungen
### Backend
- [ ] Refactoring für verbesserte Modularität
- [ ] REST API Dokumentation mit Swagger/OpenAPI
- [ ] Verbesserte Testabdeckung
- [ ] Migration zu SQLAlchemy 2.0
- [ ] **WebSocket-Integration**: Für Echtzeit-Updates
### Frontend
- [X] ~~Optimierung der Benutzeroberfläche~~ ✅ **Abgeschlossen**
- [X] ~~UI-Komponenten-System~~ ✅ **Abgeschlossen**
- [ ] **HTMX-Integration**: Für bessere Interaktivität ohne komplexe JavaScript-Frameworks
- [ ] **Progressive Web App (PWA)**: Funktionalität für App-ähnliche Erfahrung
- [ ] **Barrierefreiheit**: Nach WCAG-Richtlinien
- [ ] **Performance-Optimierung**: Lazy Loading, Code Splitting
### CSS/Styling
- [X] ~~Tailwind CSS Integration~~ ✅ **Abgeschlossen**
- [X] ~~PostCSS Build-Pipeline~~ ✅ **Abgeschlossen**
- [X] ~~Dark Mode Support~~ ✅ **Abgeschlossen**
- [X] ~~Responsive Design~~ ✅ **Abgeschlossen**
- [ ] **CSS-Optimierung**: Purging ungenutzter Styles, Critical CSS
- [ ] **Animation-System**: Micro-Interactions und Übergänge
## Leistung und Skalierung
- [ ] Optimierung der Datenbankabfragen
- [ ] Caching-Strategie implementieren
- [ ] Asynchrone Verarbeitung für zeitintensive Aufgaben
- [ ] Docker-Container für einfache Bereitstellung
- [ ] **CDN-Integration**: Für statische Assets
- [ ] **Service Worker**: Für Offline-Funktionalität
## Sicherheit
- [ ] Security Audit durchführen
- [ ] Implementierung von CSRF-Schutz
- [ ] Rate Limiting für API-Endpunkte
- [ ] Zwei-Faktor-Authentifizierung
- [ ] **Content Security Policy (CSP)**: Schutz vor XSS-Angriffen
## Entwickler-Erfahrung
### Dokumentation ✅
- [X] ~~UI-Komponenten-Dokumentation~~ ✅ **Abgeschlossen**
- [X] ~~Tailwind CSS Setup-Guide~~ ✅ **Abgeschlossen**
- [ ] API-Dokumentation mit Swagger
- [ ] Entwickler-Handbuch
- [ ] Deployment-Guide
### Tooling
- [X] ~~PostCSS Build-System~~ ✅ **Abgeschlossen**
- [X] ~~NPM Scripts für Development~~ ✅ **Abgeschlossen**
- [ ] **Hot Reload**: Für CSS und Templates
- [ ] **Linting**: ESLint, Prettier, Flake8
- [ ] **Testing**: Unit Tests, Integration Tests, E2E Tests
## Community und Beiträge
Wir freuen uns über Beiträge und Feedback zu dieser Roadmap. Wenn Sie Vorschläge haben oder an der Entwicklung teilnehmen möchten, erstellen Sie bitte einen Issue oder Pull Request im Repository.
### Aktuelle Prioritäten für Beiträge
1. **Testing**: Unit Tests für UI-Komponenten
2. **Accessibility**: WCAG-konforme Verbesserungen
3. **Performance**: Optimierung der CSS-Größe
4. **Dokumentation**: Übersetzungen und Beispiele
## Changelog
### Version 1.1 (Dezember 2024)
- ✅ Vollständiges UI-Komponenten-System implementiert
- ✅ Template-Helper für alle gängigen UI-Elemente
- ✅ JavaScript-Utilities für interaktive Funktionen
- ✅ PostCSS Build-Pipeline mit Tailwind CSS
- ✅ Umfassende Dokumentation erstellt
- ✅ Demo-Seite für alle Komponenten
### Version 1.0 (Juni 2023)
- ✅ Grundfunktionalitäten implementiert
- ✅ Basis-UI mit Tailwind CSS
- ✅ Dark Mode Support
---
*Zuletzt aktualisiert: Dezember 2024*

View File

@ -1,211 +0,0 @@
# Tailwind CSS Konfiguration für MYP Platform
Dieses Dokument beschreibt die aktuelle Tailwind CSS Konfiguration und Build-Prozesse für die MYP Platform.
## Struktur
Die CSS-Assets des Projekts sind wie folgt strukturiert:
```
static/
css/
input.css # Tailwind Direktiven & benutzerdefinierte Styles
components.css # Wiederverwendbare UI-Komponenten
tailwind.min.css # Kompiliertes Light-Mode CSS (minimiert)
tailwind-dark.min.css # Kompiliertes Dark-Mode CSS (minimiert)
```
## Tailwind Konfiguration
Die Konfiguration wird durch die Datei `tailwind.config.js` im Hauptverzeichnis des Projekts definiert. Diese enthält:
- Farbpalette mit Mercedes-Benz Farben
- Erweiterungen für Schriften, Schatten und Animation
- Dark-Mode-Konfiguration über die Klasse `dark`
## Build-Befehle
### Light Mode CSS (Standardtheme)
Um das Standard-Light-Mode CSS zu erstellen:
```bash
npx tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.min.css --minify
```
### Dark Mode CSS
Um das Dark-Mode CSS zu erstellen:
```bash
npx tailwindcss -i ./static/css/input.css -o ./static/css/tailwind-dark.min.css --minify --dark-mode 'class'
```
## Verwendung im Projekt
Beide CSS-Dateien werden im `<head>` der Seite eingebunden:
```html
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/tailwind-dark.min.css') }}" rel="stylesheet">
```
## Dark Mode Funktionalität
Der Dark Mode wird durch JavaScript in `ui-components.js` gesteuert, welches:
1. Benutzereinstellungen in `localStorage` speichert
2. Systemeinstellungen beobachtet (via `prefers-color-scheme`)
3. Eine `dark`-Klasse zum `<html>`-Element hinzufügt/entfernt
4. Ein Tastaturkürzel (Strg+Shift+D) zum Umschalten bereitstellt
## Entwicklung
Bei der Entwicklung neuer Komponenten:
1. Füge Tailwind-Klassen direkt zu HTML-Elementen hinzu
2. Für wiederverwendbare Komponenten, nutze die `@apply`-Direktive in `components.css`
3. Nach Änderungen müssen beide CSS-Dateien neu gebaut werden
## Voraussetzungen
- Node.js und npm müssen installiert sein
- Python 3.11 und Flask müssen installiert sein
## Projektstruktur
Die Tailwind-Integration besteht aus folgenden Komponenten:
```
app/
├─ static/
│ ├─ css/
│ │ ├─ input.css # Quelldatei für Tailwind
│ │ ├─ tailwind-dark-consolidated.min.css # Generierte CSS-Datei
├─ templates/ # HTML-Templates mit Tailwind-Klassen
├─ package.json # NPM-Konfiguration
├─ tailwind.config.js # Tailwind-Konfiguration
├─ postcss.config.js # PostCSS-Konfiguration
```
## Einrichtung
Das Projekt verwendet Tailwind CSS für das Frontend-Styling. Die Einrichtung wurde bereits abgeschlossen und umfasst:
1. **NPM-Abhängigkeiten** in der `package.json`
2. **Tailwind-Konfiguration** in `tailwind.config.js`
3. **PostCSS-Konfiguration** in `postcss.config.js`
4. **CSS-Eingabedatei** in `static/css/input.css`
5. **Generierte CSS-Datei** in `static/css/tailwind-dark-consolidated.min.css`
## Verwendung im Entwicklungsprozess
### CSS-Generierung
Um an den Styles zu arbeiten, verwenden Sie die folgenden Befehle:
1. **Abhängigkeiten installieren** (falls noch nicht geschehen):
```bash
npm install
```
2. **CSS überwachen und automatisch neu generieren**:
```bash
npm run watch:css
```
Dieser Befehl startet einen Watcher, der Änderungen an HTML-Templates und der input.css überwacht und automatisch die CSS-Datei neu generiert.
3. **CSS für Produktion erstellen**:
```bash
npm run build:css
```
Dieser Befehl erstellt eine minifizierte CSS-Datei für den Produktionseinsatz.
### Workflow
Der empfohlene Workflow für die Arbeit mit Tailwind CSS ist:
1. Starten Sie den Watcher mit `npm run watch:css`
2. Bearbeiten Sie die Template-Dateien in `templates/` oder fügen Sie eigene Komponenten in `static/css/input.css` hinzu
3. Die Änderungen werden automatisch in die generierte CSS-Datei übernommen
4. Führen Sie den Flask-Server mit `python3.11 app.py` aus, um die Änderungen zu sehen
## Dark Mode
Die Anwendung unterstützt einen Dark Mode. In den Templates wird dies folgendermaßen implementiert:
```html
<html class="light" data-theme="light">
<!-- oder für Dark Mode: -->
<html class="dark" data-theme="dark">
```
In Tailwind-Klassen verwenden Sie das `dark:` Präfix:
```html
<div class="bg-white text-gray-800 dark:bg-gray-800 dark:text-white">
Dieser Text passt sich dem Theme an
</div>
```
## Anpassungen
### Eigene Komponenten hinzufügen
Um eigene Komponenten zu definieren, verwenden Sie das `@layer components` Konstrukt in der `static/css/input.css` Datei:
```css
@layer components {
.custom-button {
@apply bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700;
}
}
```
### Tailwind-Konfiguration anpassen
Die Tailwind-Konfiguration wird in der `tailwind.config.js` Datei vorgenommen. Hier können Sie:
- Farben anpassen
- Eigene Abstände definieren
- Schriftarten konfigurieren
- Plugins hinzufügen
- Theme-Einstellungen vornehmen
## Optimierung für Produktion
Für die Produktionsumgebung sollte die CSS-Datei optimiert werden:
```bash
npm run build:css
```
Dies erstellt eine minifizierte Version der CSS-Datei mit PurgeCSS, das ungenutzte CSS-Klassen entfernt.
## Fehlerbehebung
### Problem: CSS wird nicht aktualisiert
1. Stellen Sie sicher, dass der Watcher läuft (`npm run watch:css`)
2. Überprüfen Sie, ob Ihre Änderungen in einem Pfad sind, der in `tailwind.config.js` unter `content` definiert ist
3. Leeren Sie den Browser-Cache (`Ctrl+F5`)
### Problem: Node-Module nicht gefunden
```bash
npm install
```
### Problem: Tailwind-Befehle funktionieren nicht
Verwenden Sie `npx` vor dem Befehl:
```bash
npx tailwindcss -i ./static/css/input.css -o ./static/css/tailwind-dark-consolidated.min.css --minify
```
## Ressourcen
- [Offizielle Tailwind-Dokumentation](https://tailwindcss.com/docs)
- [Tailwind mit Flask einrichten](https://testdriven.io/blog/flask-htmx-tailwind/)
- [Dark Mode mit Tailwind CSS](https://tailwindcss.com/docs/dark-mode)

View File

@ -1,421 +0,0 @@
# UI-Komponenten Dokumentation - MYP Platform
Diese Dokumentation beschreibt alle verfügbaren UI-Komponenten und Template-Helper der MYP Platform.
## Übersicht
Die MYP Platform bietet ein umfassendes UI-Komponenten-System basierend auf:
- **Tailwind CSS** für das Styling
- **Jinja2 Template-Helper** für einfache Verwendung in Templates
- **JavaScript-Utilities** für interaktive Funktionen
## Template-Helper
### Buttons
#### `ui_button(text, type, size, classes, icon, onclick, disabled, **attrs)`
Erstellt styled Buttons mit verschiedenen Varianten.
**Parameter:**
- `text` (str): Button-Text
- `type` (str): Button-Typ - `"primary"`, `"secondary"`, `"danger"`, `"success"`
- `size` (str): Button-Größe - `"sm"`, `"md"`, `"lg"`
- `classes` (str): Zusätzliche CSS-Klassen
- `icon` (str): SVG-Icon-Code
- `onclick` (str): JavaScript-Code für onclick
- `disabled` (bool): Button deaktiviert
- `**attrs`: Zusätzliche HTML-Attribute
**Beispiele:**
```jinja2
{{ ui_button("Speichern", "primary", "md") }}
{{ ui_button("Löschen", "danger", "sm", icon=icons.trash) }}
{{ ui_button("Bearbeiten", "secondary", onclick="editItem(123)") }}
{{ ui_button("Deaktiviert", "primary", disabled=true) }}
```
### Badges
#### `ui_badge(text, type, classes)`
Erstellt kleine Label/Tags für Status-Anzeigen.
**Parameter:**
- `text` (str): Badge-Text
- `type` (str): Badge-Typ - `"blue"`, `"green"`, `"red"`, `"yellow"`, `"purple"`
- `classes` (str): Zusätzliche CSS-Klassen
**Beispiele:**
```jinja2
{{ ui_badge("Neu", "blue") }}
{{ ui_badge("Aktiv", "green") }}
{{ ui_badge("Fehler", "red") }}
```
#### `ui_status_badge(status, type)`
Erstellt spezielle Status-Badges für Jobs und Drucker.
**Parameter:**
- `status` (str): Status-Wert
- `type` (str): Typ - `"job"` oder `"printer"`
**Job-Status:**
- `queued` → "In Warteschlange"
- `printing` → "Wird gedruckt"
- `completed` → "Abgeschlossen"
- `failed` → "Fehlgeschlagen"
- `cancelled` → "Abgebrochen"
- `paused` → "Pausiert"
**Drucker-Status:**
- `ready` → "Bereit"
- `busy` → "Beschäftigt"
- `error` → "Fehler"
- `offline` → "Offline"
- `maintenance` → "Wartung"
**Beispiele:**
```jinja2
{{ ui_status_badge("printing", "job") }}
{{ ui_status_badge("ready", "printer") }}
```
### Cards
#### `ui_card(title, content, footer, classes, hover)`
Erstellt Container-Karten für Inhalte.
**Parameter:**
- `title` (str): Karten-Titel
- `content` (str): Karten-Inhalt (HTML möglich)
- `footer` (str): Karten-Footer (HTML möglich)
- `classes` (str): Zusätzliche CSS-Klassen
- `hover` (bool): Hover-Effekt aktivieren
**Beispiele:**
```jinja2
{{ ui_card(
title="Drucker Status",
content="<p>Ultimaker S3 ist bereit</p>",
footer=ui_button("Details", "primary", "sm"),
hover=true
) }}
```
### Alerts
#### `ui_alert(message, type, dismissible)`
Erstellt Benachrichtigungen und Warnungen.
**Parameter:**
- `message` (str): Alert-Nachricht
- `type` (str): Alert-Typ - `"info"`, `"success"`, `"warning"`, `"error"`
- `dismissible` (bool): Schließbar machen
**Beispiele:**
```jinja2
{{ ui_alert("Job erfolgreich erstellt!", "success", dismissible=true) }}
{{ ui_alert("Warnung: Drucker offline", "warning") }}
```
### Modals
#### `ui_modal(modal_id, title, content, footer, size)`
Erstellt Modal-Dialoge.
**Parameter:**
- `modal_id` (str): Eindeutige Modal-ID
- `title` (str): Modal-Titel
- `content` (str): Modal-Inhalt (HTML möglich)
- `footer` (str): Modal-Footer (HTML möglich)
- `size` (str): Modal-Größe - `"sm"`, `"md"`, `"lg"`, `"xl"`
**Beispiele:**
```jinja2
{{ ui_modal(
"confirm-delete",
"Löschen bestätigen",
"<p>Möchten Sie diesen Job wirklich löschen?</p>",
ui_button("Abbrechen", "secondary", onclick="MYP.Modal.close('confirm-delete')") + " " + ui_button("Löschen", "danger"),
"sm"
) }}
```
### Tabellen
#### `ui_table(headers, rows, classes, striped)`
Erstellt styled Tabellen.
**Parameter:**
- `headers` (List[str]): Tabellen-Kopfzeilen
- `rows` (List[List[str]]): Tabellen-Zeilen
- `classes` (str): Zusätzliche CSS-Klassen
- `striped` (bool): Zebra-Streifen aktivieren
**Beispiele:**
```jinja2
{% set headers = ["Name", "Status", "Aktionen"] %}
{% set rows = [
["Job 1", ui_status_badge("printing", "job"), ui_button("Details", "secondary", "sm")],
["Job 2", ui_status_badge("queued", "job"), ui_button("Details", "secondary", "sm")]
] %}
{{ ui_table(headers, rows, striped=true) }}
```
## Template-Filter
### Datum & Zeit
#### `german_datetime`
Formatiert Datetime-Objekte für deutsche Anzeige.
**Beispiele:**
```jinja2
{{ job.created_at|german_datetime }} <!-- 15.03.2024 14:30 -->
{{ job.created_at|german_datetime("%d.%m.%Y") }} <!-- 15.03.2024 -->
{{ job.created_at|german_datetime("%H:%M") }} <!-- 14:30 -->
```
#### `duration`
Formatiert Dauer in Minuten zu lesbarem Format.
**Beispiele:**
```jinja2
{{ 30|duration }} <!-- 30 Min -->
{{ 90|duration }} <!-- 1 Std 30 Min -->
{{ 120|duration }} <!-- 2 Std -->
```
#### `json`
Enkodiert Python-Daten als JSON für JavaScript.
**Beispiele:**
```jinja2
<script>
const jobData = {{ job_dict|json|safe }};
</script>
```
## Globale Variablen
### Icons
Verfügbare SVG-Icons über `icons.*`:
- `icons.check` - Häkchen
- `icons.x` - X/Schließen
- `icons.plus` - Plus/Hinzufügen
- `icons.edit` - Bearbeiten
- `icons.trash` - Löschen
- `icons.printer` - Drucker
- `icons.dashboard` - Dashboard
**Beispiele:**
```jinja2
{{ ui_button("Hinzufügen", "primary", icon=icons.plus) }}
{{ ui_button("Löschen", "danger", icon=icons.trash) }}
```
### Weitere Globale Variablen
- `current_year` - Aktuelles Jahr
## JavaScript-Utilities
### Toast-Nachrichten
```javascript
// Toast anzeigen
MYP.Toast.show('Nachricht', 'success'); // success, error, warning, info
// Toast ausblenden
MYP.Toast.hide();
```
### Modal-Steuerung
```javascript
// Modal öffnen
MYP.Modal.open('modal-id');
// Modal schließen
MYP.Modal.close('modal-id');
```
### Dropdown-Steuerung
```javascript
// Dropdown umschalten
MYP.Dropdown.toggle('dropdown-id');
// Dropdown schließen
MYP.Dropdown.close('dropdown-id');
```
### Loading-Anzeige
```javascript
// Loading anzeigen
MYP.Loading.show();
// Loading ausblenden
MYP.Loading.hide();
```
### Status-Helper
```javascript
// CSS-Klassen für Status abrufen
const jobClass = MYP.Status.getJobStatusClass('printing');
const printerClass = MYP.Status.getPrinterStatusClass('ready');
// Status-Text formatieren
const jobText = MYP.Status.formatJobStatus('printing');
const printerText = MYP.Status.formatPrinterStatus('ready');
```
## CSS-Klassen
### Button-Klassen
- `.btn` - Basis-Button-Klasse
- `.btn-primary` - Primärer Button (blau)
- `.btn-secondary` - Sekundärer Button (grau)
- `.btn-danger` - Gefahr-Button (rot)
- `.btn-success` - Erfolg-Button (grün)
- `.btn-sm` - Kleiner Button
- `.btn-lg` - Großer Button
### Badge-Klassen
- `.badge` - Basis-Badge-Klasse
- `.badge-blue`, `.badge-green`, `.badge-red`, `.badge-yellow`, `.badge-purple`
### Status-Klassen
**Job-Status:**
- `.job-status` - Basis-Klasse
- `.job-queued`, `.job-printing`, `.job-completed`, `.job-failed`, `.job-cancelled`, `.job-paused`
**Drucker-Status:**
- `.printer-status` - Basis-Klasse
- `.printer-ready`, `.printer-busy`, `.printer-error`, `.printer-offline`, `.printer-maintenance`
### Card-Klassen
- `.card` - Basis-Card-Klasse
- `.card-hover` - Card mit Hover-Effekt
### Alert-Klassen
- `.alert` - Basis-Alert-Klasse
- `.alert-info`, `.alert-success`, `.alert-warning`, `.alert-error`
### Tabellen-Klassen
- `.table-container` - Tabellen-Container
- `.table-styled` - Styled Tabelle
## Verwendung in Templates
### Basis-Template erweitern
```jinja2
{% extends "base.html" %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold mb-6">Meine Seite</h1>
<!-- UI-Komponenten verwenden -->
{{ ui_alert("Willkommen!", "info") }}
{{ ui_card(
title="Beispiel-Karte",
content="<p>Hier ist der Inhalt der Karte.</p>",
footer=ui_button("Aktion", "primary")
) }}
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/ui-components.js') }}"></script>
<script>
// Eigene JavaScript-Funktionen
</script>
{% endblock %}
```
### Formulare mit UI-Komponenten
```jinja2
<form>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Job Name</label>
<input type="text" class="w-full px-3 py-2 border rounded-md">
</div>
<div class="flex space-x-2">
{{ ui_button("Abbrechen", "secondary") }}
{{ ui_button("Speichern", "primary", icon=icons.check) }}
</div>
</div>
</form>
```
## Demo-Seite
Eine vollständige Demo aller UI-Komponenten ist verfügbar unter `/demo` (nur für angemeldete Benutzer).
## Anpassungen
### Eigene CSS-Klassen hinzufügen
```jinja2
{{ ui_button("Custom Button", "primary", classes="my-custom-class") }}
```
### Eigene HTML-Attribute
```jinja2
{{ ui_button("Button", "primary", data_id="123", aria_label="Speichern") }}
```
### Eigene Icons verwenden
```jinja2
{% set custom_icon = '<svg>...</svg>' %}
{{ ui_button("Custom", "primary", icon=custom_icon) }}
```
## Best Practices
1. **Konsistenz**: Verwenden Sie die UI-Komponenten für einheitliches Design
2. **Accessibility**: Nutzen Sie `aria_label` und andere Accessibility-Attribute
3. **Performance**: Laden Sie JavaScript-Utilities nur bei Bedarf
4. **Responsive**: Alle Komponenten sind responsive-ready
5. **Dark Mode**: Alle Komponenten unterstützen automatisch Dark/Light Mode
## Troubleshooting
### Komponenten werden nicht angezeigt
- Prüfen Sie, ob `register_template_helpers(app)` in `init_app()` aufgerufen wird
- Stellen Sie sicher, dass Tailwind CSS kompiliert wurde
### JavaScript-Funktionen nicht verfügbar
- Laden Sie `ui-components.js` in Ihrem Template
- Prüfen Sie die Browser-Konsole auf Fehler
### Styling-Probleme
- Stellen Sie sicher, dass die CSS-Datei geladen wird
- Prüfen Sie, ob Custom-CSS die Komponenten überschreibt

View File

@ -1,91 +0,0 @@
# Unicode-Encoding-Fehler Behebung
## Problem-Beschreibung
**Fehlermeldung:**
```
UnicodeEncodeError: 'charmap' codec can't encode characters in position 47-48: character maps to <undefined>
```
**Ursache:**
Die Anwendung verwendete Unicode-Emojis (⏱️, 🔧, etc.) in Log-Nachrichten, die unter Windows mit der cp1252-Codierung nicht dargestellt werden können.
## Implementierte Lösung
### 1. Verbesserte safe_emoji Funktion
- **Datei:** `utils/logging_config.py`
- **Änderung:** Robuste Prüfung auf cp1252-Kompatibilität unter Windows
- **Fallback:** ASCII-Ersatzzeichen für alle Emojis
### 2. Alle direkten Emoji-Verwendungen ersetzt
**Betroffene Funktionen:**
- `measure_execution_time()` - Zeile 371
- `debug_response()` - Mehrere Emojis
- `debug_request()` - Mehrere Emojis
- `log_startup_info()` - Startup-Emojis
- `setup_logging()` - Debug-Emoji
**Lösung:** Alle direkten Emoji-Strings durch `safe_emoji()` Aufrufe ersetzt.
### 3. ColoredFormatter Exception-Handling
- **Try-Catch-Block** um format()-Methode
- **Fallback:** ASCII-Ersatzzeichen bei Unicode-Fehlern
- **Erhaltung:** Originale Logging-Funktionalität
### 4. UTF-8 Encoding für File-Handler
- **Alle RotatingFileHandler** mit `encoding='utf-8'` Parameter
- **Console-Handler** UTF-8 Rekonfiguration für Windows PowerShell
- **Windows-spezifische** Console-Output-Page auf UTF-8 (CP 65001)
### 5. Erweiterte EMOJI_FALLBACK-Tabelle
```python
EMOJI_FALLBACK = {
'⏱️': '[SCHED]',
'🔧': '[PRINT]',
'🐞': '[BUG]',
'🚀': '[START]',
'📂': '[FOLDER]',
# ... weitere Fallbacks
}
```
## Windows-spezifische Verbesserungen
### Console-Konfiguration
```python
# VT100-Unterstützung aktivieren
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
# UTF-8 Console Output
kernel32.SetConsoleOutputCP(65001)
# Locale-Fallbacks
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
```
### PowerShell Stream-Rekonfiguration
```python
if hasattr(console_handler.stream, 'reconfigure'):
console_handler.stream.reconfigure(encoding='utf-8')
```
## Resultat
- **✅ Keine Unicode-Encoding-Fehler mehr**
- **✅ Robuste Emoji-Darstellung mit Fallbacks**
- **✅ UTF-8-Unterstützung für alle Log-Ausgaben**
- **✅ Windows PowerShell Kompatibilität**
- **✅ Erhaltung der ursprünglichen Logging-Funktionalität**
## Präventive Maßnahmen
1. **Immer safe_emoji() verwenden** für neue Emoji-Verwendungen
2. **UTF-8 encoding** bei allen neuen File-Handlern spezifizieren
3. **Try-Catch-Blöcke** um Unicode-kritische Operationen
4. **EMOJI_FALLBACK erweitern** für neue Emojis
## Datum der Behebung
29. Mai 2025 - 10:00 Uhr
## Getestete Umgebung
- **OS:** Windows 10 (10.0.22621)
- **Python:** 3.13
- **Shell:** PowerShell
- **Encoding:** cp1252 → UTF-8

View File

@ -1 +0,0 @@

View File

@ -1,269 +0,0 @@
# Warteschlangen-System für Offline-Drucker - Dokumentation
## Übersicht
Das Warteschlangen-System ermöglicht es Benutzern, Druckjobs auch für offline Drucker zu erstellen. Diese Jobs werden automatisch aktiviert, sobald die entsprechenden Drucker wieder online sind.
## 🔧 Funktionalitäten
### 1. **Universelle Drucker-Anzeige**
- **Alle Drucker** werden in Dropdown-Menüs angezeigt (online und offline)
- **Visuelle Unterscheidung**:
- ✅ **Online-Drucker**: Grüner Hintergrund, "ONLINE - Sofortiger Start"
- 🔄 **Offline-Drucker**: Oranger Hintergrund, "OFFLINE - Warteschlange"
- **Status-Informationen**: Letzte Überprüfungszeit wird angezeigt
### 2. **Intelligente Job-Erstellung**
- **Automatische Status-Erkennung**: System erkennt automatisch, ob Drucker online/offline ist
- **Adaptive Job-Status**:
- `scheduled` - für online Drucker (sofortiger Start)
- `waiting_for_printer` - für offline Drucker (Warteschlange)
### 3. **Background-Überwachung (Queue-Manager)**
- **Automatische Überwachung** alle 2 Minuten
- **Status-Checks** für alle Drucker mit wartenden Jobs
- **Automatische Aktivierung** von Jobs bei Online-Statuswechsel
- **Thread-sichere Implementierung** mit Daemon-Thread
### 4. **Benachrichtigungssystem**
- **Sofortige Benachrichtigungen** wenn Drucker online gehen
- **Anti-Spam-Schutz** mit 5-Minuten-Cooldown
- **Strukturierte Nachrichten** mit Job- und Drucker-Details
### 5. **Frontend-Integration**
- **Live-Status-Anzeige** der Warteschlangen
- **Manuelle Queue-Checks** per Button
- **Automatische Updates** alle 30 Sekunden
- **Benutzerfreundliche Warnungen** für offline Drucker
## 🚀 Implementierte Komponenten
### Backend-Komponenten
#### 1. **Queue-Manager** (`utils/queue_manager.py`)
```python
class PrinterQueueManager:
- start() / stop() # Queue-Manager steuern
- _monitor_loop() # Hauptüberwachungsschleife
- _check_waiting_jobs() # Job-Status prüfen und aktualisieren
- _send_job_activation_notification() # Benachrichtigungen senden
- get_queue_status() # Aktueller Warteschlangen-Status
```
**Funktionen:**
- `start_queue_manager()` - Globalen Manager starten
- `stop_queue_manager()` - Globalen Manager stoppen
- `get_queue_manager()` - Manager-Instanz abrufen
#### 2. **Erweiterte API-Endpunkte** (`app.py`)
```python
/api/queue/status # GET - Queue-Status abrufen
/api/queue/check-now # POST - Manuelle Queue-Überprüfung
/api/jobs/check-waiting # POST - Wartende Jobs prüfen (bestehend, erweitert)
```
#### 3. **Job-Erstellung mit Queue-Support**
- Automatische Status-Erkennung bei Job-Erstellung
- Intelligente Zuordnung zu `scheduled` oder `waiting_for_printer`
- Drucker-Status-Check vor Job-Erstellung
### Frontend-Komponenten
#### 1. **Verbesserte Drucker-Auswahl**
```javascript
loadPrinters() # Lädt ALLE Drucker mit Live-Status
populatePrinterSelect() # Zeigt alle Drucker mit Status-Indikatoren
setupPrinterStatusWarning() # Konfiguriert Offline-Drucker-Warnungen
```
#### 2. **Queue-Status-Management**
```javascript
loadQueueStatus() # Lädt aktuellen Warteschlangen-Status
updateQueueStatusDisplay() # Aktualisiert Status-Anzeige im UI
triggerQueueCheck() # Manuelle Queue-Überprüfung
```
#### 3. **Automatische Updates**
- **30-Sekunden-Intervall** für Job-Updates und Queue-Status
- **Live-Status-Checks** für Drucker
- **Reaktive UI-Updates** basierend auf Queue-Status
## 📋 Benutzer-Workflow
### 1. **Job für Online-Drucker erstellen**
1. Benutzer wählt **Online-Drucker** (✅ grün markiert)
2. Job wird mit Status `scheduled` erstellt
3. Job startet **sofort** zur geplanten Zeit
### 2. **Job für Offline-Drucker erstellen**
1. Benutzer wählt **Offline-Drucker** (🔄 orange markiert)
2. **Ausführliche Warnung** wird angezeigt mit Details zum Warteschlangen-Modus
3. Benutzer bestätigt **bewusst** die Warteschlangen-Erstellung
4. Job wird mit Status `waiting_for_printer` erstellt
5. **Automatische Überwachung** startet
### 3. **Automatische Job-Aktivierung**
1. **Queue-Manager** überwacht Drucker-Status alle 2 Minuten
2. Sobald Drucker **online** geht:
- Job-Status wechselt zu `scheduled`
- **Benachrichtigung** wird an Benutzer gesendet
- Job startet zur **geplanten Zeit**
## 🔧 Technische Details
### Datenbank-Schema
```sql
-- Bestehende Job-Status erweitert:
Job.status = 'waiting_for_printer' -- Neuer Status für wartende Jobs
Job.status = 'scheduled' -- Bestehender Status für geplante Jobs
-- Drucker-Status:
Printer.status = 'available'/'offline'
Printer.last_checked = DATETIME -- Letzter Status-Check
```
### Queue-Manager-Konfiguration
```python
check_interval = 120 # 2 Minuten zwischen Status-Checks
timeout = 5 # 5 Sekunden Timeout für Drucker-Ping
notification_cooldown = 300 # 5 Minuten Anti-Spam für Benachrichtigungen
```
### Frontend-Update-Intervalle
```javascript
Auto-Updates: 30 Sekunden # Jobs, Queue-Status, Drucker-Status
Manual-Check: Sofort # Benutzer-getriggerte Überprüfung
```
## 🛡️ Sicherheit & Stabilität
### 1. **Thread-Sicherheit**
- **Daemon-Thread** für Queue-Manager (stoppt automatisch bei App-Shutdown)
- **Thread-sichere Datenbank-Sessions**
- **Exception-Handling** in allen Überwachungsschleifen
### 2. **Fehler-Behandlung**
- **Graceful Degradation** bei API-Fehlern
- **Fallback-Mechanismen** für Status-Checks
- **Detaillierte Logging** für Debugging
### 3. **Performance-Optimierung**
- **Status-Caching** verhindert redundante Checks
- **Batch-Processing** für mehrere Jobs
- **Optimierte Datenbankabfragen**
### 4. **Anti-Spam-Schutz**
- **Cooldown-Mechanismus** für Benachrichtigungen
- **Rate-Limiting** für manuelle Queue-Checks
## 📊 Monitoring & Logging
### Log-Kategorien
```python
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
queue_logger.info("🟢 Drucker {name} ist ONLINE geworden")
queue_logger.info("📧 Benachrichtigung für User {user} gesendet")
queue_logger.error("❌ Fehler beim Überprüfen wartender Jobs")
```
### Queue-Status-API
```json
{
"waiting_jobs": 3,
"offline_printers_with_queue": 2,
"online_printers": 4,
"total_printers": 6,
"queue_manager_running": true,
"last_check": "2024-01-15T10:30:00Z",
"check_interval_seconds": 120
}
```
## 🎯 Vorteile des Systems
### 1. **Benutzerfreundlichkeit**
- ✅ **Alle Drucker sichtbar** - keine versteckten Optionen
- ✅ **Klare Status-Indikatoren** - sofort erkennbar welcher Drucker verfügbar ist
- ✅ **Transparente Warteschlangen** - Benutzer wissen immer, was passiert
- ✅ **Automatische Benachrichtigungen** - keine manuelle Überwachung nötig
### 2. **Technische Robustheit**
- ✅ **Automatische Überwachung** - läuft im Hintergrund ohne Benutzerinteraktion
- ✅ **Fehlerresistenz** - System funktioniert auch bei temporären Netzwerkproblemen
- ✅ **Skalierbarkeit** - unterstützt beliebig viele Drucker und Jobs
- ✅ **Thread-Sicherheit** - keine Konflikte bei parallelen Zugriffen
### 3. **Produktivitätssteigerung**
- ✅ **Keine verlorenen Jobs** - Jobs warten automatisch auf verfügbare Drucker
- ✅ **Optimale Ressourcennutzung** - Drucker werden sofort bei Verfügbarkeit genutzt
- ✅ **Reduzierte Administrationsaufwände** - automatisches Management
- ✅ **Verbesserte Planbarkeit** - transparente Warteschlangen-Informationen
## 🚀 Deployment & Konfiguration
### 1. **Automatischer Start**
Der Queue-Manager startet automatisch beim App-Start:
```python
# In app.py - Startup-Sequenz
queue_manager = start_queue_manager()
atexit.register(cleanup_queue_manager)
```
### 2. **Konfiguration**
```python
# In utils/queue_manager.py
check_interval = 120 # Überwachungsintervall (Sekunden)
notification_cooldown = 300 # Benachrichtigungs-Cooldown (Sekunden)
timeout = 5 # Drucker-Ping-Timeout (Sekunden)
```
### 3. **Dependencies**
- Keine zusätzlichen Python-Packages erforderlich
- Nutzt bestehende `threading`, `time`, `datetime` Module
- Integriert sich nahtlos in vorhandene Datenbank-Struktur
## 📝 Wartung & Troubleshooting
### Häufige Probleme & Lösungen
#### 1. **Queue-Manager läuft nicht**
```bash
# Log prüfen:
tail -f logs/queue_manager/queue_manager.log
# Manueller Neustart über API:
POST /api/queue/check-now
```
#### 2. **Drucker werden nicht erkannt**
```bash
# Drucker-Status-Check:
GET /api/printers/status/live
# Netzwerk-Konnektivität prüfen:
ping [drucker-ip]
```
#### 3. **Jobs bleiben in Warteschlange**
```bash
# Queue-Status prüfen:
GET /api/queue/status
# Manueller Check:
POST /api/queue/check-now
```
### Performance-Tuning
```python
# Für viele Drucker (>20):
check_interval = 300 # 5 Minuten statt 2 Minuten
# Für kritische Umgebungen:
check_interval = 60 # 1 Minute für schnellere Reaktion
```
---
**Implementiert am:** [Aktuelles Datum]
**Version:** 1.0
**Kompatibilität:** MYP 3D-Druck Platform v2.0+

View File

@ -1,201 +0,0 @@
# Windows Socket-Fehler Fix Dokumentation
## Problem
Bei der Entwicklung auf Windows-Systemen tritt ein Socket-Fehler beim Flask Auto-Reload auf:
```
OSError: [WinError 10038] Ein Vorgang bezog sich auf ein Objekt, das kein Socket ist
```
## Ursache
Das Problem entsteht durch:
1. Flask's Auto-Reload-Feature startet den Server neu wenn Dateien geändert werden
2. Der Queue Manager startet einen Daemon-Thread für Drucker-Überwachung
3. Beim Neustart wird der alte Thread nicht ordnungsgemäß beendet
4. Socket-Ressourcen werden nicht korrekt freigegeben
5. Windows reagiert besonders empfindlich auf nicht geschlossene Sockets
## Lösung
Implementierung eines mehrstufigen Fixes:
### 1. Verbesserter Queue Manager (`utils/queue_manager.py`)
- **Threading.Event**: Verwendung von `threading.Event` statt `time.sleep()` für unterbrechbares Warten
- **Non-Daemon Threads**: Threads werden als non-daemon erstellt für bessere Kontrolle
- **Signal-Handler**: Windows-spezifische Signal-Handler für SIGINT, SIGTERM, SIGBREAK
- **Thread-Locks**: Thread-sichere Operationen mit `threading.Lock()`
- **Ordnungsgemäße Beendigung**: Timeout-basierte Thread-Beendigung mit Logging
```python
# Verbessertes Shutdown-Handling
def stop(self):
with self._lock:
if self.is_running:
self.is_running = False
self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive():
self.monitor_thread.join(timeout=10)
```
### 2. Windows-spezifische Fixes (`utils/windows_fixes.py`)
- **Socket-Patches**: SO_REUSEADDR für Socket-Wiederverwendung
- **Thread-Manager**: Zentrale Verwaltung aller Threads
- **Signal-Handler**: SIGBREAK-Unterstützung für Windows
- **Umgebungs-Optimierung**: UTF-8 Encoding und Thread-Pool-Einstellungen
```python
def fix_windows_socket_issues():
# Socket-Wiederverwendung aktivieren
socket.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
```
### 3. Verbesserte App-Startup-Logik (`app.py`)
- **Prozess-Erkennung**: Queue Manager nur im Hauptprozess starten
- **Signal-Handling**: Windows-kompatible Signal-Handler
- **Graceful Shutdown**: Koordinierte Beendigung aller Komponenten
- **Auto-Reload-Erkennung**: Spezielle Behandlung für Flask Reloader
```python
# Nur im Hauptprozess starten (nicht bei Flask Auto-Reload)
if not debug_mode or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
queue_manager = start_queue_manager()
```
## Technische Details
### Threading-Verbesserungen
```python
# Alte Implementierung (problematisch)
while self.is_running:
self._check_waiting_jobs()
time.sleep(self.check_interval) # Nicht unterbrechbar
# Neue Implementierung (robust)
while self.is_running and not self.shutdown_event.is_set():
self._check_waiting_jobs()
if self.shutdown_event.wait(timeout=self.check_interval):
break # Sofort beenden bei Shutdown-Signal
```
### Signal-Handling
```python
# Windows-spezifische Signale
if os.name == 'nt':
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGBREAK, signal_handler) # Windows-spezifisch
```
### Socket-Optimierung
```python
# Gepatchte bind-Methode für Socket-Wiederverwendung
def patched_bind(self, address):
try:
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except:
pass
return self._bind_orig(address)
```
## Vorteile der Lösung
### 1. Robustheit
- Threads werden immer ordnungsgemäß beendet
- Socket-Ressourcen werden korrekt freigegeben
- Keine hängenden Prozesse bei Auto-Reload
### 2. Windows-Kompatibilität
- Spezielle Behandlung für Windows-Eigenarten
- SIGBREAK-Signal-Unterstützung
- SO_REUSEADDR für Socket-Wiederverwendung
### 3. Entwicklerfreundlichkeit
- Auto-Reload funktioniert ohne Fehler
- Detailliertes Logging für Debugging
- Automatische Cleanup-Prozesse
### 4. Produktions-Tauglichkeit
- Graceful Shutdown in Produktionsumgebung
- Thread-sichere Operationen
- Robuste Fehlerbehandlung
## Konfiguration
### Environment-Variablen
```bash
# Für bessere Windows-Kompatibilität
PYTHONIOENCODING=utf-8
PYTHONUTF8=1
WERKZEUG_RUN_MAIN=true
```
### Flask-Konfiguration (Debug-Modus)
```python
if os.name == 'nt': # Windows
app.run(
host="0.0.0.0",
port=5000,
debug=True,
threaded=True,
use_reloader=True,
reloader_interval=1,
passthrough_errors=False
)
```
## Monitoring
### Log-Ausgaben
```
✅ Printer Queue Manager erfolgreich gestartet
🔄 Queue-Überwachung gestartet (Intervall: 120 Sekunden)
🛑 Signal 2 empfangen - fahre System herunter...
🔄 Beende Queue Manager...
✅ Monitor-Thread erfolgreich beendet
```
### Gesundheitsprüfung
```python
def is_healthy(self) -> bool:
return (self.is_running and
self.monitor_thread is not None and
self.monitor_thread.is_alive() and
not self.shutdown_event.is_set())
```
## Bekannte Probleme und Workarounds
### Problem: Thread bleibt hängen
**Lösung**: Timeout-basierte Thread-Beendigung mit Warnung
### Problem: Socket bereits in Verwendung
**Lösung**: SO_REUSEADDR aktivieren
### Problem: Auto-Reload startet Queue Manager mehrfach
**Lösung**: Prozess-Erkennung über WERKZEUG_RUN_MAIN
## Testing
```bash
# Test mit Debug-Modus
python app.py --debug
# Test mit Produktions-Modus
python app.py
# Überwachung der Logs
tail -f logs/app/app.log | grep "Queue Manager"
```
## Wartung
- Regelmäßige Überprüfung der Thread-Gesundheit
- Monitoring der Socket-Verwendung
- Log-Analyse für hanging Threads
- Performance-Überwachung der Thread-Beendigung
## Fazit
Dieser Fix behebt das Windows Socket-Problem vollständig durch:
1. Ordnungsgemäße Thread-Verwaltung
2. Windows-spezifische Socket-Behandlung
3. Robuste Signal-Handler
4. Graceful Shutdown-Mechanismen
Das System ist jetzt sowohl für Entwicklung als auch Produktion auf Windows-Systemen stabil einsetzbar.

View File

@ -1,144 +0,0 @@
# Windows Socket-Fehler Fix - Lösung Erfolgreich Implementiert ✅
## Problem VOLLSTÄNDIG Behoben ✅
Der ursprüngliche Fehler:
```
OSError: [WinError 10038] Ein Vorgang bezog sich auf ein Objekt, das kein Socket ist
Exception in thread Thread-5 (serve_forever)
maximum recursion depth exceeded
```
**IST VOLLSTÄNDIG BEHOBEN! ✅**
## Finaler Test-Status ✅
### Vor dem Fix:
```
❌ OSError: [WinError 10038] Ein Vorgang bezog sich auf ein Objekt, das kein Socket ist
❌ Exception in thread Thread-5 (serve_forever)
❌ maximum recursion depth exceeded
❌ Flask Auto-Reload verursacht Socket-Konflikte
```
### Nach dem Fix:
```
✅ Server läuft erfolgreich auf 0.0.0.0:5000
✅ Windows-Fixes erfolgreich angewendet (sichere Version)
✅ Queue Manager im Debug-Modus korrekt deaktiviert
✅ Job-Scheduler läuft stabil
✅ Keine Socket-Fehler beim Auto-Reload
✅ Keine Rekursions-Probleme
```
## Implementierte Lösung
### 1. Verbesserter Queue Manager ✅
- **Datei**: `utils/queue_manager.py`
- **Änderungen**:
- Threading.Event für unterbrechbares Warten
- Non-daemon Threads mit ordnungsgemäßer Beendigung
- Windows Signal-Handler (SIGINT, SIGTERM, SIGBREAK)
- Thread-sichere Operationen mit Locks
- Timeout-basierte Thread-Beendigung
### 2. Sichere Windows-Fixes ✅
- **Datei**: `utils/windows_fixes.py` (SICHER)
- **Features**:
- Sichere Socket-Optimierungen OHNE Monkey-Patching
- Vermeidung von Rekursions-Problemen
- Zentraler Windows Thread-Manager
- Automatische Signal-Handler-Registrierung
- UTF-8 Umgebungs-Optimierung
### 3. Robuste App Startup-Logik ✅
- **Datei**: `app.py`
- **Verbesserungen**:
- Sichere Windows-Fixes Integration
- Windows-kompatibles Signal-Handling
- Debug-Modus ohne Auto-Reload für Windows
- Queue Manager nur im Produktionsmodus
### 4. Umfassende Dokumentation ✅
- **Datei**: `WINDOWS_SOCKET_FIX_DOCUMENTATION.md`
- Vollständige technische Dokumentation
- Troubleshooting-Guide
- Konfigurationshinweise
## Wichtige Verbesserungen
### 1. Sichere Socket-Behandlung (Neue Implementierung)
```python
# Alte problematische Implementation (Monkey-Patching)
socket.socket.bind = patched_bind # ❌ Verursacht Rekursion
# Neue sichere Implementation
def windows_bind_with_reuse(self, address):
try:
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except:
pass
return self.bind(address) # ✅ Keine Rekursion
socket.socket.windows_bind_with_reuse = windows_bind_with_reuse
```
### 2. Ordnungsgemäße Thread-Beendigung
```python
def stop(self):
with self._lock:
if self.is_running:
self.is_running = False
self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive():
self.monitor_thread.join(timeout=10)
```
### 3. Sichere Windows-Fixes
```python
# Verhindert doppelte Anwendung
if _windows_fixes_applied:
return
# Sichere Socket-Optimierungen ohne Monkey-Patching
socket.setdefaulttimeout(30)
```
## Status: ✅ VOLLSTÄNDIG BEHOBEN UND GETESTET
Das Windows Socket-Problem ist **100% gelöst**:
1. ✅ Keine Socket-Fehler mehr beim Flask Auto-Reload
2. ✅ Keine Rekursions-Probleme mehr
3. ✅ Server startet erfolgreich im Debug-Modus
4. ✅ Windows-spezifische Signal-Handler funktionieren
5. ✅ Queue Manager läuft stabil im Produktionsmodus
6. ✅ Socket-Ressourcen werden korrekt freigegeben
7. ✅ Flask Auto-Reload funktioniert fehlerfrei
## Live-Test Bestätigung ✅
```bash
# Server erfolgreich gestartet:
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.178.111:5000
# Port-Check bestätigt:
TCP 0.0.0.0:5000 0.0.0.0:0 ABHÖREN
# Logs zeigen:
✅ Windows-Fixes erfolgreich angewendet
✅ Debug-Server erfolgreich gestartet
✅ Queue Manager korrekt deaktiviert im Debug-Modus
```
## Finales Ergebnis
Der Fix ist **produktionsreif** und **vollständig getestet**. Das System ist jetzt:
- **Entwicklerfreundlich**: Flask Auto-Reload funktioniert perfekt
- **Windows-kompatibel**: Alle Windows-Eigenarten werden berücksichtigt
- **Robust**: Ordnungsgemäße Thread-Verwaltung und Socket-Handling
- **Sicher**: Keine Rekursions-Probleme oder Socket-Konflikte
- **Dokumentiert**: Vollständige Dokumentation und Troubleshooting-Guide
**🎉 Das ursprüngliche Windows Socket-Problem ist zu 100% behoben! 🎉**

View File

@ -1,219 +0,0 @@
# Mercedes-Benz MYP: Admin-Gastaufträge-Verwaltung - Vollständige Verbesserung
## Übersicht der implementierten Verbesserungen
### 1. Moderne Admin-Oberfläche
#### Neue Funktionalitäten
- **Real-Time Dashboard**: Live-Statistiken mit automatischen Updates
- **Erweiterte Filterung**: Nach Status, Name, E-Mail, Datei, Grund
- **Intelligente Sortierung**: Nach Neuigkeit, Alter und Priorität
- **Bulk-Aktionen**: Mehrere Gastaufträge gleichzeitig verwalten
- **Export-Funktionen**: CSV-Export mit anpassbaren Filtern
#### Template-Verbesserungen (`templates/admin_guest_requests.html`)
```
- Responsive Mercedes-Benz Design
- Tailwind CSS für moderne UI/UX
- Dark/Light Mode Support
- Loading-Overlays und Animationen
- Modal-Dialoge für Details und Bulk-Aktionen
- Progressive Web App Features
```
### 2. Robuste Backend-API
#### Neue API-Endpunkte
```
GET /api/admin/guest-requests - Gastaufträge mit Filterung/Paginierung
POST /api/guest-requests/{id}/approve - Gastauftrag genehmigen
POST /api/guest-requests/{id}/reject - Gastauftrag ablehnen
DEL /api/guest-requests/{id} - Gastauftrag löschen
GET /api/guest-requests/{id} - Gastauftrag-Details
GET /api/admin/guest-requests/stats - Detaillierte Statistiken
GET /api/admin/guest-requests/export - CSV-Export
```
#### Erweiterte Features
- **OTP-Code-Generierung**: Für genehmigte Gastaufträge
- **E-Mail-Benachrichtigungen**: Automatische Mitteilungen an Gäste
- **Audit-Logging**: Vollständige Nachverfolgung aller Aktionen
- **Fehlerbehandlung**: Robuste Exception-Handling-Strategien
### 3. JavaScript Frontend-Framework
#### Datei: `static/js/admin-guest-requests.js`
```javascript
- Event-driven Architecture
- Async/Await API-Calls
- Real-time Updates (alle 30 Sekunden)
- Form-Validierung
- Responsive Design-Anpassungen
- Error-Handling mit User-Feedback
```
#### Kernfunktionen
- **loadGuestRequests()**: Asynchrones Laden der Gastaufträge
- **approveRequest()**: Genehmigung mit Bestätigung
- **rejectRequest()**: Ablehnung mit Begründung
- **deleteRequest()**: Sichere Löschung mit Bestätigung
- **showRequestDetail()**: Detailansicht in Modal
- **performBulkAction()**: Batch-Operationen
- **exportToCSV()**: Client-seitiger Export
### 4. Navigation und Integration
#### Admin-Panel-Integration
- Neuer "Gastaufträge" Tab in der Admin-Navigation
- Direkte Verlinkung von `/admin/guest-requests`
- Breadcrumb-Navigation für bessere UX
- Badge-Anzeigen für wartende Requests
#### Mercedes-Benz Design-System
```css
- Corporate Colors (Silber, Dunkelgrau, Blau)
- Moderne Card-Layouts
- Gradient-Buttons
- Hover-Effekte und Transitionen
- Mobile-First Design
```
### 5. Drucker-Management-Verbesserungen
#### Robuste Drucker-Initialisierung
- **force_load_all_printers()**: "Um jeden Preis" Drucker-Loading
- **Automatischer Startup**: Initialisierung beim Systemstart
- **Fallback-Strategien**: Auch bei Fehlern werden alle Drucker verarbeitet
- **Manual Override**: Admin-Button für manuelle Initialisierung
#### API-Endpunkt
```
POST /api/admin/printers/force-initialize - Robuste Drucker-Initialisierung
```
### 6. Dashboard-Verbesserungen
#### Neue Dashboard-APIs
```
GET /api/dashboard/active-jobs - Aktive Jobs für Dashboard
GET /api/dashboard/printers - Drucker-Status für Dashboard
GET /api/dashboard/activities - Neueste Aktivitäten
POST /api/dashboard/refresh - Dashboard-Daten aktualisieren
```
### 7. Erweiterte Optimierungs-Features
#### Job-Optimierung
- **Round-Robin-Algorithmus**: Gleichmäßige Verteilung
- **Load-Balancing**: Auslastungsbasierte Verteilung
- **Priority-Based**: Prioritätsbasierte Zuweisung
- **Batch-Operationen**: Mehrere Jobs gleichzeitig verwalten
#### API-Endpunkte
```
POST /api/optimization/auto-optimize - Automatische Job-Optimierung
POST /api/jobs/batch-operation - Batch-Operationen auf Jobs
GET/POST /api/optimization/settings - Optimierungs-Einstellungen
```
## Technische Details
### Datenbank-Erweiterungen
- Neue Felder für Gastaufträge (OTP, Genehmiger, Zeitstempel)
- Index-Optimierungen für bessere Performance
- Audit-Trail für alle Admin-Aktionen
### Sicherheitsverbesserungen
- CSRF-Token-Validierung für alle API-Calls
- Admin-Required Decorators für sensitive Operationen
- Input-Sanitization und Validierung
- Rate-Limiting für API-Endpunkte
### Performance-Optimierungen
- Lazy Loading für große Datensätze
- Client-seitiges Caching mit intelligenter Aktualisierung
- Asynchrone Operationen für bessere Responsivität
- Optimierte Database-Queries mit Pagination
### Monitoring und Logging
- Detailliertes Logging aller Admin-Aktionen
- Performance-Metriken für API-Calls
- Error-Tracking mit Stack-Traces
- System-Health-Monitoring
## Benutzerfreundlichkeit
### Admin-Experience
1. **Einfacher Zugang**: Direkter Tab in Admin-Navigation
2. **Intuitive Bedienung**: Moderne UI mit klaren Aktions-Buttons
3. **Bulk-Operationen**: Mehrere Requests gleichzeitig verwalten
4. **Filterung**: Schnelles Finden spezifischer Requests
5. **Export**: CSV-Download für externe Verarbeitung
### Gast-Experience
1. **Automatische Benachrichtigungen**: E-Mails bei Status-Änderungen
2. **OTP-Codes**: Sichere Authentifizierung für genehmigte Requests
3. **Transparenz**: Klare Status-Updates und Ablehnungsgründe
## Installation und Setup
### Voraussetzungen
```bash
- Python 3.8+
- Flask 2.0+
- SQLAlchemy 1.4+
- Tailwind CSS 3.0+
```
### Aktivierung
```python
# Die neuen Features sind automatisch verfügbar nach:
1. Server-Neustart für Backend-Changes
2. Browser-Refresh für Frontend-Updates
3. Admin-Login für Zugriff auf neue Features
```
## Qualitätssicherung
### Testing
- Unit-Tests für alle neuen API-Endpunkte
- Frontend-Tests für JavaScript-Funktionen
- Integration-Tests für vollständige Workflows
- Performance-Tests für große Datensätze
### Code-Qualität
- PEP 8 konform für Python-Code
- ESLint für JavaScript-Code
- Type-Hints für bessere Wartbarkeit
- Umfassende Dokumentation
## Migration und Kompatibilität
### Backward Compatibility
- Alle bestehenden Features bleiben funktional
- Bestehende API-Endpunkte unverändert
- Graceful Degradation für ältere Browser
### Future-Proof Design
- Modulare Architektur für einfache Erweiterungen
- Konfigurierbare Features
- Skalierbare Datenstrukturen
## Support und Wartung
### Dokumentation
- Inline-Code-Kommentare in Deutsch
- API-Dokumentation mit Beispielen
- User-Guide für Admin-Funktionen
### Monitoring
- System-Logs für Debugging
- Performance-Metriken
- Error-Alerting für kritische Probleme
---
**Status**: ✅ Vollständig implementiert und produktionsbereit
**Version**: 2.0.0
**Letztes Update**: Dezember 2024
**Entwickler**: Mercedes-Benz MYP Team

View File

@ -1,187 +0,0 @@
# Admin-Panel Verbesserungen: Drucker-Management und Gastaufträge
## Übersicht der implementierten Verbesserungen
### 1. Robuste Drucker-Initialisierung
#### Neue Funktionalität
- **Automatische Startup-Initialisierung**: Alle Drucker werden beim Systemstart robust überprüft
- **Fallback-Strategien**: Auch bei Fehlern werden alle Drucker verarbeitet
- **Manueller Admin-Button**: Admins können jederzeit eine robuste Initialisierung erzwingen
#### Technische Details
- **Funktion**: `force_load_all_printers()` in `app.py`
- **API-Endpunkt**: `POST /api/admin/printers/force-initialize`
- **Startup-Integration**: Automatischer Start in separatem Thread beim Systemstart
- **Logging**: Detaillierte Protokollierung aller Initialisierungsschritte
#### Vorteile
- ✅ Drucker werden "um jeden Preis" geladen
- ✅ Korrekte Online/Offline-Markierung
- ✅ Fallback bei Netzwerkfehlern
- ✅ Keine Blockierung des Systemstarts
- ✅ Vollständige Datenbank-Persistierung
### 2. Erweiterte Admin-Navigation
#### Neue Gastauftrag-Navigation
- **Direkter Tab**: Neuer "Gastaufträge" Tab im Admin-Panel
- **Einfacher Zugang**: Ein Klick von der Admin-Hauptseite
- **Icon-Integration**: Benutzerfreundliches Icon für Gastaufträge
#### Technische Details
- **Template**: Erweiterte Navigation in `templates/admin.html`
- **Route**: Verbindung zu bestehender `/admin/guest-requests` Route
- **UI-Konsistenz**: Mercedes-Benz Design-System konform
#### Vorteile
- ✅ Einfacher Admin-Zugang zu Gastaufträgen
- ✅ Keine zusätzlichen Routen notwendig
- ✅ Konsistente Benutzerführung
### 3. Erweiterte Admin-Kontrollen
#### Neue System-Management-Features
- **Drucker-Initialisierung Button**: Manuelle Auslösung der robusten Initialisierung
- **Live-Feedback**: Echtzeit-Updates nach Initialisierung
- **Status-Verfolgung**: Automatische Dashboard-Aktualisierung
#### Technische Details
- **JavaScript**: Erweiterte `admin.js` mit `forceInitializePrinters()`
- **UI-Integration**: Neuer Button im System-Management-Bereich
- **Feedback-System**: Toast-Benachrichtigungen und Auto-Refresh
#### Vorteile
- ✅ Proaktive Drucker-Verwaltung
- ✅ Sofortige Problembehebung
- ✅ Transparente System-Kontrolle
## Implementierte Dateien
### Backend-Änderungen
1. **`app.py`**
- Neue Funktion: `force_load_all_printers()`
- Neuer API-Endpunkt: `/api/admin/printers/force-initialize`
- Startup-Integration für automatische Initialisierung
### Frontend-Änderungen
2. **`templates/admin.html`**
- Erweiterte Navigation mit Gastauftrag-Tab
- Neuer Drucker-Initialisierung-Button
3. **`static/js/admin.js`**
- Neue Funktion: `forceInitializePrinters()`
- Event-Handler-Integration
- Live-Feedback-System
## Funktionsweise
### Automatische Startup-Initialisierung
```python
# Beim Systemstart (nur im Produktionsmodus)
def startup_printer_init():
force_load_all_printers()
# In separatem Thread starten
init_thread = threading.Thread(target=startup_printer_init, daemon=True)
init_thread.start()
```
### Manuelle Admin-Initialisierung
```javascript
// Admin-Button triggert API-Aufruf
async function forceInitializePrinters() {
const response = await fetch('/api/admin/printers/force-initialize', {
method: 'POST',
headers: { 'X-CSRFToken': csrfToken }
});
// Live-Feedback und Dashboard-Update
}
```
### Robuste Fehlerbehandlung
```python
# Jeder Drucker wird verarbeitet, auch bei Fehlern
for printer in printers:
try:
# Hauptstatus-Check
status, active = check_printer_status(printer.plug_ip)
printer.status = "available" if status == "online" else "offline"
successful_updates += 1
except Exception:
# Fallback: als offline markieren, aber weiter verarbeiten
printer.status = "offline"
printer.active = False
failed_updates += 1
```
## Gastauftrag-System
### Bestehende Funktionalität (bereits implementiert)
- ✅ Vollständiges Gastauftrag-Management
- ✅ Admin-Oberfläche für Genehmigung/Ablehnung
- ✅ OTP-Code-System für Gastnutzer
- ✅ API-Endpunkte für alle Operationen
- ✅ Datenbank-Persistierung
### Neue Verbesserung
- ✅ **Einfache Navigation**: Direkter Zugang vom Admin-Panel
## Datenbank-Integration
### Drucker-Status-Persistierung
- Alle Status-Updates werden in der Datenbank gespeichert
- `last_checked` Zeitstempel für Nachverfolgung
- `active` Boolean für Online/Offline-Status
- Caching-System für Performance-Optimierung
### Transaktionale Sicherheit
- Rollback bei Datenbankfehlern
- Graceful Degradation bei Verbindungsproblemen
- Separate Error-Logs für Debugging
## Monitoring und Logging
### Detaillierte Protokollierung
```
🔄 Starte robuste Drucker-Initialisierung...
📊 5 Drucker in der Datenbank gefunden
🔍 Prüfe Drucker Ultimaker S3 (192.168.1.100)
✅ Drucker Ultimaker S3: offline → available
📈 Drucker-Initialisierung abgeschlossen:
Gesamt: 5
Online: 3
Offline: 2
Erfolgreich: 4
Fehlgeschlagen: 1
```
### Performance-Metriken
- Ausführungszeit-Messung mit `@measure_execution_time`
- Separate Logger für verschiedene Komponenten
- Startup-Performance-Optimierung durch Threading
## Benutzerfreundlichkeit
### Admin-Erfahrung
1. **Ein-Klick-Zugang**: Gastaufträge direkt vom Admin-Panel
2. **Proaktive Kontrolle**: Manuelle Drucker-Initialisierung bei Bedarf
3. **Live-Feedback**: Sofortige Rückmeldung über System-Status
4. **Automatisierung**: Startup-Initialisierung ohne Admin-Eingriff
### System-Zuverlässigkeit
1. **Fehlerresistenz**: System startet auch bei Drucker-Problemen
2. **Vollständige Abdeckung**: Alle Drucker werden verarbeitet
3. **Datenintegrität**: Transaktionale Datenbank-Updates
4. **Performance**: Non-blocking Initialisierung
## Fazit
Das System erfüllt jetzt vollständig die Anforderungen:
**Drucker werden um jeden Preis geladen**: Robuste Initialisierung mit Fallback-Strategien
**Online/Offline-Markierung**: Korrekte Status-Erfassung und Datenbank-Persistierung
**Einfacher Admin-Zugang**: Direkter Link zu Gastauftrag-Verwaltung
**Vollständige Datenbank-Integration**: Alle Operationen werden korrekt gespeichert/abgerufen
Das System ist nun produktionsreif und bietet eine umfassende, benutzerfreundliche Verwaltung von Druckern und Gastaufträgen.

View File

@ -1,385 +0,0 @@
# Live-Druckererkennungs-System - MYP Platform
## Übersicht
Das Live-Druckererkennungs-System überwacht kontinuierlich den Status aller konfigurierten Drucker und bietet Echtzeit-Updates mit Session-Caching und automatischer Steckdosen-Initialisierung.
## Features
### 🔄 Live-Monitoring
- **Echtzeit-Status-Updates** alle 30 Sekunden
- **Parallele Abfragen** für bessere Performance
- **Automatische Fehlerbehandlung** mit exponential backoff
- **Adaptive Aktualisierungsintervalle** basierend auf Seitensichtbarkeit
### 💾 Session-Caching
- **Session-basierter Cache** für schnelle Zugriffe (30 Sekunden TTL)
- **Datenbank-Cache** für persistente Daten (5 Minuten TTL)
- **Threadsicheres Caching** mit Locks
- **Automatische Cache-Invalidierung**
### 🔌 Steckdosen-Initialisierung
- **Automatischer Start** beim Programmstart
- **Einheitlicher Startzustand** (alle Steckdosen ausgeschaltet)
- **Fehlertolerante Initialisierung** mit detailliertem Logging
- **Admin-gesteuerte manuelle Initialisierung**
### 📊 Status-Kategorien
- **Online**: Drucker eingeschaltet und erreichbar
- **Standby**: Drucker ausgeschaltet aber Steckdose erreichbar
- **Offline**: Drucker/Steckdose nicht erreichbar
- **Unreachable**: Grundkonnektivität fehlgeschlagen
- **Unconfigured**: Unvollständige Konfiguration
## Backend-Architektur
### PrinterMonitor Klasse (`utils/printer_monitor.py`)
```python
class PrinterMonitor:
def __init__(self):
self.session_cache = {} # Session-Cache für schnelle Zugriffe
self.db_cache = {} # DB-Cache für persistente Daten
self.cache_lock = threading.Lock()
self.session_cache_ttl = 30 # 30 Sekunden
self.db_cache_ttl = 300 # 5 Minuten
```
#### Hauptmethoden
##### `initialize_all_outlets_on_startup()`
- Schaltet beim Programmstart alle Steckdosen aus
- Setzt alle Drucker in den gleichen Startzustand
- Protokolliert detaillierte Ergebnisse
##### `get_live_printer_status(use_session_cache=True)`
- Holt Live-Status mit mehrstufigem Caching
- Prüft Session-Cache → DB-Cache → Live-Abfrage
- Aktualisiert beide Cache-Ebenen
##### `_check_single_printer_status(printer, timeout=7)`
- Überprüft einzelnen Drucker mit umfassenden Tests
- Ping-Test für Grundkonnektivität
- Smart-Plug-Status-Abfrage über HTTP-APIs
- Unterstützt verschiedene Smart-Plug-Typen
## API-Endpunkte
### GET `/api/printers/monitor/live-status`
Holt Live-Druckerstatus mit Session-Caching.
**Parameter:**
- `use_cache` (optional): Verwende Session-Cache (default: true)
**Response:**
```json
{
"success": true,
"printers": {
"1": {
"id": 1,
"name": "Printer 1",
"status": "online",
"active": true,
"ip_address": "192.168.0.100",
"plug_ip": "192.168.0.110",
"location": "Werk 040 - Berlin - TBA",
"last_checked": "2025-01-05T10:30:00",
"ping_successful": true,
"outlet_reachable": true,
"outlet_state": "on"
}
},
"summary": {
"total": 6,
"online": 2,
"offline": 1,
"standby": 2,
"unreachable": 1,
"unconfigured": 0
},
"cache_used": true,
"timestamp": "2025-01-05T10:30:00",
"total_printers": 6
}
```
### GET `/api/printers/monitor/summary`
Schnelle Status-Zusammenfassung ohne vollständige Details.
**Response:**
```json
{
"success": true,
"summary": {
"total": 6,
"online": 2,
"offline": 1,
"standby": 2,
"unreachable": 1,
"unconfigured": 0
},
"timestamp": "2025-01-05T10:30:00"
}
```
### POST `/api/printers/monitor/clear-cache`
Löscht alle Monitor-Caches (Session und DB).
**Response:**
```json
{
"success": true,
"message": "Drucker-Monitor-Cache erfolgreich geleert"
}
```
### POST `/api/printers/monitor/initialize-outlets` (Admin)
Initialisiert alle Drucker-Steckdosen (schaltet sie aus).
**Response:**
```json
{
"success": true,
"message": "Steckdosen-Initialisierung abgeschlossen: 5/6 erfolgreich",
"results": {
"Printer 1": true,
"Printer 2": true,
"Printer 3": false,
"Printer 4": true,
"Printer 5": true,
"Printer 6": true
},
"statistics": {
"total": 6,
"successful": 5,
"failed": 1
}
}
```
## Frontend-Integration
### PrinterMonitor JavaScript-Klasse (`static/js/printer_monitor.js`)
#### Automatischer Start
```javascript
// Auto-Start auf relevanten Seiten
const relevantPages = ['/printers', '/dashboard', '/admin'];
if (relevantPages.some(page => currentPath.includes(page))) {
window.printerMonitor.start();
}
```
#### Event-Handler registrieren
```javascript
// Status-Updates abonnieren
window.printerMonitor.onUpdate((data) => {
if (data.type === 'update') {
updatePrinterDisplay(data.printers);
updateSummary(data.summary);
// Änderungen anzeigen
data.changes.forEach(change => {
if (change.type === 'status_change') {
showStatusChangeNotification(change);
}
});
}
});
```
#### Schnelle Updates aktivieren
```javascript
// Für kritische Operationen
window.printerMonitor.enableFastUpdates(); // 5 Sekunden Intervall
// Später wieder deaktivieren
window.printerMonitor.disableFastUpdates(); // 30 Sekunden Intervall
```
#### Cache-Management
```javascript
// Cache leeren und neu laden
await window.printerMonitor.clearCache();
// Sofortige Aktualisierung ohne Cache
await window.printerMonitor.forceUpdate();
```
#### Admin-Funktionen
```javascript
// Steckdosen initialisieren (nur für Admins)
try {
const result = await window.printerMonitor.initializeAllOutlets();
console.log('Initialisierung erfolgreich:', result.statistics);
} catch (error) {
console.error('Initialisierung fehlgeschlagen:', error);
}
```
## Smart-Plug-Unterstützung
Das System unterstützt verschiedene Smart-Plug-APIs:
### TP-Link Tapo
```http
GET http://192.168.0.110/status
Authorization: Basic dXNlcjpwYXNzd29yZA==
Response:
{
"system": {
"get_sysinfo": {
"relay_state": 1 // 1 = on, 0 = off
}
}
}
```
### Generische APIs
```http
# Status-Abfrage
GET http://192.168.0.110/api/status
GET http://192.168.0.110/relay/status
GET http://192.168.0.110/state
# Steckdose ausschalten
POST http://192.168.0.110/relay/off
POST http://192.168.0.110/api/relay/off
POST http://192.168.0.110/set?relay=off
```
## Konfiguration
### Drucker-Modell erweitern
```python
# models.py
class Printer(Base):
# ... bestehende Felder ...
plug_ip = Column(String(50), nullable=False)
plug_username = Column(String(100), nullable=False)
plug_password = Column(String(100), nullable=False)
last_checked = Column(DateTime, nullable=True)
```
### Rate-Limiting
```python
# Rate-Limits für API-Endpunkte (bereits in RATE_LIMITS konfiguriert)
@limit_requests("printer_monitor_live") # 5 Anfragen pro Minute
@limit_requests("printer_monitor_summary") # 10 Anfragen pro 30 Sekunden
@limit_requests("printer_monitor_cache") # 3 Anfragen pro 2 Minuten
@limit_requests("printer_monitor_init") # 2 Anfragen pro 5 Minuten
# Konfiguration in utils/rate_limiter.py:
RATE_LIMITS = {
'printer_monitor_live': RateLimit(5, 60, "Zu viele Live-Status-Anfragen..."),
'printer_monitor_summary': RateLimit(10, 30, "Zu viele Zusammenfassungs-Anfragen..."),
'printer_monitor_cache': RateLimit(3, 120, "Zu viele Cache-Lösch-Anfragen..."),
'printer_monitor_init': RateLimit(2, 300, "Zu viele Initialisierungs-Anfragen..."),
}
```
## Logging
### Log-Kategorien
- **INFO**: Normale Operationen, Status-Updates
- **WARNING**: Fehlerhafte Konfigurationen, Initialisierungsprobleme
- **ERROR**: Kritische Fehler, Netzwerkprobleme
- **DEBUG**: Detaillierte Ping/HTTP-Informationen
### Beispiel-Logs
```
2025-01-05 10:30:00 - printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
2025-01-05 10:30:01 - printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
2025-01-05 10:30:02 - printer_monitor - INFO - ✅ Printer 1: Steckdose ausgeschaltet
2025-01-05 10:30:03 - printer_monitor - WARNING - ❌ Printer 3: Steckdose konnte nicht ausgeschaltet werden
2025-01-05 10:30:05 - printer_monitor - INFO - 🎯 Steckdosen-Initialisierung abgeschlossen: 5/6 erfolgreich
```
## Performance-Optimierungen
### Threading
- **Parallele Drucker-Abfragen** mit ThreadPoolExecutor
- **Maximale Worker**: min(anzahl_drucker, 8)
- **Timeout-Handling** für hängende Anfragen
### Caching-Strategie
1. **Session-Cache** (30s): Sofortige Antworten für wiederholte Anfragen
2. **DB-Cache** (5min): Reduziert Datenbankzugriffe
3. **Adaptive TTL**: Längere Cache-Zeiten bei Fehlern
### Netzwerk-Optimierungen
- **Kurze Ping-Timeouts** (3s) für schnelle Konnektivitätsprüfung
- **HTTP-Timeouts** (5-7s) für Smart-Plug-APIs
- **Exponential Backoff** bei wiederholten Fehlern
## Fehlerbehandlung
### Automatische Wiederherstellung
- **Fehler-Zähler** mit maximal 3 Versuchen
- **Intervall-Erhöhung** bei wiederholten Fehlern
- **Cache-Fallback** bei Netzwerkproblemen
### Graceful Degradation
- **Teilweise Ergebnisse** bei einzelnen Drucker-Fehlern
- **Letzte bekannte Werte** aus Cache bei Totalausfall
- **Benutzerbenachrichtigung** über Systemprobleme
## Wartung und Monitoring
### System-Health-Checks
```bash
# Cache-Status prüfen
curl -X GET /api/printers/monitor/summary
# Cache leeren
curl -X POST /api/printers/monitor/clear-cache
# Vollständige Aktualisierung
curl -X GET "/api/printers/monitor/live-status?use_cache=false"
```
### Database-Wartung
```sql
-- Alte last_checked Werte bereinigen
UPDATE printers SET last_checked = NULL WHERE last_checked < datetime('now', '-1 day');
-- Drucker-Status-Statistiken
SELECT status, COUNT(*) FROM printers GROUP BY status;
```
## Troubleshooting
### Häufige Probleme
#### Problem: Drucker werden als "unreachable" angezeigt
**Lösung:**
1. Netzwerkverbindung prüfen
2. IP-Adressen in Datenbank validieren
3. Firewall-Regeln überprüfen
#### Problem: Steckdosen-Initialisierung schlägt fehl
**Lösung:**
1. Smart-Plug-Konfiguration prüfen (IP, Username, Password)
2. API-Endpunkte testen
3. Timeout-Werte erhöhen
#### Problem: Cache funktioniert nicht
**Lösung:**
1. Session-Konfiguration prüfen
2. Cache manuell leeren
3. Speicher-Limits überprüfen
### Debug-Befehle
```python
# Einzelnen Drucker testen
from utils.printer_monitor import printer_monitor
status = printer_monitor._check_single_printer_status(printer)
# Cache-Inhalt prüfen
print(printer_monitor.db_cache)
# Steckdose manuell testen
success = printer_monitor._turn_outlet_off("192.168.0.110", "user", "pass")
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
{
"name": "myp-platform",
"version": "3.0.0",
"description": "MYP Platform - 3D-Drucker Reservierungssystem mit TP-Link Tapo Steuerung",
"main": "app.py",
"scripts": {
"build:css": "npx tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.min.css --minify",
"watch:css": "npx tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.min.css --watch",
"build": "npm install && npm run build:css",
"start": "python app.py --port=5000"
},
"keywords": [
"flask",
"python",
"tp-link",
"tapo",
"3d-printer",
"smart-plug"
],
"author": "Mercedes-Benz",
"license": "UNLICENSED",
"private": true,
"devDependencies": {
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"postcss-selector-parser": "^6.0.13",
"tailwindcss": "^3.4.17"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.2",
"@fullcalendar/core": "^6.1.10",
"@fullcalendar/daygrid": "^6.1.10",
"@fullcalendar/interaction": "^6.1.10",
"@fullcalendar/list": "^6.1.10",
"@fullcalendar/timegrid": "^6.1.10",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"chart.js": "^4.4.9",
"chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^4.1.0",
"vite": "^6.3.5"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

View File

@ -1,57 +0,0 @@
# MYP Platform - Python Dependencies
# Basierend auf tatsächlich verwendeten Imports in app.py
# Automatisch generiert am: 2025-05-29 19:41:49
# Installiere mit: pip install -r requirements.txt
# ===== CORE FLASK FRAMEWORK =====
# Direkt in app.py verwendet
Flask==3.1.1
Flask-Login==0.6.3
Flask-WTF==1.2.1
# ===== DATENBANK =====
# SQLAlchemy für Datenbankoperationen (models.py, app.py)
SQLAlchemy==2.0.36
# ===== SICHERHEIT UND AUTHENTIFIZIERUNG =====
# Werkzeug für Passwort-Hashing und Utilities (app.py)
bcrypt==4.2.1
cryptography==44.0.0
Werkzeug==3.1.3
# ===== SMART PLUG STEUERUNG =====
# PyP100 für TP-Link Tapo Smart Plugs (utils/job_scheduler.py)
PyP100
# ===== RATE LIMITING UND CACHING =====
# Redis für Rate Limiting (utils/rate_limiter.py) - optional
redis==5.2.1
# ===== HTTP REQUESTS =====
# Requests für HTTP-Anfragen (utils/queue_manager.py, utils/debug_drucker_erkennung.py)
requests==2.32.3
# ===== TEMPLATE ENGINE =====
# Jinja2 und MarkupSafe (automatisch mit Flask installiert, aber explizit für utils/template_helpers.py)
MarkupSafe==3.0.2
# ===== SYSTEM MONITORING =====
# psutil für System-Monitoring (utils/debug_utils.py, utils/debug_cli.py)
psutil==6.1.1
# ===== ZUSÄTZLICHE CORE ABHÄNGIGKEITEN =====
# Click für CLI-Kommandos (automatisch mit Flask)
# Keine Core-Requirements
# ===== WINDOWS-SPEZIFISCHE ABHÄNGIGKEITEN =====
# Nur für Windows-Systeme erforderlich
# Keine Windows-Requirements
# ===== OPTIONAL: ENTWICKLUNG UND TESTING =====
# Nur für Entwicklungsumgebung
pytest==8.3.4; extra == "dev"
pytest-cov==6.0.0; extra == "dev"
# ===== OPTIONAL: PRODUKTIONS-SERVER =====
# Gunicorn für Produktionsumgebung
gunicorn==23.0.0; extra == "prod"

View File

@ -1,604 +0,0 @@
/**
* MYP Platform Komponenten-Bibliothek
* Erweiterte UI-Komponenten basierend auf Tailwind CSS
*/
@layer components {
/* Professionelle Mercedes-Benz Karten und Container */
.card {
@apply bg-white dark:bg-slate-900 rounded-xl shadow-lg border border-slate-200 dark:border-slate-700 p-6 m-4 transition-all duration-300;
}
.card-hover {
@apply hover:shadow-xl hover:shadow-slate-300/50 dark:hover:shadow-slate-900/50 hover:bg-slate-50 dark:hover:bg-slate-800 transform hover:-translate-y-1 transition-all duration-300;
}
.container-panel {
@apply bg-slate-50 dark:bg-slate-800 rounded-xl p-6 m-4 border border-slate-200 dark:border-slate-700 shadow-sm;
}
/* Professionelle Formulare */
.form-input {
@apply w-full rounded-xl border-2 border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-4 py-3 text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:border-blue-500 dark:focus:border-blue-400 focus:ring-4 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 transition-all duration-300;
}
.form-label {
@apply block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 transition-colors duration-300;
}
.form-group {
@apply mb-6;
}
.form-help {
@apply mt-1 text-xs text-slate-500 dark:text-slate-400 transition-colors duration-300;
}
.form-error {
@apply mt-1 text-xs text-red-600 dark:text-red-400 font-medium transition-colors duration-300;
}
/* Professionelle Buttons */
.btn-icon {
@apply inline-flex items-center justify-center rounded-xl p-3 transition-all duration-300 shadow-md hover:shadow-lg;
}
.btn-text {
@apply inline-flex items-center justify-center gap-2 rounded-xl px-6 py-3 text-sm font-semibold transition-all duration-300 shadow-md hover:shadow-lg;
}
.btn-rounded {
@apply rounded-full;
}
.btn-sm {
@apply px-4 py-2 text-xs;
}
.btn-lg {
@apply px-8 py-4 text-base;
}
/* Professionelle Badges und Tags */
.badge {
@apply inline-flex items-center rounded-full px-3 py-1.5 text-xs font-semibold transition-all duration-300 shadow-sm;
}
.badge-blue {
@apply bg-blue-100 text-blue-800 border border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700;
}
.badge-green {
@apply bg-green-100 text-green-800 border border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700;
}
.badge-red {
@apply bg-red-100 text-red-800 border border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-700;
}
.badge-yellow {
@apply bg-yellow-100 text-yellow-800 border border-yellow-200 dark:bg-yellow-900/30 dark:text-yellow-300 dark:border-yellow-700;
}
.badge-purple {
@apply bg-purple-100 text-purple-800 border border-purple-200 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-700;
}
/* Erweiterte Status Anzeigen */
.status-dot {
@apply relative flex h-3 w-3 rounded-full shadow-sm;
}
.status-dot::after {
@apply absolute top-0 left-0 h-full w-full rounded-full content-[''] animate-ping opacity-75;
}
.status-online {
@apply bg-green-500 dark:bg-green-400;
}
.status-online::after {
@apply bg-green-500 dark:bg-green-400;
}
.status-offline {
@apply bg-red-500 dark:bg-red-400;
}
.status-warning {
@apply bg-yellow-500 dark:bg-yellow-400;
}
.status-warning::after {
@apply bg-yellow-500 dark:bg-yellow-400;
}
/* Professionelle Tabellen */
.table-container {
@apply w-full overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700 shadow-lg bg-white dark:bg-slate-900;
}
.table-styled {
@apply w-full whitespace-nowrap text-left text-sm text-slate-700 dark:text-slate-300;
}
.table-styled thead {
@apply bg-slate-100 dark:bg-slate-800 transition-colors duration-300;
}
.table-styled th {
@apply px-6 py-4 font-semibold text-slate-900 dark:text-white transition-colors duration-300;
}
.table-styled tbody tr {
@apply border-t border-slate-200 dark:border-slate-700 transition-colors duration-300;
}
.table-styled tbody tr:hover {
@apply bg-slate-50 dark:bg-slate-800/50 transition-colors duration-300;
}
.table-styled td {
@apply px-6 py-4 transition-colors duration-300;
}
/* Professionelle Alert und Toast */
.alert {
@apply rounded-xl border-2 p-6 mb-4 transition-all duration-300 shadow-lg;
}
.alert-info {
@apply bg-blue-50 dark:bg-blue-900/20 border-blue-300 dark:border-blue-600 text-blue-900 dark:text-blue-200;
}
.alert-success {
@apply bg-green-50 dark:bg-green-900/20 border-green-300 dark:border-green-600 text-green-900 dark:text-green-200;
}
.alert-warning {
@apply bg-yellow-50 dark:bg-yellow-900/20 border-yellow-300 dark:border-yellow-600 text-yellow-900 dark:text-yellow-200;
}
.alert-error {
@apply bg-red-50 dark:bg-red-900/20 border-red-300 dark:border-red-600 text-red-900 dark:text-red-200;
}
/* Professionelle Navigation */
.nav-tab {
@apply inline-flex items-center gap-2 px-6 py-3 border-b-2 text-sm font-semibold transition-all duration-300;
}
.nav-tab-active {
@apply border-blue-600 dark:border-blue-400 text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 rounded-t-lg;
}
.nav-tab-inactive {
@apply border-transparent text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 hover:border-slate-300 dark:hover:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-t-lg;
}
/* Professionelle Navigation Links */
.nav-link {
@apply flex items-center gap-3 px-4 py-3 rounded-xl text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-white transition-all duration-300 font-medium;
}
.nav-link.active {
@apply bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-semibold shadow-sm;
}
/* Erweiterte Printer Status */
.printer-status {
@apply inline-flex items-center gap-2 px-4 py-2 rounded-full text-xs font-semibold shadow-sm border;
}
.printer-ready {
@apply bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 border-green-200 dark:border-green-700;
}
.printer-busy {
@apply bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-300 border-orange-200 dark:border-orange-700;
}
.printer-error {
@apply bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300 border-red-200 dark:border-red-700;
}
.printer-offline {
@apply bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-600;
}
.printer-maintenance {
@apply bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 border-purple-200 dark:border-purple-700;
}
/* Erweiterte Job Status */
.job-status {
@apply inline-flex items-center gap-2 px-4 py-2 rounded-full text-xs font-semibold shadow-sm border;
}
.job-queued {
@apply bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-600;
}
.job-printing {
@apply bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 border-blue-200 dark:border-blue-700;
}
.job-completed {
@apply bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 border-green-200 dark:border-green-700;
}
.job-failed {
@apply bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300 border-red-200 dark:border-red-700;
}
.job-cancelled {
@apply bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300 border-yellow-200 dark:border-yellow-700;
}
.job-paused {
@apply bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 border-purple-200 dark:border-purple-700;
}
/* Professionelle Buttons für beide Modi */
.btn {
@apply px-6 py-3 rounded-xl transition-all duration-300 focus:outline-none focus:ring-4 shadow-lg hover:shadow-xl font-semibold;
}
.btn-primary {
@apply btn bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500/50 shadow-blue-500/25;
}
.btn-secondary {
@apply btn bg-slate-200 hover:bg-slate-300 text-slate-800 dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white focus:ring-slate-500/50;
}
.btn-danger {
@apply btn bg-red-600 hover:bg-red-700 text-white focus:ring-red-500/50 shadow-red-500/25;
}
.btn-success {
@apply btn bg-green-600 hover:bg-green-700 text-white focus:ring-green-500/50 shadow-green-500/25;
}
/* Professionelle Mercedes-Benz Design-Komponenten */
/* Glassmorphism - Verbessert für beide Modi */
.mercedes-glass {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.dark .mercedes-glass {
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
/* Professionelle Gradients - Strikt getrennt */
.professional-gradient {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 25%, #cbd5e1 50%, #94a3b8 75%, #64748b 100%);
}
.dark .professional-gradient {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 25%, #334155 50%, #475569 75%, #64748b 100%);
}
/* Mercedes-Pattern - Verbessert */
.mercedes-pattern {
background-image:
radial-gradient(circle at 25% 25%, rgba(255,255,255,0.1) 2px, transparent 2px),
radial-gradient(circle at 75% 75%, rgba(255,255,255,0.1) 2px, transparent 2px);
background-size: 60px 60px;
}
.dark .mercedes-pattern {
background-image:
radial-gradient(circle at 25% 25%, rgba(255,255,255,0.05) 2px, transparent 2px),
radial-gradient(circle at 75% 75%, rgba(255,255,255,0.05) 2px, transparent 2px);
background-size: 60px 60px;
}
/* Professionelle Schatten - Kontextabhängig */
.professional-shadow {
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.15),
0 8px 16px rgba(0, 0, 0, 0.1),
0 0 0 1px rgba(255, 255, 255, 0.05);
}
.dark .professional-shadow {
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.5),
0 8px 16px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.1);
}
/* Professionelle Button Styles - Erweitert */
.professional-button {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}
.dark .professional-button {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.2);
}
.professional-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.professional-button:hover::before {
left: 100%;
}
.professional-button:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #1e40af 100%);
transform: translateY(-2px);
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.4);
}
.dark .professional-button:hover {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.3);
}
/* Professionelle Input Fields - Erweitert */
.input-field {
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 2px solid rgba(203, 213, 225, 0.8);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.dark .input-field {
background: rgba(51, 65, 85, 0.95);
border: 2px solid rgba(71, 85, 105, 0.8);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.input-field:focus {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.15);
border-color: #3b82f6;
background: rgba(255, 255, 255, 1);
}
.dark .input-field:focus {
background: rgba(51, 65, 85, 1);
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.2);
}
/* Professionelle Cards - Erweitert */
.professional-card {
border-radius: 1.5rem;
overflow: hidden;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20px);
border: 1px solid rgba(203, 213, 225, 0.5);
transition: all 0.3s ease;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.dark .professional-card {
background: rgba(15, 23, 42, 0.98);
border: 1px solid rgba(71, 85, 105, 0.5);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.professional-card:hover {
transform: translateY(-4px);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
.dark .professional-card:hover {
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
}
/* Professionelle Navigation Verbesserungen */
.nav-item {
position: relative;
transition: all 0.3s ease;
border-radius: 0.75rem;
}
.nav-item::after {
content: '';
position: absolute;
bottom: -2px;
left: 50%;
width: 0;
height: 2px;
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-item:hover::after,
.nav-item.active::after {
width: 100%;
}
/* Verbesserte Header-Stile */
.hero-header {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
border: 1px solid rgba(203, 213, 225, 0.5);
}
.dark .hero-header {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
border: 1px solid rgba(71, 85, 105, 0.5);
}
/* Verbesserte Container */
.main-container {
background: rgba(248, 250, 252, 0.8);
backdrop-filter: blur(10px);
}
.dark .main-container {
background: rgba(15, 23, 42, 0.8);
}
/* Professionelle Status Badges - Erweitert */
.status-badge {
display: inline-flex;
align-items: center;
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
border-radius: 9999px;
transition: all 0.2s ease;
border: 1px solid transparent;
text-transform: uppercase;
letter-spacing: 0.025em;
}
.status-badge:hover {
transform: scale(1.05);
}
/* Smooth Transitions für alle Elemente */
* {
transition:
background-color 0.3s ease,
border-color 0.3s ease,
color 0.3s ease,
box-shadow 0.3s ease,
transform 0.3s ease;
}
/* Interactive Hover Effects */
.interactive-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.interactive-hover:hover {
transform: translateY(-2px);
}
/* Light Mode spezifische Hover-Effekte */
.interactive-hover:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
.dark .interactive-hover:hover {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
/* Professional Loading States */
.loading-shimmer {
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
background-size: 200% 100%;
animation: shimmer 2s infinite;
}
.dark .loading-shimmer {
background: linear-gradient(90deg, #334155 25%, #475569 50%, #334155 75%);
background-size: 200% 100%;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Focus Indicators für Accessibility */
.focus-ring:focus {
outline: 3px solid #3b82f6;
outline-offset: 2px;
}
.dark .focus-ring:focus {
outline: 3px solid #60a5fa;
}
/* Professionelle Typography */
.professional-title {
background: linear-gradient(135deg, #1e293b 0%, #475569 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.025em;
}
.dark .professional-title {
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Responsives Design für kleine Bildschirme */
@media (max-width: 768px) {
.professional-shadow {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.professional-card {
border-radius: 1rem;
}
.mercedes-glass {
backdrop-filter: blur(15px);
}
}
/* Animationen für bessere UX */
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
.slide-up {
animation: slideUp 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Root Variablen für konsistente Farben */
:root {
--mercedes-primary: #3b82f6;
--mercedes-secondary: #64748b;
--mercedes-accent: #1d4ed8;
--shadow-light: rgba(0, 0, 0, 0.1);
--shadow-dark: rgba(0, 0, 0, 0.3);
}
.dark {
--shadow-light: rgba(0, 0, 0, 0.2);
--shadow-dark: rgba(0, 0, 0, 0.5);
}
}

View File

@ -1,237 +0,0 @@
/* Enhanced Glassmorphism Effects for MYP Application */
/* Base Glass Effects */
.glass-base {
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.glass-strong {
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
box-shadow: 0 35px 60px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.glass-subtle {
backdrop-filter: blur(16px) saturate(150%) brightness(105%);
-webkit-backdrop-filter: blur(16px) saturate(150%) brightness(105%);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* Light Mode Glass */
.glass-light {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.glass-light-strong {
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.4);
}
/* Dark Mode Glass */
.glass-dark {
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.glass-dark-strong {
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 35px 60px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
}
/* Interactive Glass Elements */
.glass-interactive {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.glass-interactive:hover {
transform: translateY(-2px);
backdrop-filter: blur(28px) saturate(220%) brightness(125%);
-webkit-backdrop-filter: blur(28px) saturate(220%) brightness(125%);
box-shadow: 0 40px 80px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.15);
}
/* Glass Navigation */
.glass-nav {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.dark .glass-nav {
background: rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* Glass Cards */
.glass-card-enhanced {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px) saturate(180%) brightness(110%);
-webkit-backdrop-filter: blur(20px) saturate(180%) brightness(110%);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.dark .glass-card-enhanced {
background: rgba(0, 0, 0, 0.7);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.glass-card-enhanced:hover {
transform: translateY(-4px);
box-shadow: 0 35px 70px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.15);
}
.dark .glass-card-enhanced:hover {
box-shadow: 0 35px 70px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
/* Glass Buttons */
.glass-btn {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(16px) saturate(150%) brightness(110%);
-webkit-backdrop-filter: blur(16px) saturate(150%) brightness(110%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.1);
transition: all 0.2s ease;
}
.dark .glass-btn {
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.glass-btn:hover {
transform: translateY(-1px);
backdrop-filter: blur(20px) saturate(170%) brightness(115%);
-webkit-backdrop-filter: blur(20px) saturate(170%) brightness(115%);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.15);
}
/* Glass Modals */
.glass-modal {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(32px) saturate(200%) brightness(115%);
-webkit-backdrop-filter: blur(32px) saturate(200%) brightness(115%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
box-shadow: 0 50px 100px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.2);
}
.dark .glass-modal {
background: rgba(0, 0, 0, 0.85);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 50px 100px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
/* Glass Form Elements */
.glass-input {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(16px) saturate(150%);
-webkit-backdrop-filter: blur(16px) saturate(150%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.05);
transition: all 0.2s ease;
}
.dark .glass-input {
background: rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.05);
}
.glass-input:focus {
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15), 0 0 0 2px rgba(59, 130, 246, 0.5);
}
/* Glass Dropdown */
.glass-dropdown {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(24px) saturate(200%) brightness(120%);
-webkit-backdrop-filter: blur(24px) saturate(200%) brightness(120%);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.dark .glass-dropdown {
background: rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
}
/* Animation for glass elements */
@keyframes glassFloat {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-2px);
}
}
.glass-float {
animation: glassFloat 3s ease-in-out infinite;
}
/* Glass overlay for backgrounds */
.glass-overlay {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
backdrop-filter: blur(40px) saturate(200%);
-webkit-backdrop-filter: blur(40px) saturate(200%);
}
.dark .glass-overlay {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.1) 100%);
}
/* Responsive glass effects */
@media (max-width: 768px) {
.glass-base,
.glass-strong,
.glass-card-enhanced {
backdrop-filter: blur(16px) saturate(150%);
-webkit-backdrop-filter: blur(16px) saturate(150%);
}
}
/* High contrast mode adjustments */
@media (prefers-contrast: high) {
.glass-base,
.glass-strong,
.glass-card-enhanced {
border-width: 2px;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.glass-interactive,
.glass-card-enhanced,
.glass-btn {
transition: none;
}
.glass-float {
animation: none;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,177 +0,0 @@
/* Erweiterte Drucker-Styles für MYP Platform */
/* Filter-Button-Styles */
.filter-btn {
transition: all 0.2s ease-in-out;
}
.filter-btn.active {
background-color: white;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
color: #374151;
}
.dark .filter-btn.active {
background-color: #475569;
color: #f1f5f9;
}
/* Online-Drucker-Hervorhebung */
.printer-card-online {
background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%);
border-color: #bbf7d0;
box-shadow: 0 1px 3px 0 rgba(34, 197, 94, 0.1), 0 1px 2px 0 rgba(34, 197, 94, 0.06);
}
.dark .printer-card-online {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, #1e293b 100%);
border-color: #166534;
box-shadow: 0 1px 3px 0 rgba(34, 197, 94, 0.2), 0 1px 2px 0 rgba(34, 197, 94, 0.1);
}
.printer-card-online:hover {
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.2), 0 2px 4px -1px rgba(34, 197, 94, 0.1);
}
.dark .printer-card-online:hover {
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.3), 0 2px 4px -1px rgba(34, 197, 94, 0.2);
}
/* Online-Indikator-Animation */
.online-indicator {
animation: pulse-green 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-green {
0%, 100% {
opacity: 1;
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);
}
50% {
opacity: .8;
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0);
}
}
/* Status-Übersicht-Animationen */
.status-count-change {
animation: count-change 0.5s ease-in-out;
}
@keyframes count-change {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* Auto-Refresh-Button-Animationen */
.auto-refresh-active {
background: linear-gradient(45deg, #10b981, #059669);
animation: gradient-shift 3s ease-in-out infinite;
}
@keyframes gradient-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* Drucker-Karten-Übergangseffekte */
.printer-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.printer-card:hover {
transform: translateY(-2px);
}
/* Loading-Spinner für Live-Updates */
.live-update-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Responsive Verbesserungen */
@media (max-width: 640px) {
.filter-btn {
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
}
.status-overview {
flex-direction: column;
gap: 0.5rem;
}
.printer-card {
padding: 1rem;
}
}
/* Dark Mode Verbesserungen */
.dark .printer-card {
background-color: #1e293b;
border-color: #334155;
}
.dark .printer-card:hover {
background-color: #334155;
}
/* Accessibility Verbesserungen */
.filter-btn:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.printer-card:focus-within {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Print-Styles */
@media print {
.filter-btn,
.auto-refresh-btn,
.printer-detail-btn,
.delete-printer-btn {
display: none;
}
.printer-card {
break-inside: avoid;
box-shadow: none;
border: 1px solid #000;
}
}
/* High Contrast Mode */
@media (prefers-contrast: high) {
.printer-card-online {
border: 2px solid #059669;
}
.online-indicator {
border: 1px solid #000;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
.online-indicator,
.auto-refresh-active,
.live-update-spinner {
animation: none;
}
.printer-card {
transition: none;
}
.printer-card:hover {
transform: none;
}
}

View File

@ -1,983 +0,0 @@
/**
* Mercedes-Benz MYP Platform - Professional Theme
* Professionelle Light/Dark Mode Implementierung
*/
/* Globale CSS-Variablen für konsistente Theming */
:root {
/* Mercedes-Benz Markenfarben */
--mb-primary: #3b82f6;
--mb-primary-dark: #1d4ed8;
--mb-secondary: #64748b;
--mb-accent: #0ea5e9;
/* Light Mode Farbpalette */
--light-bg-primary: #ffffff;
--light-bg-secondary: #f8fafc;
--light-bg-tertiary: #f1f5f9;
--light-surface: #ffffff;
--light-surface-hover: #f8fafc;
--light-text-primary: #0f172a;
--light-text-secondary: #475569;
--light-text-muted: #64748b;
--light-border: #e2e8f0;
--light-border-strong: #cbd5e1;
--light-shadow: rgba(0, 0, 0, 0.1);
--light-shadow-strong: rgba(0, 0, 0, 0.15);
/* Dark Mode Farbpalette */
--dark-bg-primary: #0f172a;
--dark-bg-secondary: #1e293b;
--dark-bg-tertiary: #334155;
--dark-surface: #1e293b;
--dark-surface-hover: #334155;
--dark-text-primary: #f8fafc;
--dark-text-secondary: #e2e8f0;
--dark-text-muted: #94a3b8;
--dark-border: #334155;
--dark-border-strong: #475569;
--dark-shadow: rgba(0, 0, 0, 0.3);
--dark-shadow-strong: rgba(0, 0, 0, 0.5);
}
/* Professionelle Hero-Header Stile */
.professional-hero {
position: relative;
overflow: hidden;
border-radius: 2rem;
margin: 1.5rem;
margin-bottom: 2rem;
background: linear-gradient(135deg, var(--light-bg-secondary) 0%, var(--light-bg-tertiary) 100%);
border: 1px solid var(--light-border);
box-shadow: 0 20px 40px var(--light-shadow);
}
.dark .professional-hero {
background: linear-gradient(135deg, var(--dark-bg-primary) 0%, var(--dark-bg-secondary) 100%);
border: 1px solid var(--dark-border);
box-shadow: 0 20px 40px var(--dark-shadow-strong);
}
.professional-hero::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
opacity: 0.5;
}
.dark .professional-hero::before {
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.05) 50%, transparent 70%);
opacity: 0.3;
}
/* Hero Pattern Overlay */
.hero-pattern {
background-image:
radial-gradient(circle at 20% 20%, var(--light-border) 1px, transparent 1px),
radial-gradient(circle at 80% 80%, var(--light-border) 1px, transparent 1px);
background-size: 50px 50px;
background-position: 0 0, 25px 25px;
}
.dark .hero-pattern {
background-image:
radial-gradient(circle at 20% 20%, var(--dark-border) 1px, transparent 1px),
radial-gradient(circle at 80% 80%, var(--dark-border) 1px, transparent 1px);
}
/* Professionelle Container */
.professional-container {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 1.5rem;
box-shadow: 0 10px 30px var(--light-shadow);
backdrop-filter: blur(20px);
transition: all 0.3s ease;
}
.dark .professional-container {
background: var(--dark-surface);
border: 1px solid var(--dark-border);
box-shadow: 0 10px 30px var(--dark-shadow);
}
.professional-container:hover {
transform: translateY(-4px);
box-shadow: 0 20px 40px var(--light-shadow-strong);
}
.dark .professional-container:hover {
box-shadow: 0 20px 40px var(--dark-shadow-strong);
}
/* Mercedes-Benz Glassmorphism Effekt */
.mb-glass {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.dark .mb-glass {
background: rgba(15, 23, 42, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.mb-glass:hover {
background: rgba(255, 255, 255, 0.95);
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
.dark .mb-glass:hover {
background: rgba(15, 23, 42, 0.95);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}
/* Professional Buttons */
.btn-professional {
background: linear-gradient(135deg, var(--mb-primary) 0%, var(--mb-primary-dark) 100%);
color: white;
border: none;
border-radius: 1rem;
padding: 0.75rem 2rem;
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.025em;
text-transform: uppercase;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}
.btn-professional::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-professional:hover::before {
left: 100%;
}
.btn-professional:hover {
background: linear-gradient(135deg, var(--mb-primary-dark) 0%, #1e40af 100%);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(59, 130, 246, 0.4);
}
.btn-professional:active {
transform: translateY(0);
}
/* Secondary Button Style */
.btn-secondary-professional {
background: var(--light-surface);
color: var(--light-text-primary);
border: 2px solid var(--light-border-strong);
border-radius: 1rem;
padding: 0.75rem 2rem;
font-weight: 600;
font-size: 0.875rem;
transition: all 0.3s ease;
box-shadow: 0 4px 15px var(--light-shadow);
}
.dark .btn-secondary-professional {
background: var(--dark-surface);
color: var(--dark-text-primary);
border-color: var(--dark-border-strong);
box-shadow: 0 4px 15px var(--dark-shadow);
}
.btn-secondary-professional:hover {
background: var(--light-surface-hover);
border-color: var(--mb-primary);
transform: translateY(-2px);
box-shadow: 0 8px 25px var(--light-shadow-strong);
}
.dark .btn-secondary-professional:hover {
background: var(--dark-surface-hover);
box-shadow: 0 8px 25px var(--dark-shadow);
}
/* Professional Input Fields */
.input-professional {
background: var(--light-surface);
border: 2px solid var(--light-border);
border-radius: 0.75rem;
padding: 0.875rem 1rem;
color: var(--light-text-primary);
font-size: 0.875rem;
transition: all 0.3s ease;
box-shadow: 0 2px 8px var(--light-shadow);
}
.dark .input-professional {
background: var(--dark-surface);
border-color: var(--dark-border);
color: var(--dark-text-primary);
box-shadow: 0 2px 8px var(--dark-shadow);
}
.input-professional:focus {
border-color: var(--mb-primary);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
transform: translateY(-1px);
}
.input-professional::placeholder {
color: var(--light-text-muted);
}
.dark .input-professional::placeholder {
color: var(--dark-text-muted);
}
/* Professional Cards */
.card-professional {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 1.25rem;
padding: 1.5rem;
box-shadow: 0 4px 20px var(--light-shadow);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.dark .card-professional {
background: var(--dark-surface);
border-color: var(--dark-border);
box-shadow: 0 4px 20px var(--dark-shadow);
}
.card-professional::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--mb-primary), var(--mb-accent));
transform: scaleX(0);
transition: transform 0.3s ease;
}
.card-professional:hover::before {
transform: scaleX(1);
}
.card-professional:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px var(--light-shadow-strong);
}
.dark .card-professional:hover {
box-shadow: 0 12px 40px var(--dark-shadow-strong);
}
/* Professional Statistics Cards */
.stat-card {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 1rem;
padding: 1.5rem;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px var(--light-shadow);
position: relative;
overflow: hidden;
}
.dark .stat-card {
background: var(--dark-surface);
border-color: var(--dark-border);
box-shadow: 0 4px 15px var(--dark-shadow);
}
.stat-card:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 30px var(--light-shadow-strong);
}
.dark .stat-card:hover {
box-shadow: 0 8px 30px var(--dark-shadow-strong);
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
color: var(--light-text-primary);
line-height: 1;
margin-bottom: 0.5rem;
}
.dark .stat-number {
color: var(--dark-text-primary);
}
.stat-label {
font-size: 0.875rem;
font-weight: 500;
color: var(--light-text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.dark .stat-label {
color: var(--dark-text-muted);
}
/* Professional Status Badges */
.status-professional {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.status-professional:hover {
transform: scale(1.05);
}
/* Status-spezifische Farben */
.status-pending {
background: rgba(251, 191, 36, 0.1);
color: #92400e;
border-color: rgba(251, 191, 36, 0.3);
}
.dark .status-pending {
background: rgba(251, 191, 36, 0.2);
color: #fbbf24;
}
.status-approved {
background: rgba(16, 185, 129, 0.1);
color: #065f46;
border-color: rgba(16, 185, 129, 0.3);
}
.dark .status-approved {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.status-denied {
background: rgba(239, 68, 68, 0.1);
color: #991b1b;
border-color: rgba(239, 68, 68, 0.3);
}
.dark .status-denied {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
/* Professional Typography */
.title-professional {
background: linear-gradient(135deg, var(--light-text-primary) 0%, var(--light-text-secondary) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.1;
}
.dark .title-professional {
background: linear-gradient(135deg, var(--dark-text-primary) 0%, var(--dark-text-secondary) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle-professional {
color: var(--light-text-muted);
font-size: 1.125rem;
line-height: 1.6;
font-weight: 400;
}
.dark .subtitle-professional {
color: var(--dark-text-muted);
}
/* Professional Navigation */
.nav-professional {
background: var(--light-surface);
border: 1px solid var(--light-border);
border-radius: 1rem;
padding: 0.5rem;
box-shadow: 0 4px 15px var(--light-shadow);
backdrop-filter: blur(20px);
}
.dark .nav-professional {
background: var(--dark-surface);
border-color: var(--dark-border);
box-shadow: 0 4px 15px var(--dark-shadow);
}
.nav-item-professional {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
color: var(--light-text-secondary);
text-decoration: none;
font-weight: 500;
transition: all 0.2s ease;
position: relative;
}
.dark .nav-item-professional {
color: var(--dark-text-secondary);
}
.nav-item-professional:hover {
background: var(--light-surface-hover);
color: var(--light-text-primary);
transform: translateX(4px);
}
.dark .nav-item-professional:hover {
background: var(--dark-surface-hover);
color: var(--dark-text-primary);
}
.nav-item-professional.active {
background: rgba(59, 130, 246, 0.1);
color: var(--mb-primary);
font-weight: 600;
}
.dark .nav-item-professional.active {
background: rgba(59, 130, 246, 0.2);
}
/* Professional Tables */
.table-professional {
width: 100%;
border-collapse: collapse;
background: var(--light-surface);
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 4px 20px var(--light-shadow);
}
.dark .table-professional {
background: var(--dark-surface);
box-shadow: 0 4px 20px var(--dark-shadow);
}
.table-professional th {
background: var(--light-bg-secondary);
color: var(--light-text-primary);
font-weight: 600;
text-align: left;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--light-border);
}
.dark .table-professional th {
background: var(--dark-bg-secondary);
color: var(--dark-text-primary);
border-bottom-color: var(--dark-border);
}
.table-professional td {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--light-border);
color: var(--light-text-secondary);
}
.dark .table-professional td {
border-bottom-color: var(--dark-border);
color: var(--dark-text-secondary);
}
.table-professional tbody tr:hover {
background: var(--light-surface-hover);
}
.dark .table-professional tbody tr:hover {
background: var(--dark-surface-hover);
}
/* Professional Alerts */
.alert-professional {
border-radius: 1rem;
padding: 1.5rem;
border: 1px solid transparent;
display: flex;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
box-shadow: 0 4px 15px var(--light-shadow);
}
.alert-info {
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.3);
color: #1e40af;
}
.dark .alert-info {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
.alert-success {
background: rgba(16, 185, 129, 0.1);
border-color: rgba(16, 185, 129, 0.3);
color: #065f46;
}
.dark .alert-success {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.alert-warning {
background: rgba(251, 191, 36, 0.1);
border-color: rgba(251, 191, 36, 0.3);
color: #92400e;
}
.dark .alert-warning {
background: rgba(251, 191, 36, 0.2);
color: #fbbf24;
}
.alert-error {
background: rgba(239, 68, 68, 0.1);
border-color: rgba(239, 68, 68, 0.3);
color: #991b1b;
}
.dark .alert-error {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
/* Background Gradients für verschiedene Seiten */
.bg-professional {
background: linear-gradient(135deg, var(--light-bg-primary) 0%, var(--light-bg-secondary) 50%, var(--light-bg-tertiary) 100%);
min-height: 100vh;
}
.dark .bg-professional {
background: linear-gradient(135deg, var(--dark-bg-primary) 0%, var(--dark-bg-secondary) 50%, var(--dark-bg-tertiary) 100%);
}
/* Utilities */
.text-professional-primary {
color: var(--light-text-primary);
}
.dark .text-professional-primary {
color: var(--dark-text-primary);
}
.text-professional-secondary {
color: var(--light-text-secondary);
}
.dark .text-professional-secondary {
color: var(--dark-text-secondary);
}
.text-professional-muted {
color: var(--light-text-muted);
}
.dark .text-professional-muted {
color: var(--dark-text-muted);
}
/* Censored Text for Privacy Protection */
.censored-text {
font-family: monospace;
background: linear-gradient(45deg, var(--light-text-secondary), var(--light-text-muted));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 500;
}
.dark .censored-text {
background: linear-gradient(45deg, var(--dark-text-secondary), var(--dark-text-muted));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Smooth transitions für alle professionellen Komponenten */
.professional-hero,
.professional-container,
.mb-glass,
.btn-professional,
.btn-secondary-professional,
.input-professional,
.card-professional,
.stat-card,
.status-professional,
.nav-professional,
.nav-item-professional,
.table-professional,
.alert-professional {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Responsive Design */
@media (max-width: 768px) {
.professional-hero {
margin: 1rem;
border-radius: 1.5rem;
}
.card-professional {
padding: 1rem;
}
.stat-number {
font-size: 2rem;
}
.btn-professional,
.btn-secondary-professional {
padding: 0.625rem 1.5rem;
font-size: 0.8rem;
}
}
/* Animation-Klassen */
.animate-fade-in {
animation: fadeInProfessional 0.6s ease-out;
}
.animate-slide-up {
animation: slideUpProfessional 0.6s ease-out;
}
.animate-scale-in {
animation: scaleInProfessional 0.4s ease-out;
}
@keyframes fadeInProfessional {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUpProfessional {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleInProfessional {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* Professional Mini Button Styles */
.btn-professional-mini {
background: rgba(59, 130, 246, 0.1);
color: var(--mb-primary);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 0.75rem;
padding: 0.5rem 1rem;
font-weight: 500;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.025em;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dark .btn-professional-mini {
background: rgba(59, 130, 246, 0.2);
border-color: rgba(59, 130, 246, 0.3);
}
.btn-professional-mini:hover {
background: rgba(59, 130, 246, 0.2);
border-color: var(--mb-primary);
transform: translateY(-1px);
}
.dark .btn-professional-mini:hover {
background: rgba(59, 130, 246, 0.3);
}
/* Danger Mini Button */
.btn-danger-professional-mini {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
border: 1px solid rgba(239, 68, 68, 0.2);
border-radius: 0.5rem;
padding: 0.5rem;
font-weight: 500;
font-size: 0.75rem;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dark .btn-danger-professional-mini {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border-color: rgba(239, 68, 68, 0.3);
}
.btn-danger-professional-mini:hover {
background: rgba(239, 68, 68, 0.2);
border-color: #dc2626;
transform: translateY(-1px) scale(1.05);
}
.dark .btn-danger-professional-mini:hover {
background: rgba(239, 68, 68, 0.3);
border-color: #ef4444;
}
/* Success Button */
.btn-success-professional {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
border-radius: 1rem;
padding: 0.75rem 2rem;
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.025em;
text-transform: uppercase;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.btn-success-professional::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-success-professional:hover::before {
left: 100%;
}
.btn-success-professional:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
}
.btn-success-professional:active {
transform: translateY(0);
}
/* Danger Button */
.btn-danger-professional {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
border: none;
border-radius: 1rem;
padding: 0.75rem 2rem;
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.025em;
text-transform: uppercase;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.btn-danger-professional::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn-danger-professional:hover::before {
left: 100%;
}
.btn-danger-professional:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4);
}
.btn-danger-professional:active {
transform: translateY(0);
}
/* Filter Button Mercedes */
.filter-btn-mercedes {
background: transparent;
color: var(--light-text-muted);
border: none;
border-radius: 0.75rem;
padding: 0.5rem 1rem;
font-weight: 500;
font-size: 0.875rem;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
}
.dark .filter-btn-mercedes {
color: var(--dark-text-muted);
}
.filter-btn-mercedes:hover {
color: var(--light-text-primary);
background: rgba(255, 255, 255, 0.1);
}
.dark .filter-btn-mercedes:hover {
color: var(--dark-text-primary);
background: rgba(255, 255, 255, 0.05);
}
.filter-btn-mercedes.active {
background: var(--mb-primary);
color: white;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}
/* Status Dots */
.status-dot {
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
display: inline-block;
position: relative;
}
.status-dot.status-online {
background: #10b981;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
}
.status-dot.status-offline {
background: #ef4444;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
.status-dot.status-online::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
border-radius: 50%;
background: inherit;
animation: pulse-professional 2s infinite;
}
/* Professional Shadow */
.professional-shadow {
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.2);
}
.dark .professional-shadow {
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.4);
}
/* Professional Accent Colors */
.text-professional-accent {
color: var(--mb-accent);
}
.bg-professional-accent {
background-color: var(--mb-accent);
}
/* Animate Spin Slow */
.animate-spin-slow {
animation: spin 3s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes pulse-professional {
0%, 100% {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
50% {
opacity: 0.5;
transform: translate(-50%, -50%) scale(1.2);
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80">
<defs>
<style>
.mb-logo { fill: currentColor; }
@media (prefers-color-scheme: dark) {
.mb-logo { fill: #ffffff; }
}
@media (prefers-color-scheme: light) {
.mb-logo { fill: #000000; }
}
</style>
</defs>
<!-- Mercedes-Benz Logo -->
<path class="mb-logo" d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
c0-13.1,7-24.5,17.5-30.9v0C26.6,6,32.5,4.2,39,4l-4.5,32.7L21.5,46.8v0L8.3,57.1C5.6,52,4,46.2,4,40z M58.6,70.8
C53.1,74.1,46.8,76,40,76c-6.8,0-13.2-1.9-18.6-5.2c-4.9-2.9-8.9-6.9-11.9-11.7l11.9-4.9v0L40,46.6l18.6,7.5v0l12,4.9
C67.6,63.9,63.4,67.9,58.6,70.8z M58.6,46.8L58.6,46.8l-12.9-10L41.1,4c6.3,0.2,12.3,2,17.4,5.1v0C69,15.4,76,26.9,76,40
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

View File

@ -1,75 +0,0 @@
#!/usr/bin/env python3
"""
Icon-Generator für Mercedes-Benz MYP Platform
Generiert PWA-Icons in verschiedenen Größen
"""
import os
from PIL import Image, ImageDraw, ImageFont
def create_mercedes_icon(size, output_path):
"""
Erstellt ein einfaches Mercedes-Benz-Logo-Icon
Args:
size: Größe des Icons (quadratisch)
output_path: Ausgabepfad für das Icon
"""
# Erstelle ein schwarzes quadratisches Bild
img = Image.new('RGB', (size, size), color='#000000')
draw = ImageDraw.Draw(img)
# Berechne Kreis-Dimensionen
center = size // 2
radius = int(size * 0.4) # 80% der Größe für den äußeren Kreis
# Äußerer weißer Kreis
draw.ellipse([center - radius, center - radius, center + radius, center + radius],
outline='#FFFFFF', width=max(2, size // 50))
# Mercedes-Stern (vereinfacht)
star_radius = int(radius * 0.7)
# Drei Linien für den Mercedes-Stern
# Linie nach oben
draw.line([center, center, center, center - star_radius],
fill='#FFFFFF', width=max(2, size // 40))
# Linie nach rechts unten (60°)
import math
angle1 = math.radians(60)
x1 = center + int(star_radius * math.sin(angle1))
y1 = center + int(star_radius * math.cos(angle1))
draw.line([center, center, x1, y1],
fill='#FFFFFF', width=max(2, size // 40))
# Linie nach links unten (120°)
angle2 = math.radians(120)
x2 = center + int(star_radius * math.sin(angle2))
y2 = center + int(star_radius * math.cos(angle2))
draw.line([center, center, x2, y2],
fill='#FFFFFF', width=max(2, size // 40))
# Speichere das Bild
img.save(output_path, 'PNG', optimize=True)
print(f"✅ Icon erstellt: {output_path} ({size}x{size})")
def main():
"""Generiert alle benötigten Icon-Größen"""
sizes = [72, 96, 128, 144, 152, 192, 384, 512]
# Stelle sicher, dass das Verzeichnis existiert
os.makedirs('static/icons', exist_ok=True)
for size in sizes:
output_path = f'icon-{size}x{size}.png'
try:
create_mercedes_icon(size, output_path)
except Exception as e:
print(f"❌ Fehler beim Erstellen von {output_path}: {str(e)}")
print("\n🎯 Alle Icons wurden erfolgreich generiert!")
print("📁 Verzeichnis: static/icons/")
if __name__ == "__main__":
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 551 B

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="192" height="192">
<circle cx="96" cy="96" r="96" fill="#000000"/>
<path d="M96 12c46.392 0 84 37.608 84 84s-37.608 84-84 84-84-37.608-84-84 37.608-84 84-84m0-12C42.98 0 0 42.98 0 96s42.98 96 96 96 96-42.98 96-96S149.02 0 96 0z" fill="#ffffff"/>
<path d="M96 30l21.65 57.68L174 96l-56.35 8.32L96 162l-21.65-57.68L18 96l56.35-8.32L96 30z" fill="#ffffff"/>
</svg>

Before

Width:  |  Height:  |  Size: 476 B

View File

@ -1,584 +0,0 @@
/**
* MYP Admin Dashboard
* Core JavaScript für das Admin-Dashboard
*/
document.addEventListener('DOMContentLoaded', function() {
// Initialize navigation
initNavigation();
// Initialize modal events
initModals();
// Load initial data
loadDashboardData();
});
/**
* Navigation Initialization
*/
function initNavigation() {
// Desktop navigation
const desktopNavItems = document.querySelectorAll('.admin-nav-item');
desktopNavItems.forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
activateSection(section);
updateActiveNavItem(this, desktopNavItems);
});
});
// Mobile navigation
const mobileNavItems = document.querySelectorAll('.mobile-nav-item');
mobileNavItems.forEach(item => {
item.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
activateSection(section);
updateActiveNavItem(this, mobileNavItems);
closeMobileNav();
});
});
// Mobile menu toggle
const mobileMenuButton = document.getElementById('mobile-menu-button');
const closeMobileNavButton = document.getElementById('close-mobile-nav');
if (mobileMenuButton) {
mobileMenuButton.addEventListener('click', openMobileNav);
}
if (closeMobileNavButton) {
closeMobileNavButton.addEventListener('click', closeMobileNav);
}
// Setup hash navigation
window.addEventListener('hashchange', handleHashChange);
if (window.location.hash) {
handleHashChange();
}
}
function activateSection(section) {
// Hide all sections
document.querySelectorAll('.admin-section').forEach(el => {
el.classList.remove('active');
el.classList.add('hidden');
});
// Show selected section
const targetSection = document.getElementById(`${section}-section`);
if (targetSection) {
targetSection.classList.remove('hidden');
targetSection.classList.add('active');
// Load section data if needed
switch(section) {
case 'dashboard':
loadDashboardData();
break;
case 'users':
loadUsers();
break;
case 'printers':
loadPrinters();
break;
case 'scheduler':
loadSchedulerStatus();
break;
case 'logs':
loadLogs();
break;
}
// Update URL hash
window.location.hash = section;
}
}
function updateActiveNavItem(activeItem, allItems) {
// Remove active class from all items
allItems.forEach(item => {
item.classList.remove('active');
});
// Add active class to selected item
activeItem.classList.add('active');
}
function handleHashChange() {
const hash = window.location.hash.substring(1);
if (hash) {
const navItem = document.querySelector(`.admin-nav-item[data-section="${hash}"]`);
if (navItem) {
activateSection(hash);
updateActiveNavItem(navItem, document.querySelectorAll('.admin-nav-item'));
// Also update mobile nav
const mobileNavItem = document.querySelector(`.mobile-nav-item[data-section="${hash}"]`);
if (mobileNavItem) {
updateActiveNavItem(mobileNavItem, document.querySelectorAll('.mobile-nav-item'));
}
}
}
}
function openMobileNav() {
const mobileNav = document.getElementById('mobile-nav');
if (mobileNav) {
mobileNav.classList.remove('hidden');
}
}
function closeMobileNav() {
const mobileNav = document.getElementById('mobile-nav');
if (mobileNav) {
mobileNav.classList.add('hidden');
}
}
/**
* Modal Initialization
*/
function initModals() {
// Delete modal
const deleteModal = document.getElementById('delete-modal');
const closeDeleteModalBtn = document.getElementById('close-delete-modal');
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
if (closeDeleteModalBtn) {
closeDeleteModalBtn.addEventListener('click', closeDeleteModal);
}
if (cancelDeleteBtn) {
cancelDeleteBtn.addEventListener('click', closeDeleteModal);
}
// Toast notification
const closeToastBtn = document.getElementById('close-toast');
if (closeToastBtn) {
closeToastBtn.addEventListener('click', closeToast);
}
// Global refresh button
const refreshAllBtn = document.getElementById('refresh-all-btn');
if (refreshAllBtn) {
refreshAllBtn.addEventListener('click', refreshAllData);
}
}
function showDeleteModal(message, onConfirm) {
const modal = document.getElementById('delete-modal');
const messageEl = document.getElementById('delete-message');
const confirmBtn = document.getElementById('confirm-delete-btn');
if (modal && messageEl && confirmBtn) {
messageEl.textContent = message;
modal.classList.add('modal-show');
// Setup confirm button action
confirmBtn.onclick = function() {
closeDeleteModal();
if (typeof onConfirm === 'function') {
onConfirm();
}
};
}
}
function closeDeleteModal() {
const modal = document.getElementById('delete-modal');
if (modal) {
modal.classList.remove('modal-show');
}
}
function showToast(message, type = 'info') {
const toast = document.getElementById('toast-notification');
const messageEl = document.getElementById('toast-message');
const iconEl = document.getElementById('toast-icon');
if (toast && messageEl && iconEl) {
messageEl.textContent = message;
// Set icon based on type
const iconSvg = getToastIcon(type);
iconEl.innerHTML = iconSvg;
// Show toast
toast.classList.add('toast-show');
// Auto-hide after 5 seconds
setTimeout(closeToast, 5000);
}
}
function closeToast() {
const toast = document.getElementById('toast-notification');
if (toast) {
toast.classList.remove('toast-show');
}
}
function getToastIcon(type) {
switch(type) {
case 'success':
return '<svg class="h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>';
case 'error':
return '<svg class="h-6 w-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>';
case 'warning':
return '<svg class="h-6 w-6 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>';
case 'info':
default:
return '<svg class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';
}
}
/**
* Dashboard Data Loading
*/
function loadDashboardData() {
// Load dashboard stats
loadStats();
// Load recent activity
loadRecentActivity();
// Load system status
loadSystemStatus();
// Setup refresh buttons
const refreshActivityBtn = document.getElementById('refresh-activity-btn');
if (refreshActivityBtn) {
refreshActivityBtn.addEventListener('click', loadRecentActivity);
}
const refreshSystemBtn = document.getElementById('refresh-system-btn');
if (refreshSystemBtn) {
refreshSystemBtn.addEventListener('click', loadSystemStatus);
}
}
async function loadStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
// Update dashboard counters
document.getElementById('total-users-count').textContent = data.total_users || 0;
document.getElementById('total-printers-count').textContent = data.total_printers || 0;
document.getElementById('active-jobs-count').textContent = data.active_jobs || 0;
// Update scheduler status
updateSchedulerStatusIndicator(data.scheduler_status || false);
} catch (error) {
console.error('Error loading stats:', error);
showToast('Fehler beim Laden der Statistiken', 'error');
}
}
function updateSchedulerStatusIndicator(isRunning) {
const statusText = document.getElementById('scheduler-status');
const indicator = document.getElementById('scheduler-indicator');
if (statusText && indicator) {
if (isRunning) {
statusText.textContent = 'Aktiv';
statusText.classList.add('text-green-600', 'dark:text-green-400');
statusText.classList.remove('text-red-600', 'dark:text-red-400');
indicator.classList.add('bg-green-500');
indicator.classList.remove('bg-red-500', 'bg-gray-300');
} else {
statusText.textContent = 'Inaktiv';
statusText.classList.add('text-red-600', 'dark:text-red-400');
statusText.classList.remove('text-green-600', 'dark:text-green-400');
indicator.classList.add('bg-red-500');
indicator.classList.remove('bg-green-500', 'bg-gray-300');
}
}
}
async function loadRecentActivity() {
const container = document.getElementById('recent-activity-container');
if (!container) return;
// Show loading state
container.innerHTML = `
<div class="flex justify-center items-center py-8">
<svg class="animate-spin h-8 w-8 text-accent-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
`;
try {
const response = await fetch('/api/activity/recent');
const data = await response.json();
if (data.activities && data.activities.length > 0) {
const activities = data.activities;
const html = activities.map(activity => `
<div class="p-3 rounded-lg bg-light-surface dark:bg-dark-surface border border-light-border dark:border-dark-border">
<div class="flex items-start">
<div class="w-2 h-2 rounded-full bg-accent-primary mt-2 mr-3 flex-shrink-0"></div>
<div>
<p class="text-sm text-light-text dark:text-dark-text">${activity.description}</p>
<p class="text-xs text-light-text-muted dark:text-dark-text-muted mt-1">
${formatDateTime(activity.timestamp)}
</p>
</div>
</div>
</div>
`).join('');
container.innerHTML = html;
} else {
container.innerHTML = `
<div class="text-center py-8">
<p class="text-light-text-muted dark:text-dark-text-muted">Keine Aktivitäten gefunden</p>
</div>
`;
}
} catch (error) {
console.error('Error loading activities:', error);
container.innerHTML = `
<div class="text-center py-8">
<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Aktivitäten</p>
</div>
`;
}
}
async function loadSystemStatus() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
// Update system stats
document.getElementById('total-print-time').textContent =
formatPrintTime(data.total_print_time_hours);
document.getElementById('completed-jobs-count').textContent =
data.total_jobs_completed || 0;
document.getElementById('total-material-used').textContent =
formatMaterialUsed(data.total_material_used);
document.getElementById('last-updated-time').textContent =
data.last_updated ? formatDateTime(data.last_updated) : '-';
} catch (error) {
console.error('Error loading system status:', error);
showToast('Fehler beim Laden des Systemstatus', 'error');
}
}
function formatPrintTime(hours) {
if (!hours) return '-';
return `${hours} Stunden`;
}
function formatMaterialUsed(grams) {
if (!grams) return '-';
return `${grams} g`;
}
function formatDateTime(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
/**
* Global refresh function
*/
function refreshAllData() {
// Get active section
const activeSection = document.querySelector('.admin-section.active');
if (activeSection) {
const sectionId = activeSection.id;
const section = sectionId.replace('-section', '');
// Reload data based on active section
switch(section) {
case 'dashboard':
loadDashboardData();
break;
case 'users':
loadUsers();
break;
case 'printers':
loadPrinters();
break;
case 'scheduler':
loadSchedulerStatus();
break;
case 'logs':
loadLogs();
break;
}
}
showToast('Daten aktualisiert', 'success');
}
/**
* Benutzer laden und anzeigen
*/
function loadUsers() {
const usersContainer = document.getElementById('users-container');
if (!usersContainer) return;
// Lade-Animation anzeigen
usersContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
</div>
`;
// Benutzer vom Server laden
fetch('/api/users')
.then(response => {
if (!response.ok) throw new Error('Fehler beim Laden der Benutzer');
return response.json();
})
.then(data => {
renderUsers(data.users);
updateUserStatistics(data.users);
})
.catch(error => {
console.error('Fehler beim Laden der Benutzer:', error);
usersContainer.innerHTML = `
<div class="text-center py-8">
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Benutzer</div>
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
<button onclick="loadUsers()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
});
}
/**
* Drucker laden und anzeigen
*/
function loadPrinters() {
const printersContainer = document.getElementById('printers-container');
if (!printersContainer) return;
// Lade-Animation anzeigen
printersContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
</div>
`;
// Drucker vom Server laden
fetch('/api/printers')
.then(response => {
if (!response.ok) throw new Error('Fehler beim Laden der Drucker');
return response.json();
})
.then(data => {
renderPrinters(data.printers);
updatePrinterStatistics(data.printers);
})
.catch(error => {
console.error('Fehler beim Laden der Drucker:', error);
printersContainer.innerHTML = `
<div class="text-center py-8">
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Drucker</div>
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
<button onclick="loadPrinters()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
});
}
/**
* Scheduler-Status laden und anzeigen
*/
function loadSchedulerStatus() {
const schedulerContainer = document.getElementById('scheduler-container');
if (!schedulerContainer) return;
// Lade-Animation anzeigen
schedulerContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
</div>
`;
// Scheduler-Status vom Server laden
fetch('/api/scheduler/status')
.then(response => {
if (!response.ok) throw new Error('Fehler beim Laden des Scheduler-Status');
return response.json();
})
.then(data => {
renderSchedulerStatus(data);
updateSchedulerControls(data.active);
})
.catch(error => {
console.error('Fehler beim Laden des Scheduler-Status:', error);
schedulerContainer.innerHTML = `
<div class="text-center py-8">
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden des Scheduler-Status</div>
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
<button onclick="loadSchedulerStatus()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
});
}
/**
* Logs laden und anzeigen
*/
function loadLogs() {
const logsContainer = document.getElementById('logs-container');
if (!logsContainer) return;
// Lade-Animation anzeigen
logsContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
</div>
`;
// Logs vom Server laden
fetch('/api/logs')
.then(response => {
if (!response.ok) throw new Error('Fehler beim Laden der Logs');
return response.json();
})
.then(data => {
window.logsData = data.logs;
window.filteredLogs = [...data.logs];
renderLogs();
updateLogStatistics();
scrollLogsToBottom();
})
.catch(error => {
console.error('Fehler beim Laden der Logs:', error);
logsContainer.innerHTML = `
<div class="text-center py-8">
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Logs</div>
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
<button onclick="loadLogs()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
});
}

View File

@ -1,875 +0,0 @@
/**
* Mercedes-Benz MYP Admin Guest Requests Management
* Moderne Verwaltung von Gastaufträgen mit Live-Updates
*/
// Globale Variablen
let currentRequests = [];
let filteredRequests = [];
let currentPage = 0;
let totalPages = 0;
let totalRequests = 0;
let refreshInterval = null;
let csrfToken = '';
// API Base URL Detection - Korrigierte Version für CSP-Kompatibilität
function detectApiBaseUrl() {
// Für lokale Entwicklung und CSP-Kompatibilität immer relative URLs verwenden
// Das verhindert CSP-Probleme mit connect-src
return ''; // Leerer String für relative URLs
}
const API_BASE_URL = detectApiBaseUrl();
// Initialisierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
// CSRF Token abrufen
csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
// Event Listeners initialisieren
initEventListeners();
// Daten initial laden
loadGuestRequests();
// Auto-Refresh starten
startAutoRefresh();
console.log('🎯 Admin Guest Requests Management geladen');
});
/**
* Event Listeners initialisieren
*/
function initEventListeners() {
// Search Input
const searchInput = document.getElementById('search-requests');
if (searchInput) {
searchInput.addEventListener('input', debounce(handleSearch, 300));
}
// Status Filter
const statusFilter = document.getElementById('status-filter');
if (statusFilter) {
statusFilter.addEventListener('change', handleFilterChange);
}
// Sort Order
const sortOrder = document.getElementById('sort-order');
if (sortOrder) {
sortOrder.addEventListener('change', handleSortChange);
}
// Action Buttons
const refreshBtn = document.getElementById('refresh-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
loadGuestRequests();
showNotification('🔄 Gastaufträge aktualisiert', 'info');
});
}
const exportBtn = document.getElementById('export-btn');
if (exportBtn) {
exportBtn.addEventListener('click', handleExport);
}
const bulkActionsBtn = document.getElementById('bulk-actions-btn');
if (bulkActionsBtn) {
bulkActionsBtn.addEventListener('click', showBulkActionsModal);
}
// Select All Checkbox
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', handleSelectAll);
}
}
/**
* Gastaufträge von der API laden
*/
async function loadGuestRequests() {
try {
showLoading(true);
const url = `${API_BASE_URL}/api/admin/guest-requests`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
currentRequests = data.requests || [];
totalRequests = data.total || 0;
// Statistiken aktualisieren
updateStats(data.stats || {});
// Tabelle aktualisieren
applyFiltersAndSort();
console.log(`${currentRequests.length} Gastaufträge geladen`);
} else {
throw new Error(data.message || 'Fehler beim Laden der Gastaufträge');
}
} catch (error) {
console.error('Fehler beim Laden der Gastaufträge:', error);
showNotification('❌ Fehler beim Laden der Gastaufträge: ' + error.message, 'error');
showEmptyState();
} finally {
showLoading(false);
}
}
/**
* Statistiken aktualisieren
*/
function updateStats(stats) {
const elements = {
'pending-count': stats.pending || 0,
'approved-count': stats.approved || 0,
'rejected-count': stats.rejected || 0,
'total-count': stats.total || 0
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
animateCounter(element, value);
}
});
}
/**
* Counter mit Animation
*/
function animateCounter(element, targetValue) {
const currentValue = parseInt(element.textContent) || 0;
const difference = targetValue - currentValue;
const steps = 20;
const stepValue = difference / steps;
let step = 0;
const interval = setInterval(() => {
step++;
const value = Math.round(currentValue + (stepValue * step));
element.textContent = value;
if (step >= steps) {
clearInterval(interval);
element.textContent = targetValue;
}
}, 50);
}
/**
* Filter und Sortierung anwenden
*/
function applyFiltersAndSort() {
let requests = [...currentRequests];
// Status Filter
const statusFilter = document.getElementById('status-filter')?.value;
if (statusFilter && statusFilter !== 'all') {
requests = requests.filter(req => req.status === statusFilter);
}
// Such-Filter
const searchTerm = document.getElementById('search-requests')?.value.toLowerCase();
if (searchTerm) {
requests = requests.filter(req =>
req.name?.toLowerCase().includes(searchTerm) ||
req.email?.toLowerCase().includes(searchTerm) ||
req.file_name?.toLowerCase().includes(searchTerm) ||
req.reason?.toLowerCase().includes(searchTerm)
);
}
// Sortierung
const sortOrder = document.getElementById('sort-order')?.value;
requests.sort((a, b) => {
switch (sortOrder) {
case 'oldest':
return new Date(a.created_at) - new Date(b.created_at);
case 'priority':
return getPriorityValue(b) - getPriorityValue(a);
case 'newest':
default:
return new Date(b.created_at) - new Date(a.created_at);
}
});
filteredRequests = requests;
renderRequestsTable();
}
/**
* Prioritätswert für Sortierung berechnen
*/
function getPriorityValue(request) {
const now = new Date();
const created = new Date(request.created_at);
const hoursOld = (now - created) / (1000 * 60 * 60);
let priority = 0;
// Status-basierte Priorität
if (request.status === 'pending') priority += 10;
else if (request.status === 'approved') priority += 5;
// Alter-basierte Priorität
if (hoursOld > 24) priority += 5;
else if (hoursOld > 8) priority += 3;
else if (hoursOld > 2) priority += 1;
return priority;
}
/**
* Requests-Tabelle rendern
*/
function renderRequestsTable() {
const tableBody = document.getElementById('requests-table-body');
const emptyState = document.getElementById('empty-state');
if (!tableBody) return;
if (filteredRequests.length === 0) {
tableBody.innerHTML = '';
showEmptyState();
return;
}
hideEmptyState();
const requestsHtml = filteredRequests.map(request => createRequestRow(request)).join('');
tableBody.innerHTML = requestsHtml;
// Event Listeners für neue Rows hinzufügen
addRowEventListeners();
}
/**
* Request Row HTML erstellen
*/
function createRequestRow(request) {
const statusColor = getStatusColor(request.status);
const priorityLevel = getPriorityLevel(request);
const timeAgo = getTimeAgo(request.created_at);
return `
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200" data-request-id="${request.id}">
<td class="px-6 py-4">
<input type="checkbox" class="request-checkbox rounded border-slate-300 text-blue-600 focus:ring-blue-500"
value="${request.id}">
</td>
<td class="px-6 py-4">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
${request.name ? request.name[0].toUpperCase() : 'G'}
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-slate-900 dark:text-white">${escapeHtml(request.name || 'Unbekannt')}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">${escapeHtml(request.email || 'Keine E-Mail')}</div>
</div>
</div>
</td>
<td class="px-6 py-4">
<div class="text-sm text-slate-900 dark:text-white font-medium">${escapeHtml(request.file_name || 'Keine Datei')}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">
${request.duration_minutes ? `${request.duration_minutes} Min.` : 'Unbekannte Dauer'}
${request.copies ? `${request.copies} Kopien` : ''}
</div>
${request.reason ? `<div class="text-xs text-slate-400 dark:text-slate-500 mt-1 truncate max-w-xs">${escapeHtml(request.reason)}</div>` : ''}
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full ${statusColor}">
<span class="w-2 h-2 mr-1 rounded-full ${getStatusDot(request.status)}"></span>
${getStatusText(request.status)}
</span>
</td>
<td class="px-6 py-4">
<div class="text-sm text-slate-900 dark:text-white">${timeAgo}</div>
<div class="text-xs text-slate-500 dark:text-slate-400">${formatDateTime(request.created_at)}</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center">
${getPriorityBadge(priorityLevel)}
${request.is_urgent ? '<span class="ml-2 text-red-500 text-xs">🔥 Dringend</span>' : ''}
</div>
</td>
<td class="px-6 py-4">
<div class="flex items-center space-x-2">
<button onclick="showRequestDetail(${request.id})"
class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors"
title="Details anzeigen">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
</button>
${request.status === 'pending' ? `
<button onclick="approveRequest(${request.id})"
class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors"
title="Genehmigen">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
<button onclick="rejectRequest(${request.id})"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Ablehnen">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
` : ''}
<button onclick="deleteRequest(${request.id})"
class="text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 transition-colors"
title="Löschen">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
`;
}
/**
* Status-Helper-Funktionen
*/
function getStatusColor(status) {
const colors = {
'pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
'approved': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
'rejected': 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
'expired': 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300'
};
return colors[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';
}
function getStatusDot(status) {
const dots = {
'pending': 'bg-yellow-400 dark:bg-yellow-300',
'approved': 'bg-green-400 dark:bg-green-300',
'rejected': 'bg-red-400 dark:bg-red-300',
'expired': 'bg-gray-400 dark:bg-gray-300'
};
return dots[status] || 'bg-gray-400 dark:bg-gray-300';
}
function getStatusText(status) {
const texts = {
'pending': 'Wartend',
'approved': 'Genehmigt',
'rejected': 'Abgelehnt',
'expired': 'Abgelaufen'
};
return texts[status] || status;
}
function getPriorityLevel(request) {
const priority = getPriorityValue(request);
if (priority >= 15) return 'high';
if (priority >= 8) return 'medium';
return 'low';
}
function getPriorityBadge(level) {
const badges = {
'high': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">🔴 Hoch</span>',
'medium': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300">🟡 Mittel</span>',
'low': '<span class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300">🟢 Niedrig</span>'
};
return badges[level] || badges['low'];
}
/**
* CRUD-Operationen
*/
async function approveRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich genehmigen?')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/approve`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({}) // Leeres JSON-Objekt senden
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich genehmigt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Genehmigen');
}
} catch (error) {
console.error('Fehler beim Genehmigen:', error);
showNotification('❌ Fehler beim Genehmigen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function rejectRequest(requestId) {
const reason = prompt('Grund für die Ablehnung:');
if (!reason) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/reject`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ reason })
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich abgelehnt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Ablehnen');
}
} catch (error) {
console.error('Fehler beim Ablehnen:', error);
showNotification('❌ Fehler beim Ablehnen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function deleteRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich gelöscht', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Löschen');
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
showNotification('❌ Fehler beim Löschen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
/**
* Detail-Modal Funktionen
*/
function showRequestDetail(requestId) {
const request = currentRequests.find(req => req.id === requestId);
if (!request) return;
const modal = document.getElementById('detail-modal');
const content = document.getElementById('modal-content');
content.innerHTML = `
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
<div class="flex justify-between items-center">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Gastauftrag Details</h3>
<button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Antragsteller</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p><strong>Name:</strong> ${escapeHtml(request.name || 'Unbekannt')}</p>
<p><strong>E-Mail:</strong> ${escapeHtml(request.email || 'Keine E-Mail')}</p>
<p><strong>Erstellt am:</strong> ${formatDateTime(request.created_at)}</p>
</div>
</div>
<div class="space-y-4">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white">Auftrag Details</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p><strong>Datei:</strong> ${escapeHtml(request.file_name || 'Keine Datei')}</p>
<p><strong>Dauer:</strong> ${request.duration_minutes || 'Unbekannt'} Minuten</p>
<p><strong>Kopien:</strong> ${request.copies || 1}</p>
<p><strong>Status:</strong> ${getStatusText(request.status)}</p>
</div>
</div>
</div>
${request.reason ? `
<div class="mt-6">
<h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Begründung</h4>
<div class="bg-slate-50 dark:bg-slate-700 rounded-lg p-4">
<p class="text-gray-700 dark:text-gray-300">${escapeHtml(request.reason)}</p>
</div>
</div>
` : ''}
<div class="mt-8 flex justify-end space-x-3">
${request.status === 'pending' ? `
<button onclick="approveRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
Genehmigen
</button>
<button onclick="rejectRequest(${request.id}); closeDetailModal();"
class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
Ablehnen
</button>
` : ''}
<button onclick="closeDetailModal()"
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition-colors">
Schließen
</button>
</div>
</div>
`;
modal.classList.remove('hidden');
}
function closeDetailModal() {
const modal = document.getElementById('detail-modal');
modal.classList.add('hidden');
}
/**
* Bulk Actions
*/
function showBulkActionsModal() {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) {
showNotification('⚠️ Bitte wählen Sie mindestens einen Gastauftrag aus', 'warning');
return;
}
const modal = document.getElementById('bulk-modal');
modal.classList.remove('hidden');
}
function closeBulkModal() {
const modal = document.getElementById('bulk-modal');
modal.classList.add('hidden');
}
async function performBulkAction(action) {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) return;
const confirmMessages = {
'approve': `Möchten Sie ${selectedIds.length} Gastaufträge genehmigen?`,
'reject': `Möchten Sie ${selectedIds.length} Gastaufträge ablehnen?`,
'delete': `Möchten Sie ${selectedIds.length} Gastaufträge löschen?`
};
if (!confirm(confirmMessages[action])) return;
try {
showLoading(true);
closeBulkModal();
const promises = selectedIds.map(async (id) => {
const url = `${API_BASE_URL}/api/guest-requests/${id}/${action}`;
const method = action === 'delete' ? 'DELETE' : 'POST';
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
});
const results = await Promise.allSettled(promises);
const successCount = results.filter(r => r.status === 'fulfilled' && r.value.ok).length;
showNotification(`${successCount} von ${selectedIds.length} Aktionen erfolgreich`, 'success');
loadGuestRequests();
// Alle Checkboxen zurücksetzen
document.getElementById('select-all').checked = false;
document.querySelectorAll('.request-checkbox').forEach(cb => cb.checked = false);
} catch (error) {
console.error('Fehler bei Bulk-Aktion:', error);
showNotification('❌ Fehler bei der Bulk-Aktion: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
function getSelectedRequestIds() {
const checkboxes = document.querySelectorAll('.request-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
/**
* Event Handlers
*/
function handleSearch() {
applyFiltersAndSort();
}
function handleFilterChange() {
applyFiltersAndSort();
}
function handleSortChange() {
applyFiltersAndSort();
}
function handleSelectAll(event) {
const checkboxes = document.querySelectorAll('.request-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = event.target.checked;
});
}
function handleExport() {
const selectedIds = getSelectedRequestIds();
const exportData = selectedIds.length > 0 ?
filteredRequests.filter(req => selectedIds.includes(req.id)) :
filteredRequests;
if (exportData.length === 0) {
showNotification('⚠️ Keine Daten zum Exportieren verfügbar', 'warning');
return;
}
exportToCSV(exportData);
}
/**
* Export-Funktionen
*/
function exportToCSV(data) {
const headers = ['ID', 'Name', 'E-Mail', 'Datei', 'Status', 'Erstellt', 'Dauer (Min)', 'Kopien', 'Begründung'];
const rows = data.map(req => [
req.id,
req.name || '',
req.email || '',
req.file_name || '',
getStatusText(req.status),
formatDateTime(req.created_at),
req.duration_minutes || '',
req.copies || '',
req.reason || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(field => `"${String(field).replace(/"/g, '""')}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `gastauftraege_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
showNotification('📄 CSV-Export erfolgreich erstellt', 'success');
}
/**
* Auto-Refresh
*/
function startAutoRefresh() {
// Refresh alle 30 Sekunden
refreshInterval = setInterval(() => {
loadGuestRequests();
}, 30000);
}
function stopAutoRefresh() {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
}
/**
* Utility-Funktionen
*/
function addRowEventListeners() {
// Falls notwendig, können hier zusätzliche Event Listener hinzugefügt werden
}
function showLoading(show) {
const loadingElement = document.getElementById('table-loading');
const tableBody = document.getElementById('requests-table-body');
if (loadingElement) {
loadingElement.classList.toggle('hidden', !show);
}
if (show && tableBody) {
tableBody.innerHTML = '';
}
}
function showEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.remove('hidden');
}
}
function hideEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.add('hidden');
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-xl shadow-2xl z-50 transform transition-all duration-500 translate-x-full ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' :
type === 'warning' ? 'bg-yellow-500 text-black' :
'bg-blue-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center space-x-3">
<span class="text-lg">
${type === 'success' ? '✅' :
type === 'error' ? '❌' :
type === 'warning' ? '⚠️' : ''}
</span>
<span class="font-medium">${message}</span>
</div>
`;
document.body.appendChild(notification);
// Animation einblenden
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Nach 5 Sekunden entfernen
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 5000);
}, 5000);
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text ? String(text).replace(/[&<>"']/g, m => map[m]) : '';
}
function formatDateTime(dateString) {
if (!dateString) return 'Unbekannt';
const date = new Date(dateString);
return date.toLocaleString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
function getTimeAgo(dateString) {
if (!dateString) return 'Unbekannt';
const now = new Date();
const date = new Date(dateString);
const diffMs = now - date;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffHours / 24);
if (diffDays > 0) {
return `vor ${diffDays} Tag${diffDays === 1 ? '' : 'en'}`;
} else if (diffHours > 0) {
return `vor ${diffHours} Stunde${diffHours === 1 ? '' : 'n'}`;
} else {
const diffMinutes = Math.floor(diffMs / (1000 * 60));
return `vor ${Math.max(1, diffMinutes)} Minute${diffMinutes === 1 ? '' : 'n'}`;
}
}
// Globale Funktionen für onclick-Handler
window.showRequestDetail = showRequestDetail;
window.approveRequest = approveRequest;
window.rejectRequest = rejectRequest;
window.deleteRequest = deleteRequest;
window.closeDetailModal = closeDetailModal;
window.closeBulkModal = closeBulkModal;
window.performBulkAction = performBulkAction;
console.log('📋 Admin Guest Requests JavaScript vollständig geladen');

View File

@ -1,585 +0,0 @@
/**
* Mercedes-Benz MYP Admin Live Dashboard
* Echtzeit-Updates für das Admin Panel mit echten Daten
*/
class AdminLiveDashboard {
constructor() {
this.isLive = false;
this.updateInterval = null;
this.retryCount = 0;
this.maxRetries = 3;
// Dynamische API-Base-URL-Erkennung
this.apiBaseUrl = this.detectApiBaseUrl();
console.log('🔗 API Base URL erkannt:', this.apiBaseUrl);
this.init();
}
detectApiBaseUrl() {
const currentHost = window.location.hostname;
const currentProtocol = window.location.protocol;
const currentPort = window.location.port;
console.log('🔍 Live Dashboard API URL Detection:', { currentHost, currentProtocol, currentPort });
// Wenn wir bereits auf dem richtigen Port sind, verwende relative URLs
if (currentPort === '443' || !currentPort) {
console.log('✅ Verwende relative URLs (HTTPS Port 443)');
return '';
}
// Für alle anderen Fälle, verwende HTTPS auf Port 443
const fallbackUrl = `https://${currentHost}`;
console.log('🔄 Fallback zu HTTPS:443:', fallbackUrl);
return fallbackUrl;
}
init() {
console.log('🚀 Mercedes-Benz MYP Admin Live Dashboard gestartet');
// Live-Status anzeigen
this.updateLiveTime();
this.startLiveUpdates();
// Event Listeners
this.bindEvents();
// Initial Load
this.loadLiveStats();
// Error Monitoring System
this.initErrorMonitoring();
}
bindEvents() {
// Quick Action Buttons
const systemStatusBtn = document.getElementById('system-status-btn');
const analyticsBtn = document.getElementById('analytics-btn');
const maintenanceBtn = document.getElementById('maintenance-btn');
if (systemStatusBtn) {
systemStatusBtn.addEventListener('click', () => this.showSystemStatus());
}
if (analyticsBtn) {
analyticsBtn.addEventListener('click', () => this.showAnalytics());
}
if (maintenanceBtn) {
maintenanceBtn.addEventListener('click', () => this.showMaintenance());
}
// Page Visibility API für optimierte Updates
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.pauseLiveUpdates();
} else {
this.resumeLiveUpdates();
}
});
}
startLiveUpdates() {
this.isLive = true;
this.updateLiveIndicator(true);
// Live Stats alle 30 Sekunden aktualisieren
this.updateInterval = setInterval(() => {
this.loadLiveStats();
}, 30000);
// Zeit jede Sekunde aktualisieren
setInterval(() => {
this.updateLiveTime();
}, 1000);
}
pauseLiveUpdates() {
this.isLive = false;
this.updateLiveIndicator(false);
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
}
resumeLiveUpdates() {
if (!this.isLive) {
this.startLiveUpdates();
this.loadLiveStats(); // Sofortiges Update beim Fortsetzen
}
}
updateLiveIndicator(isLive) {
const indicator = document.getElementById('live-indicator');
if (indicator) {
if (isLive) {
indicator.className = 'w-2 h-2 bg-green-400 rounded-full animate-pulse';
} else {
indicator.className = 'w-2 h-2 bg-gray-400 rounded-full';
}
}
}
updateLiveTime() {
const timeElement = document.getElementById('live-time');
if (timeElement) {
const now = new Date();
timeElement.textContent = now.toLocaleTimeString('de-DE');
}
}
async loadLiveStats() {
try {
const url = `${this.apiBaseUrl}/api/admin/stats/live`;
console.log('🔄 Lade Live-Statistiken von:', url);
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCSRFToken()
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
this.updateStatsDisplay(data);
this.retryCount = 0; // Reset retry count on success
// Success notification (optional)
this.showQuietNotification('Live-Daten aktualisiert', 'success');
} else {
throw new Error(data.error || 'Unbekannter Fehler beim Laden der Live-Statistiken');
}
} catch (error) {
console.error('Fehler beim Laden der Live-Statistiken:', error);
this.retryCount++;
if (this.retryCount <= this.maxRetries) {
console.log(`Versuche erneut... (${this.retryCount}/${this.maxRetries})`);
setTimeout(() => this.loadLiveStats(), 5000); // Retry nach 5 Sekunden
} else {
this.handleConnectionError();
}
}
}
updateStatsDisplay(data) {
// Benutzer Stats
this.updateCounter('live-users-count', data.total_users);
this.updateProgress('users-progress', Math.min((data.total_users / 20) * 100, 100)); // Max 20 users = 100%
// Drucker Stats
this.updateCounter('live-printers-count', data.total_printers);
this.updateElement('live-printers-online', `${data.online_printers} online`);
if (data.total_printers > 0) {
this.updateProgress('printers-progress', (data.online_printers / data.total_printers) * 100);
}
// Jobs Stats
this.updateCounter('live-jobs-active', data.active_jobs);
this.updateElement('live-jobs-queued', `${data.queued_jobs} in Warteschlange`);
this.updateProgress('jobs-progress', Math.min(data.active_jobs * 20, 100)); // Max 5 jobs = 100%
// Erfolgsrate Stats
this.updateCounter('live-success-rate', `${data.success_rate}%`);
this.updateProgress('success-progress', data.success_rate);
// Trend Analysis
this.updateSuccessTrend(data.success_rate);
console.log('📊 Live-Statistiken aktualisiert:', data);
}
updateCounter(elementId, newValue) {
const element = document.getElementById(elementId);
if (element) {
const currentValue = parseInt(element.textContent) || 0;
if (currentValue !== newValue) {
this.animateCounter(element, currentValue, newValue);
}
}
}
animateCounter(element, from, to) {
const duration = 1000; // 1 Sekunde
const increment = (to - from) / (duration / 16); // 60 FPS
let current = from;
const timer = setInterval(() => {
current += increment;
if ((increment > 0 && current >= to) || (increment < 0 && current <= to)) {
current = to;
clearInterval(timer);
}
element.textContent = Math.round(current);
}, 16);
}
updateElement(elementId, newValue) {
const element = document.getElementById(elementId);
if (element && element.textContent !== newValue) {
element.textContent = newValue;
}
}
updateProgress(elementId, percentage) {
const element = document.getElementById(elementId);
if (element) {
element.style.width = `${Math.max(0, Math.min(100, percentage))}%`;
}
}
updateSuccessTrend(successRate) {
const trendElement = document.getElementById('success-trend');
if (trendElement) {
let trendText = 'Stabil';
let trendClass = 'text-green-500';
let trendIcon = 'M5 10l7-7m0 0l7 7m-7-7v18'; // Up arrow
if (successRate >= 95) {
trendText = 'Excellent';
trendClass = 'text-green-600';
} else if (successRate >= 80) {
trendText = 'Gut';
trendClass = 'text-green-500';
} else if (successRate >= 60) {
trendText = 'Mittel';
trendClass = 'text-yellow-500';
trendIcon = 'M5 12h14'; // Horizontal line
} else {
trendText = 'Niedrig';
trendClass = 'text-red-500';
trendIcon = 'M19 14l-7 7m0 0l-7-7m7 7V3'; // Down arrow
}
trendElement.className = `text-sm ${trendClass}`;
trendElement.innerHTML = `
<span class="inline-flex items-center">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${trendIcon}"/>
</svg>
${trendText}
</span>
`;
}
}
showSystemStatus() {
// System Status Modal oder Navigation
console.log('🔧 System Status angezeigt');
this.showNotification('System Status wird geladen...', 'info');
// Hier könnten weitere System-Details geladen werden
const url = `${this.apiBaseUrl}/api/admin/system/status`;
fetch(url)
.then(response => response.json())
.then(data => {
// System Status anzeigen
console.log('System Status:', data);
})
.catch(error => {
console.error('Fehler beim Laden des System Status:', error);
});
}
showAnalytics() {
console.log('📈 Live Analytics angezeigt');
this.showNotification('Analytics werden geladen...', 'info');
// Analytics Tab aktivieren oder Modal öffnen
const analyticsTab = document.querySelector('a[href*="tab=system"]');
if (analyticsTab) {
analyticsTab.click();
}
}
showMaintenance() {
console.log('🛠️ Wartung angezeigt');
this.showNotification('Wartungsoptionen werden geladen...', 'info');
// Wartungs-Tab aktivieren oder Modal öffnen
const systemTab = document.querySelector('a[href*="tab=system"]');
if (systemTab) {
systemTab.click();
}
}
handleConnectionError() {
console.error('🔴 Verbindung zu Live-Updates verloren');
this.updateLiveIndicator(false);
this.showNotification('Verbindung zu Live-Updates verloren. Versuche erneut...', 'error');
// Auto-Recovery nach 30 Sekunden
setTimeout(() => {
this.retryCount = 0;
this.loadLiveStats();
}, 30000);
}
showNotification(message, type = 'info') {
// Erstelle oder aktualisiere Notification
let notification = document.getElementById('live-notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'live-notification';
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm';
document.body.appendChild(notification);
}
const colors = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
info: 'bg-blue-500 text-white',
warning: 'bg-yellow-500 text-white'
};
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm ${colors[type]} transform transition-all duration-300 translate-x-0`;
notification.textContent = message;
// Auto-Hide nach 3 Sekunden
setTimeout(() => {
if (notification) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
if (notification && notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
}, 3000);
}
showQuietNotification(message, type) {
// Nur in der Konsole loggen für nicht-störende Updates
const emoji = type === 'success' ? '✅' : type === 'error' ? '❌' : '';
console.log(`${emoji} ${message}`);
}
getCSRFToken() {
const meta = document.querySelector('meta[name="csrf-token"]');
return meta ? meta.getAttribute('content') : '';
}
// Error Monitoring System
initErrorMonitoring() {
// Check system health every 30 seconds
this.checkSystemHealth();
setInterval(() => this.checkSystemHealth(), 30000);
// Setup error alert event handlers
this.setupErrorAlertHandlers();
}
async checkSystemHealth() {
try {
const response = await fetch('/api/admin/system-health');
const data = await response.json();
if (data.success) {
this.updateHealthDisplay(data);
this.updateErrorAlerts(data);
} else {
console.error('System health check failed:', data.error);
}
} catch (error) {
console.error('Error checking system health:', error);
}
}
updateHealthDisplay(data) {
// Update database health status
const statusIndicator = document.getElementById('db-status-indicator');
const statusText = document.getElementById('db-status-text');
const lastMigration = document.getElementById('last-migration');
const schemaIntegrity = document.getElementById('schema-integrity');
const recentErrorsCount = document.getElementById('recent-errors-count');
if (statusIndicator && statusText) {
if (data.health_status === 'critical') {
statusIndicator.className = 'w-3 h-3 bg-red-500 rounded-full animate-pulse';
statusText.textContent = 'Kritisch';
statusText.className = 'text-sm font-medium text-red-600 dark:text-red-400';
} else if (data.health_status === 'warning') {
statusIndicator.className = 'w-3 h-3 bg-yellow-500 rounded-full animate-pulse';
statusText.textContent = 'Warnung';
statusText.className = 'text-sm font-medium text-yellow-600 dark:text-yellow-400';
} else {
statusIndicator.className = 'w-3 h-3 bg-green-400 rounded-full animate-pulse';
statusText.textContent = 'Gesund';
statusText.className = 'text-sm font-medium text-green-600 dark:text-green-400';
}
}
if (lastMigration) {
lastMigration.textContent = data.last_migration || 'Unbekannt';
}
if (schemaIntegrity) {
schemaIntegrity.textContent = data.schema_integrity || 'Prüfung';
if (data.schema_integrity === 'FEHLER') {
schemaIntegrity.className = 'text-lg font-semibold text-red-600 dark:text-red-400';
} else {
schemaIntegrity.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
}
}
if (recentErrorsCount) {
const errorCount = data.recent_errors_count || 0;
recentErrorsCount.textContent = errorCount;
if (errorCount > 0) {
recentErrorsCount.className = 'text-lg font-semibold text-red-600 dark:text-red-400';
} else {
recentErrorsCount.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
}
}
}
updateErrorAlerts(data) {
const alertContainer = document.getElementById('critical-errors-alert');
const errorList = document.getElementById('error-list');
if (!alertContainer || !errorList) return;
const allErrors = [...(data.critical_errors || []), ...(data.warnings || [])];
if (allErrors.length > 0) {
// Show alert container
alertContainer.classList.remove('hidden');
// Clear previous errors
errorList.innerHTML = '';
// Add each error
allErrors.forEach(error => {
const errorElement = document.createElement('div');
errorElement.className = `p-3 rounded-lg border-l-4 ${
error.severity === 'critical' ? 'bg-red-50 dark:bg-red-900/30 border-red-500' :
error.severity === 'high' ? 'bg-orange-50 dark:bg-orange-900/30 border-orange-500' :
'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-500'
}`;
errorElement.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex-1">
<h4 class="font-medium ${
error.severity === 'critical' ? 'text-red-800 dark:text-red-200' :
error.severity === 'high' ? 'text-orange-800 dark:text-orange-200' :
'text-yellow-800 dark:text-yellow-200'
}">${error.message}</h4>
<p class="text-sm mt-1 ${
error.severity === 'critical' ? 'text-red-600 dark:text-red-300' :
error.severity === 'high' ? 'text-orange-600 dark:text-orange-300' :
'text-yellow-600 dark:text-yellow-300'
}">💡 ${error.suggested_fix}</p>
<p class="text-xs mt-1 text-gray-500 dark:text-gray-400">
📅 ${new Date(error.timestamp).toLocaleString('de-DE')}
</p>
</div>
<span class="ml-2 px-2 py-1 text-xs font-medium rounded-full ${
error.severity === 'critical' ? 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' :
error.severity === 'high' ? 'bg-orange-100 text-orange-800 dark:bg-orange-800 dark:text-orange-100' :
'bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100'
}">
${error.severity.toUpperCase()}
</span>
</div>
`;
errorList.appendChild(errorElement);
});
} else {
// Hide alert container
alertContainer.classList.add('hidden');
}
}
setupErrorAlertHandlers() {
// Fix errors button
const fixErrorsBtn = document.getElementById('fix-errors-btn');
if (fixErrorsBtn) {
fixErrorsBtn.addEventListener('click', async () => {
await this.fixErrors();
});
}
// Dismiss errors button
const dismissErrorsBtn = document.getElementById('dismiss-errors-btn');
if (dismissErrorsBtn) {
dismissErrorsBtn.addEventListener('click', () => {
const alertContainer = document.getElementById('critical-errors-alert');
if (alertContainer) {
alertContainer.classList.add('hidden');
}
});
}
// View details button
const viewDetailsBtn = document.getElementById('view-error-details-btn');
if (viewDetailsBtn) {
viewDetailsBtn.addEventListener('click', () => {
// Redirect to logs tab
window.location.href = '/admin-dashboard?tab=logs';
});
}
}
async fixErrors() {
const fixBtn = document.getElementById('fix-errors-btn');
if (!fixBtn) return;
// Show loading state
const originalText = fixBtn.innerHTML;
fixBtn.innerHTML = '🔄 Repariere...';
fixBtn.disabled = true;
try {
const response = await fetch('/api/admin/fix-errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
// Show success message
this.showNotification('✅ Automatische Reparatur erfolgreich durchgeführt!', 'success');
// Refresh health check
setTimeout(() => {
this.checkSystemHealth();
}, 2000);
} else {
// Show error message
this.showNotification(`❌ Reparatur fehlgeschlagen: ${data.error}`, 'error');
}
} catch (error) {
console.error('Error fixing errors:', error);
this.showNotification('❌ Fehler bei der automatischen Reparatur', 'error');
} finally {
// Restore button
fixBtn.innerHTML = originalText;
fixBtn.disabled = false;
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
new AdminLiveDashboard();
});
// Export for global access
window.AdminLiveDashboard = AdminLiveDashboard;

File diff suppressed because it is too large Load Diff

View File

@ -1,350 +0,0 @@
/**
* Admin System Management JavaScript
* Funktionen für System-Wartung und -Konfiguration
*/
// CSRF Token für AJAX-Anfragen
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// Hilfsfunktion für API-Aufrufe
async function makeApiCall(url, method = 'GET', data = null) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
};
if (data) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
const result = await response.json();
if (response.ok) {
showNotification(result.message || 'Aktion erfolgreich ausgeführt', 'success');
return result;
} else {
showNotification(result.error || 'Ein Fehler ist aufgetreten', 'error');
return null;
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
return null;
}
}
// Logs laden und anzeigen
async function loadLogs() {
const logsContainer = document.getElementById('logs-container');
if (!logsContainer) return;
// Lade-Animation anzeigen
logsContainer.innerHTML = `
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 dark:border-indigo-400"></div>
</div>
`;
try {
const response = await fetch('/api/logs');
if (!response.ok) throw new Error('Fehler beim Laden der Logs');
const data = await response.json();
window.logsData = data.logs || [];
window.filteredLogs = [...window.logsData];
renderLogs();
updateLogStatistics();
scrollLogsToBottom();
} catch (error) {
console.error('Fehler beim Laden der Logs:', error);
logsContainer.innerHTML = `
<div class="text-center py-8">
<div class="text-red-600 dark:text-red-400 text-xl mb-2">Fehler beim Laden der Logs</div>
<p class="text-gray-600 dark:text-gray-400">${error.message}</p>
<button onclick="loadLogs()" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors">
Erneut versuchen
</button>
</div>
`;
}
}
// Logs rendern
function renderLogs() {
const logsContainer = document.getElementById('logs-container');
if (!logsContainer || !window.filteredLogs) return;
if (window.filteredLogs.length === 0) {
logsContainer.innerHTML = `
<div class="text-center py-8">
<p class="text-gray-600 dark:text-gray-400">Keine Logs gefunden</p>
</div>
`;
return;
}
const logsHtml = window.filteredLogs.map(log => {
const levelColor = getLogLevelColor(log.level);
return `
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4 border ${levelColor.border}">
<div class="flex items-start space-x-3">
<span class="inline-block px-2 py-1 text-xs font-semibold rounded-full ${levelColor.bg} ${levelColor.text}">
${log.level}
</span>
<div class="flex-1">
<div class="flex items-center justify-between mb-1">
<span class="text-sm font-medium text-slate-600 dark:text-slate-400">${log.category}</span>
<span class="text-xs text-slate-500 dark:text-slate-500">${log.timestamp}</span>
</div>
<p class="text-sm text-slate-900 dark:text-white break-all">${log.message}</p>
</div>
</div>
</div>
`;
}).join('');
logsContainer.innerHTML = logsHtml;
}
// Log-Level-Farben bestimmen
function getLogLevelColor(level) {
const colors = {
'ERROR': {
bg: 'bg-red-100 dark:bg-red-900/30',
text: 'text-red-800 dark:text-red-200',
border: 'border-red-200 dark:border-red-700'
},
'WARNING': {
bg: 'bg-yellow-100 dark:bg-yellow-900/30',
text: 'text-yellow-800 dark:text-yellow-200',
border: 'border-yellow-200 dark:border-yellow-700'
},
'INFO': {
bg: 'bg-blue-100 dark:bg-blue-900/30',
text: 'text-blue-800 dark:text-blue-200',
border: 'border-blue-200 dark:border-blue-700'
},
'DEBUG': {
bg: 'bg-gray-100 dark:bg-gray-900/30',
text: 'text-gray-800 dark:text-gray-200',
border: 'border-gray-200 dark:border-gray-700'
}
};
return colors[level.toUpperCase()] || colors['INFO'];
}
// Log-Statistiken aktualisieren
function updateLogStatistics() {
if (!window.logsData) return;
const stats = {
total: window.logsData.length,
errors: window.logsData.filter(log => log.level.toUpperCase() === 'ERROR').length,
warnings: window.logsData.filter(log => log.level.toUpperCase() === 'WARNING').length,
info: window.logsData.filter(log => log.level.toUpperCase() === 'INFO').length
};
// Aktualisiere Statistik-Anzeigen falls vorhanden
const totalElement = document.getElementById('log-stats-total');
const errorsElement = document.getElementById('log-stats-errors');
const warningsElement = document.getElementById('log-stats-warnings');
const infoElement = document.getElementById('log-stats-info');
if (totalElement) totalElement.textContent = stats.total;
if (errorsElement) errorsElement.textContent = stats.errors;
if (warningsElement) warningsElement.textContent = stats.warnings;
if (infoElement) infoElement.textContent = stats.info;
}
// Zum Ende der Logs scrollen
function scrollLogsToBottom() {
const logsContainer = document.getElementById('logs-container');
if (logsContainer) {
logsContainer.scrollTop = logsContainer.scrollHeight;
}
}
// Notification anzeigen
function showNotification(message, type = 'info') {
// Erstelle Notification-Element falls nicht vorhanden
let notification = document.getElementById('admin-notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'admin-notification';
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full';
document.body.appendChild(notification);
}
// Setze Farbe basierend auf Typ
const colors = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
notification.textContent = message;
// Zeige Notification
notification.style.transform = 'translateX(0)';
// Verstecke nach 5 Sekunden
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
}, 5000);
}
// Cache leeren
async function clearCache() {
if (confirm('Möchten Sie wirklich den Cache leeren?')) {
showNotification('Cache wird geleert...', 'info');
const result = await makeApiCall('/api/admin/cache/clear', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// Datenbank optimieren
async function optimizeDatabase() {
if (confirm('Möchten Sie wirklich die Datenbank optimieren? Dies kann einige Minuten dauern.')) {
showNotification('Datenbank wird optimiert...', 'info');
const result = await makeApiCall('/api/admin/database/optimize', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// Backup erstellen
async function createBackup() {
if (confirm('Möchten Sie wirklich ein Backup erstellen?')) {
showNotification('Backup wird erstellt...', 'info');
const result = await makeApiCall('/api/admin/backup/create', 'POST');
}
}
// Drucker aktualisieren
async function updatePrinters() {
if (confirm('Möchten Sie alle Drucker-Verbindungen aktualisieren?')) {
showNotification('Drucker werden aktualisiert...', 'info');
const result = await makeApiCall('/api/admin/printers/update', 'POST');
if (result) {
setTimeout(() => location.reload(), 2000);
}
}
}
// System neustarten
async function restartSystem() {
if (confirm('WARNUNG: Möchten Sie wirklich das System neustarten? Alle aktiven Verbindungen werden getrennt.')) {
const result = await makeApiCall('/api/admin/system/restart', 'POST');
if (result) {
showNotification('System wird neugestartet...', 'warning');
setTimeout(() => {
window.location.href = '/';
}, 3000);
}
}
}
// Einstellungen bearbeiten
function editSettings() {
window.location.href = '/settings';
}
// Systemstatus automatisch aktualisieren
async function updateSystemStatus() {
if (window.location.search.includes('tab=system')) {
const result = await makeApiCall('/api/admin/system/status');
if (result) {
// Aktualisiere die Anzeige
updateStatusDisplay('cpu_usage', result.cpu_usage + '%');
updateStatusDisplay('memory_usage', result.memory_usage + '%');
updateStatusDisplay('disk_usage', result.disk_usage + '%');
updateStatusDisplay('uptime', result.uptime);
updateStatusDisplay('db_size', result.db_size);
updateStatusDisplay('scheduler_jobs', result.scheduler_jobs);
updateStatusDisplay('next_job', result.next_job);
// Scheduler-Status aktualisieren
const schedulerStatus = document.querySelector('.scheduler-status');
if (schedulerStatus) {
if (result.scheduler_running) {
schedulerStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-blue-400 animate-pulse"></span>Läuft';
schedulerStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200';
} else {
schedulerStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-red-400"></span>Gestoppt';
schedulerStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
}
}
}
}
}
// Hilfsfunktion zum Aktualisieren der Status-Anzeige
function updateStatusDisplay(key, value) {
const element = document.querySelector(`[data-status="${key}"]`);
if (element) {
element.textContent = value;
}
}
// Datenbankstatus aktualisieren
async function updateDatabaseStatus() {
if (window.location.search.includes('tab=system')) {
const result = await makeApiCall('/api/admin/database/status');
if (result) {
const dbStatus = document.querySelector('.database-status');
if (dbStatus) {
if (result.connected) {
dbStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>Verbunden';
dbStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
} else {
dbStatus.innerHTML = '<span class="w-2 h-2 mr-1 rounded-full bg-red-400"></span>Getrennt';
dbStatus.className = 'inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200';
}
}
updateStatusDisplay('db_size', result.size);
updateStatusDisplay('db_connections', result.connected ? 'Aktiv' : 'Getrennt');
}
}
}
// Auto-Update alle 30 Sekunden
setInterval(() => {
updateSystemStatus();
updateDatabaseStatus();
}, 30000);
// Initial load
document.addEventListener('DOMContentLoaded', function() {
updateSystemStatus();
updateDatabaseStatus();
});
// Export für globale Verwendung
window.adminSystem = {
clearCache,
optimizeDatabase,
createBackup,
updatePrinters,
restartSystem,
editSettings,
updateSystemStatus,
updateDatabaseStatus,
loadLogs,
renderLogs,
updateLogStatistics,
scrollLogsToBottom
};

File diff suppressed because it is too large Load Diff

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