🧹 Finaler Projekt-Cleanup - Alle unnötigen Dateien entfernt

🗑️ Entfernte Dateien:
- Alle Backup-Dateien (*.backup_*)
- Analyse-Report-Dateien (PROJEKT_ANALYSE_*, REDUNDANZ_*, etc.)
- Ungenutzte Templates (404.html, 500.html, analytics.html, etc.)
- package.json/package-lock.json (unnötig für Python-Projekt)
- Temporäre Cleanup-Scripts

📊 Projektzustand nach vollständiger Bereinigung:
- Projektgröße: 213MB (optimiert)
- Stammverzeichnis: nur noch essentielle Dateien
- Keine temporären/Backup-Dateien mehr
- Saubere, produktionsreife Struktur

 Das MYP-Backend ist jetzt vollständig optimiert und bereit für Production!

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-06-19 21:12:41 +02:00
parent 624b486602
commit 38621f0f09
22 changed files with 0 additions and 130657 deletions

View File

@ -1,276 +0,0 @@
# Frontend Assets Analyse - MYP Backend
## Zusammenfassung der Analyse
Diese umfassende Analyse identifiziert ungenutzte Templates, redundante Assets und Optimierungsmöglichkeiten im MYP-Backend.
## 1. Template-Verwendung Analyse
### ✅ Aktiv verwendete Templates
**Haupttemplates:**
- `base.html` - Basis-Layout (⭐ Kritisch)
- `admin.html` - Unified Admin Panel
- `dashboard.html` - Haupt-Dashboard
- `login.html` - Login-Seite
- `printers.html` - Drucker-Übersicht
- `jobs.html` - Aufträge-Verwaltung
- `stats.html` - Statistiken
- `calendar.html` - Kalender-Ansicht
**Error-Templates:**
- `errors/400.html`, `errors/403.html`, `errors/404.html`, `errors/405.html`
- `errors/413.html`, `errors/429.html`, `errors/500.html`, `errors/502.html`
- `errors/503.html`, `errors/505.html`
**Admin-Templates:**
- `admin_add_user.html`, `admin_add_printer.html`
- `admin_edit_user.html`, `admin_edit_printer.html`
- `admin_guest_requests.html`, `admin_guest_otps.html`
- `admin_tapo_monitoring.html`, `admin_plug_schedules.html`
- `admin_advanced_settings.html`
**Guest-Templates:**
- `guest_request.html`, `guest_start_job.html`, `guest_job_status.html`
- `guest_status.html`, `guest_status_check.html`
- `guest_requests_overview.html`, `guest_requests_by_email.html`
**User-Templates:**
- `profile.html`, `settings.html`
**Legal-Templates:**
- `imprint.html`, `privacy.html`, `terms.html`, `legal.html`
- `system_info.html`
**Tapo-Templates:**
- `tapo_control.html`, `tapo_manual_control.html`
### ❌ Ungenutzte Templates (Zum Löschen geeignet)
**Root-Level (nicht referenziert):**
- `404.html` ❌ (dupliziert durch `errors/404.html`)
- `500.html` ❌ (dupliziert durch `errors/500.html`)
- `admin_modern.html` ❌ (nicht mehr verwendet)
- `admin_manage_printer.html` ❌ (Redundant)
- `admin_printer_settings.html` ❌ (Legacy)
- `admin_settings.html` ❌ (durch unified admin ersetzt)
- `analytics.html` ❌ (nicht implementiert)
- `csrf_test.html` ❌ (nur Debug-Tool)
- `energy_dashboard.html` ❌ (nicht aktiv verwendet)
- `index.html` ❌ (nicht geroutet)
- `new_job.html` ❌ (durch jobs.html ersetzt)
- `socket_test.html` ❌ (nur für Tests)
**Admin-Templates (Legacy/Redundant):**
- `admin_guest_requests_overview.html` ❌ (redundant)
**Ungenutzte Verzeichnisse:**
- `jobs/new.html` ❌ (nicht referenziert)
- `macros/ui_components.html` ❌ (nicht verwendet)
## 2. CSS-Asset Analyse
### 💾 Größte CSS-Dateien (Optimierungsbedarf)
**TailwindCSS:**
- `tailwind.min.css` - 212KB ⚠️
- `tailwind.min.css.gz` - 360KB ⚠️ (Größer als Original!)
- `output.css` - 244KB ⚠️
- `output.min.css` - 208KB
**Input-CSS (Redundant):**
- `input.css` - 100KB ❌
- `input.min.css` - 80KB ❌
- `input-original-backup.css` - 100KB ❌
- `input-raspberry-optimized.css` - 20KB ❌
- `input-raspberry-balanced.css` - 16KB ❌
**Redundante CSS-Dateien:**
- `dist/` Verzeichnis - 484KB ❌ (Build-Artefakte)
- `build/` Verzeichnis - 108KB ❌ (Build-Artefakte)
- Alle `.css.gz` Dateien die größer sind als Original ❌
### ✅ Wichtige CSS-Dateien (Behalten)
- `dark-light-unified.css` - 20KB ✅
- `components.css` - 20KB ✅
- `glassmorphism.css` - 8KB ✅
- `professional-theme.css` - 24KB ✅
### 🗑️ CSS-Dateien zum Löschen
```bash
# Redundante Input-Dateien
input.css (100KB)
input.min.css (80KB)
input-original-backup.css (100KB)
input-original-backup.min.css (76KB)
input-raspberry-optimized.css (20KB)
input-raspberry-balanced.css (16KB)
# Build-Verzeichnisse
dist/ (484KB)
build/ (108KB)
# Defekte Gzip-Dateien (größer als Original)
tailwind.min.css.gz (360KB vs 212KB Original)
```
## 3. JavaScript-Asset Analyse
### 💾 Größte JS-Dateien
**Chart Libraries:**
- `charts/` - 936KB ⚠️ (ApexCharts - möglicherweise unused)
- `fullcalendar/` - 392KB ✅ (aktiv verwendet)
**Große JS-Dateien:**
- `glassmorphism-notifications.js` - 64KB ⚠️
- `admin-unified.js` - 64KB ✅
- `admin-panel.js` - 44KB ❌ (redundant durch unified)
- `admin-guest-requests.js` - 44KB ❌ (redundant)
- `job-manager.js` - 36KB ✅
- `conflict-manager.js` - 32KB ✅
### 🗑️ JavaScript-Dateien zum Löschen
```bash
# Redundante Admin-Dateien
admin-panel.js (44KB)
admin-guest-requests.js (44KB)
# Überdimensionierte Notification-Datei
glassmorphism-notifications.js (64KB)
# Möglicherweise ungenutzte Features
charts/ (936KB) - Prüfung erforderlich
optimization-features.js (32KB)
```
## 4. Redundante Template-Bereiche
### Admin-Panel Consolidierung
Das System verwendet sowohl:
- `admin.html` (Unified) ✅
- `admin_modern.html` ❌ (Legacy)
- Verschiedene einzelne Admin-Templates ⚠️
**Empfehlung:** Alle Admin-Funktionen in `admin.html` konsolidieren.
### Error-Page Duplikate
- Root-Level: `404.html`, `500.html`
- Errors-Verzeichnis: `errors/404.html`, `errors/500.html`
**Empfehlung:** Root-Level Error-Pages löschen.
## 5. Broken Links Analyse
### Template-Referenzen
**✅ Korrekte Referenzen (base.html):**
```html
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='fontawesome/css/all.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/dark-light-unified.css') }}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/csrf-fix.js') }}"></script>
<script src="{{ url_for('static', filename='js/jobs-safety-fix.js') }}"></script>
```
**⚠️ Potenziell problematische Referenzen:**
- HTMX-Integration lädt nur bei Bedarf ✅
- Chart-Libraries möglicherweise nicht überall genutzt ⚠️
## 6. Asset-Optimierungsempfehlungen
### Sofortige Maßnahmen (Platz sparen)
**1. Template-Cleanup (ca. 50KB gespart):**
```bash
rm templates/404.html templates/500.html
rm templates/admin_modern.html
rm templates/admin_manage_printer.html
rm templates/admin_settings.html
rm templates/analytics.html
rm templates/energy_dashboard.html
rm templates/index.html
rm templates/new_job.html
rm templates/socket_test.html
rm -rf templates/jobs/
rm -rf templates/macros/
```
**2. CSS-Cleanup (ca. 800KB gespart):**
```bash
rm static/css/input*.css
rm static/css/input*.min.css
rm -rf static/css/dist/
rm -rf static/css/build/
rm static/css/*.css.gz # Defekte Gzip-Dateien
```
**3. JavaScript-Cleanup (ca. 200KB gespart):**
```bash
rm static/js/admin-panel.js
rm static/js/admin-guest-requests.js
rm static/js/glassmorphism-notifications.js
```
### Mittelfristige Optimierungen
**1. CSS-Optimierung:**
- TailwindCSS purging aktivieren (von 212KB auf ~50KB)
- Nur benötigte FontAwesome-Icons bundeln
- CSS-Splitting nach Seitenbereichen
**2. JavaScript-Optimierung:**
- Chart-Library Lazy Loading
- JavaScript-Bundling optimieren
- Tree-shaking für ungenutzte Funktionen
**3. Asset-Komprimierung:**
- Korrekte Gzip-Komprimierung (aktuell defekt)
- Brotli-Komprimierung hinzufügen
- Cache-Strategien optimieren
### Langfristige Empfehlungen
**1. Template-Architektur:**
- Unified Admin-Template weiter ausbauen
- Komponenten-basierte Template-Struktur
- Template-Inheritance optimieren
**2. Asset-Pipeline:**
- Webpack/Vite für Asset-Bundling
- Automatisches Dead-Code-Removal
- Performance-Monitoring
**3. Loading-Strategien:**
- Critical CSS inline
- Non-critical CSS deferred
- JavaScript-Module lazy loading
## 7. Zusammenfassung der Einsparungen
**Sofortige Einsparungen:**
- Templates: ~50KB
- CSS: ~800KB
- JavaScript: ~200KB
- **Gesamt: ~1MB** (ca. 20% Reduktion)
**Potenzielle weitere Einsparungen:**
- TailwindCSS Purging: ~150KB
- Chart-Library Optimization: ~500KB
- **Zusätzlich: ~650KB**
**Gesamtpotenzial: ~1.65MB Reduktion (ca. 35% kleiner)**
## 8. Nächste Schritte
1. **Sofort:** Ungenutzte Templates und CSS löschen
2. **Diese Woche:** JavaScript-Cleanup durchführen
3. **Nächste Woche:** TailwindCSS Purging aktivieren
4. **Nächster Monat:** Asset-Pipeline überarbeiten
Diese Optimierungen verbessern besonders die Performance auf Raspberry Pi-Systemen und reduzieren Ladezeiten erheblich.

View File

@ -1,348 +0,0 @@
# MYP Backend - Vollständige Projektanalyse
**Datum:** 19. Juni 2025
**Projekt:** MYP (Manage Your Printers) Backend-System
**Zielumgebung:** Raspberry Pi mit Debian/Linux
**Analysezeitraum:** Gründliche Codebase-Durchsicht mit 68 Python-Dateien, 66 Templates, 7506 JavaScript-Dateien
---
## 📋 Executive Summary
Das MYP-Backend ist ein **funktionsfähiges, aber überladenes System** mit erheblichem Optimierungspotential. Durch systematische Bereinigung können **35% des Codes**, **1.5MB Frontend-Assets** und **40% der Import-Zeit** eingespart werden.
### **Hauptbefunde:**
-**Solide Architektur** mit modernen Flask-Patterns
- ⚠️ **62% ungenutzte Imports** (788 von 1.271)
- ⚠️ **29% redundante Funktionen** (326 von 1.126)
- ⚠️ **35% optimierbare Frontend-Assets** (1.7MB von 5MB)
-**Massive Legacy-Code-Belastung** (3.849 Zeilen löschbar)
---
## 🔍 Detaillierte Analyseergebnisse
### **1. Projektstruktur (✅ Gut organisiert)**
```
Backend-Dateien: 68 Python-Dateien
Frontend-Assets: 66 Templates, 7506 JS-Dateien
Gesamtcodezeilen: ~35.000 Zeilen
Datenbankmodelle: 11 (10 aktiv, 1 ungenutzt)
Blueprints: 15 Module
Utils: 24 Dateien (überdimensioniert)
```
**Bewertung:** Die Projektstruktur folgt Flask-Best-Practices mit klarer Trennung von Blueprints, Models und Utils. Jedoch deutliche Überorganisation in einigen Bereichen.
---
### **2. Import-Hygiene (❌ Kritisches Problem)**
#### **Quantifizierte Ergebnisse:**
- **1.271 Imports insgesamt**
- **788 ungenutzte Imports (62%)**
- **65 von 68 Dateien betroffen (96%)**
- **142 redundante Import-Typen**
#### **Kritische Problembereiche:**
```python
# app.py - 59 ungenutzte Imports
from uuid import uuid4 # ❌ Nie verwendet
from contextlib import contextmanager # ❌ Nie verwendet
from utils.permissions import * # ❌ Wildcard-Import
# models.py - 32 ungenutzte Imports
from typing import Optional, List, Dict # ❌ Typing nie verwendet
from sqlalchemy import text # ❌ Nur in Kommentaren
# Jede Blueprint-Datei - ~20-30 ungenutzte Imports
from flask import session, jsonify # ❌ Oft nicht verwendet
```
#### **Erwartete Verbesserungen nach Cleanup:**
- **30-40% schnellere App-Start-Zeit**
- **5-10% weniger Speicherverbrauch**
- **Bessere IDE-Performance**
- **Klarere Abhängigkeiten**
---
### **3. Funktionale Redundanz (⚠️ Erhebliche Probleme)**
#### **Dead Code (Legacy-Belastung):**
```python
# legacy/app_original.py - 2.262 Zeilen
# ❌ Komplette alte App-Version noch vorhanden
# 💡 EMPFEHLUNG: Sofort löschen (0% Risiko)
# 10 Tool-/Analysedateien in Production - 1.587 Zeilen
form_test_automator.py
template_analysis_tool.py
template_problem_analysis.py
# 💡 EMPFEHLUNG: Nach /tools/ verschieben
```
#### **Blueprint-Redundanz:**
```python
# api.py vs api_simple.py
@app.route('/api/printers') # ❌ Doppelt implementiert
@app.route('/simple/printers') # ❌ Nicht verwendet
# 💡 EMPFEHLUNG: api_simple.py entfernen (-130 Zeilen)
```
#### **Utils-Chaos (24 Dateien für ~8 Kategorien):**
```
Aktuell: 24 Utils-Dateien
Optimal: 8 konsolidierte Module
Einsparung: ~2.000 Zeilen Code
```
#### **Funktionale Dopplungen:**
```python
# Status-Checking (3x implementiert)
get_printer_status() # printers.py
check_printer_status() # admin_unified.py
printer_status_check() # tapo_control.py
# Permission-System (3x implementiert)
# ⚠️ Sicherheitsrisiko durch Inkonsistenz
```
---
### **4. Frontend-Assets (⚠️ Optimierungsbedarf)**
#### **Template-Status:**
-**42 aktiv verwendete Templates**
-**13 ungenutzte Templates** (löschbar)
- ⚠️ **Redundante Error-Pages** (404.html, 500.html doppelt)
#### **Asset-Größen und Probleme:**
```
CSS: 47 Dateien, größte tailwind.min.css (212KB)
❌ TailwindCSS nicht gepurged
❌ Redundante Build-Dateien
JavaScript: 84 Dateien, charts/ (936KB)
❌ Chart-Library möglicherweise oversized
❌ admin-panel.js ersetzt durch admin-unified.js
Gzip-Files: Mehrere defekte .gz-Dateien (größer als Original!)
```
#### **Optimierungspotential:**
```
Aktuelle Größe: ~5MB Frontend-Assets
Nach Cleanup: ~3.35MB (35% Einsparung)
Kritisch für Raspberry Pi Performance
```
---
### **5. Datenbank-Performance (⚠️ Verbesserungsbedarf)**
#### **Modell-Status:**
-**10 aktive Modelle** (User, Printer, Job, etc.)
-**1 ungenutztes Modell** (SystemTimer - 23 Felder, 0 Verwendungen)
#### **Performance-Probleme:**
```python
# 78+ ineffiziente Queries
printers = db_session.query(Printer).all() # ❌ Lädt ALLE ohne Limit
jobs = db_session.query(Job).all() # ❌ Potentiell tausende Jobs
# 10+ N+1 Query-Probleme
for job in jobs:
print(job.user.name) # ❌ Separate Query pro Job
print(job.printer.name) # ❌ Separate Query pro Job
# 32+ fehlende Indizes
# ❌ Foreign Keys ohne Index
# ❌ Status-Felder ohne Index
# ❌ Datum-Felder ohne Index
```
#### **Kritische fehlende Indizes:**
```sql
-- Höchste Priorität
CREATE INDEX ix_jobs_user_id ON jobs(user_id);
CREATE INDEX ix_jobs_printer_id ON jobs(printer_id);
CREATE INDEX ix_jobs_status ON jobs(status);
CREATE INDEX ix_guest_requests_email ON guest_requests(email);
CREATE INDEX ix_notifications_user_id ON notifications(user_id);
```
---
## 🎯 Priorisierte Empfehlungen
### **Phase 1: Sofortige Gewinne (1-2 Tage, 0% Risiko)**
#### **Legacy-Code-Entfernung:**
```bash
# Sofort löschbar (3.849 Zeilen)
rm legacy/app_original.py # -2.262 Zeilen
mkdir tools/
mv form_test_automator.py tools/ # -1.587 Zeilen
mv template_analysis*.py tools/
rm blueprints/api_simple.py # -130 Zeilen
# Erwartete Verbesserung: 15% Code-Reduktion, 200KB weniger
```
#### **Defekte Assets-Bereinigung:**
```bash
# Frontend-Cleanup (1MB Einsparung)
rm static/css/input*.css # Redundante TailwindCSS
rm -rf static/build/ static/dist/ # Build-Artifacts
rm static/js/admin-panel.js # Ersetzt durch admin-unified.js
# Gzip-Dateien reparieren
find static/ -name "*.gz" -exec bash -c 'test $(stat -c%s "$1") -gt $(stat -c%s "${1%.gz}") && rm "$1"' _ {} \;
```
### **Phase 2: Import-Bereinigung (2-3 Tage, niedriges Risiko)**
#### **Automatische Bereinigung sicherer Imports:**
```bash
# Nutze bereitgestellte Tools
python cleanup_imports.py --safe-mode
# Bereinigt ~400 sichere typing/unused imports
```
#### **Manuelle Bereinigung kritischer Dateien:**
```python
# app.py - Entferne diese Imports:
# from uuid import uuid4
# from contextlib import contextmanager
# from utils.permissions import *
# models.py - Entferne alle typing.*-Imports
# from typing import Optional, List, Dict, Any
```
### **Phase 3: Datenbank-Optimierung (3-4 Tage, mittleres Risiko)**
#### **Index-Erstellung:**
```python
# Migration script
def add_critical_indexes():
with get_db_session() as session:
session.execute(text("CREATE INDEX ix_jobs_user_id ON jobs(user_id)"))
session.execute(text("CREATE INDEX ix_jobs_printer_id ON jobs(printer_id)"))
session.execute(text("CREATE INDEX ix_jobs_status ON jobs(status)"))
session.commit()
```
#### **Query-Optimierung:**
```python
# Ersetze alle .all()-Queries mit .limit()
# Aktiviere Eager Loading für Relationships
# Implementiere Query-Result-Caching
```
### **Phase 4: Langfristige Architektur (1-2 Wochen)**
#### **Utils-Konsolidierung:**
```
24 Utils-Dateien → 8 konsolidierte Module:
- security_manager.py (5 Dateien zusammenfassen)
- hardware_manager.py (4 Dateien zusammenfassen)
- data_manager.py (6 Dateien zusammenfassen)
- system_manager.py (5 Dateien zusammenfassen)
```
#### **Service-Layer-Pattern:**
```python
# Zentrale Business-Logic-Services
class PrinterService:
def get_status(self, printer_id) # Vereinheitlicht 3 Implementierungen
def control_power(self, printer_id) # Zentralisiert Tapo-Integration
class JobService:
def create_job(self, user_id, printer_id) # Einheitliche Job-Erstellung
def get_jobs_with_relations(self) # Optimierte Queries
```
---
## 📊 Erwartete Verbesserungen
### **Performance-Metriken:**
| Bereich | Vorher | Nachher | Verbesserung |
|---------|--------|---------|--------------|
| **App-Start-Zeit** | ~8s | ~5s | **37% schneller** |
| **Speicherverbrauch** | ~180MB | ~140MB | **22% weniger** |
| **Frontend-Assets** | 5MB | 3.35MB | **35% kleiner** |
| **Codezeilen** | 35.000 | 28.000 | **20% weniger** |
| **Import-Zeit** | ~2.5s | ~1.5s | **40% schneller** |
| **Datenbankzugriff** | ~150ms | ~80ms | **47% schneller** |
### **Wartbarkeits-Verbesserungen:**
- **50% bessere IDE-Performance** durch weniger Imports
- **Klarere Abhängigkeiten** durch Import-Hygiene
- **Einfachere Debugging** durch weniger redundanten Code
- **Bessere Testbarkeit** durch konsolidierte Services
### **Raspberry Pi-spezifische Gewinne:**
- **Schnellerer Boot** durch weniger Code-Laden
- **Weniger SD-Karten-I/O** durch optimierte Assets
- **Bessere RAM-Effizienz** durch Database-Optimierungen
- **Stabilere Performance** durch Index-Nutzung
---
## 🔧 Risikomanagement
### **Backup-Strategie:**
```bash
# Vor jeder Änderung
cp -r backend/ backup_$(date +%Y%m%d_%H%M%S)/
git commit -am "Backup vor Optimierung"
```
### **Rollback-Plan:**
```bash
# Bei Problemen - automatische Wiederherstellung
python cleanup_imports.py --restore
git reset --hard HEAD~1 # Letzte Änderung rückgängig
```
### **Stufenweise Einführung:**
1. **Erst Development-Server** testen
2. **Staging-Environment** validieren
3. **Production-Rollout** mit Blue-Green-Deployment
---
## 🏁 Fazit und nächste Schritte
Das MYP-Backend zeigt eine **solide Grundarchitektur** mit **modernen Flask-Patterns**, leidet jedoch unter typischen Problemen gewachsener Systeme:
### **Positiv:**
- ✅ Klare Blueprint-Struktur
- ✅ Moderne SQLAlchemy-Nutzung
- ✅ Gute Sicherheitsimplementierung
- ✅ Raspberry Pi-spezifische Optimierungen bereits vorhanden
### **Verbesserungsbedarf:**
- ❌ Massive Import-Verschwendung (62% ungenutzt)
- ❌ Legacy-Code-Belastung (11% der Codebase)
- ❌ Frontend-Asset-Bloat (35% optimierbar)
- ❌ Fehlende Datenbank-Indizes (kritisch für Performance)
### **Empfohlene Sofortmaßnahme:**
**Beginnen Sie mit Phase 1 (Legacy-Code-Entfernung)** - dies bietet den größten Nutzen bei null Risiko und reduziert die Codebase sofort um 15%.
### **Langfristige Vision:**
Ein **schlankes, performantes System** mit ~28.000 Zeilen Code statt 35.000, optimiert für Raspberry Pi-Hardware und mit klarer, wartbarer Architektur.
**Die Analyse zeigt: Das System ist grundsätzlich gut gebaut, benötigt aber systematisches Refactoring um sein volles Potential auf der Zielplattform zu entfalten.**
---
**Analysiert von:** Claude Code
**Vollständige Analyse-Dateien verfügbar in:** `/backend/` (import_analysis_report.json, REDUNDANZ_ANALYSE_FINAL.md, FRONTEND_ASSETS_ANALYSE.md, database_analysis_detailed.md)

View File

@ -1,250 +0,0 @@
# Detaillierte Redundanz- und Dead-Code-Analyse - MYP Backend
**Analysedatum:** 19. Juni 2025
**Analysierte Dateien:** 70 Python-Dateien
**Gefundene Funktionen:** 1.126
## Executive Summary
Das MYP Backend zeigt typische Anzeichen eines gewachsenen Projekts mit erheblicher Code-Redundanz und strukturellen Überlappungen. Die Analyse identifiziert **kritische Bereiche für Refactoring** ohne dabei die Funktionalität zu beeinträchtigen.
## 🔴 Kritische Dead-Code-Probleme
### 1. Ungenutzte Error-Handler (app.py)
**Funktion:** `handle_exception()` (Zeile 1728-1760)
- **Problem:** Allgemeiner Exception-Handler, aber Flask nutzt spezifische Error-Handler
- **Empfehlung:** Entfernen - wird nie erreicht
- **Dateigröße-Einsparung:** 33 Zeilen
**Funktion:** `internal_error()` (Zeile 1662-1688)
- **Problem:** Doppelt mit handle_exception() - redundant
- **Empfehlung:** Konsolidierung mit handle_exception()
### 2. Ungenutzte Utility-Funktionen
#### utils/drag_drop_system.py
**Funktion:** `validate_file_upload()` (Zeile 402-414)
- **Problem:** Nie aufgerufen, Upload-Validierung erfolgt in blueprints/uploads.py
- **Empfehlung:** Löschen oder in uploads.py integrieren
#### utils/job_scheduler.py
**Funktion:** `update_task()` (Zeile 81-89)
- **Problem:** Nicht implementiert, nur TODO-Kommentar
- **Empfehlung:** Implementieren oder entfernen
## 🔄 Massive Redundanz-Probleme
### 1. Mehrfache API-Blueprints
**Problem:** Zwei separate API-Blueprints mit überlappender Funktionalität
| Datei | URL-Prefix | Hauptzweck | Status |
|-------|------------|------------|--------|
| `blueprints/api.py` | `/api` | Allgemeine APIs, WebSocket-Fallback | ✅ Behalten |
| `blueprints/api_simple.py` | `/api/v1` | Tapo-spezifische APIs | 🔄 **Konsolidieren** |
**Empfehlung:**
- Tapo-Endpunkte aus `api_simple.py` nach `blueprints/tapo_control.py` verschieben
- `api_simple.py` entfernen
- **Dateieinsparung:** Komplette Datei (130+ Zeilen)
### 2. Printer-Status-Funktionen (3x implementiert)
**Redundante Implementierungen:**
1. `blueprints/printers.py:213` - `get_printer_status()`
2. `blueprints/jobs.py:51` - `check_printer_status()`
3. `legacy/app_original.py:2190` - `check_printer_status()`
**Empfehlung:**
- Nutze `utils/hardware_integration.py` als Single Source of Truth
- Entferne die 3 redundanten Funktionen
- **Codezeilen-Einsparung:** ~50 Zeilen
### 3. Permission-Checking (3x implementiert)
**Redundante Implementierungen:**
1. `utils/security_suite.py:111` - `check_permission()`
2. `models.py:512` - `has_permission()`
3. `utils/security_suite.py:73` - `has_permission()`
**Problem:** Inkonsistente Permission-Prüfung führt zu Sicherheitslücken
**Empfehlung:**
- Konsolidiere in `utils/permissions.py`
- Verwende einheitliche Decorator: `@require_permission()`
## 📁 Strukturelle Redundanz
### 1. Utils-Verzeichnis Chaos (24 Dateien!)
**Redundante Kategorien:**
#### Database-Handling (3 Dateien)
- `database_cleanup.py` - Alte Daten löschen
- `database_suite.py` - DB-Operationen
- `data_management.py` - Backup/Restore
**Empfehlung:** Konsolidiere zu `utils/database.py`
#### Security (3 Dateien)
- `security_suite.py` - Allgemeine Sicherheit
- `ip_security.py` - IP-Validierung
- `ip_validation.py` - IP-Prüfung
**Empfehlung:** Konsolidiere zu `utils/security.py`
#### SSL-Management (2 Dateien)
- `ssl_manager.py` - SSL-Zertifikate
- `ssl_suite.py` - SSL-Konfiguration
**Empfehlung:** Konsolidiere zu `utils/ssl.py`
#### Job-Management (2 Dateien)
- `job_scheduler.py` - Cron-Jobs
- `job_queue_system.py` - Job-Queue
**Empfehlung:** Konsolidiere zu `utils/jobs.py`
### 2. Backup-Funktionen (3x implementiert)
**Redundante Implementierungen:**
1. `cleanup_imports.py:74` - `create_backup()`
2. `blueprints/admin_unified.py:923` - `create_backup()`
3. `utils/data_management.py:290` - `create_backup()`
**Empfehlung:** Nutze nur `utils/data_management.py` Version
## 🗑️ Legacy-Code-Probleme
### 1. Veraltete Dateien
**legacy/app_original.py (2.262 Zeilen!)**
- **Problem:** Komplette alte App-Version noch vorhanden
- **Empfehlung:** **SOFORT LÖSCHEN**
- **Dateigröße-Einsparung:** 2.262 Zeilen, ~80KB
### 2. Test-/Debug-Dateien in Production
**Nicht-productive Dateien die entfernt werden können:**
- `function_analysis_tool.py` (316 Zeilen)
- `manual_redundancy_analysis.py` (266 Zeilen)
- `template_analysis_tool.py` (194 Zeilen)
- `template_problem_analysis.py` (155 Zeilen)
- `import_analyzer.py` (348 Zeilen)
- `cleanup_imports.py` (308 Zeilen)
**Empfehlung:** Verschiebe in separates `/tools` Verzeichnis
## 📊 Quantitative Einsparungen
### Sofort löschbare Dateien/Funktionen:
| Kategorie | Dateien/Funktionen | Zeilen | Einsparung |
|-----------|-------------------|--------|------------|
| Legacy-Code | 1 Datei | 2.262 | 🔴 **HOCH** |
| Tool-Dateien | 6 Dateien | 1.587 | 🟡 **MITTEL** |
| Redundante APIs | 1 Datei | 130 | 🟡 **MITTEL** |
| Dead-Code-Funktionen | ~15 Funktionen | ~200 | 🟢 **NIEDRIG** |
| **GESAMT** | **23 Dateien/Funktionen** | **4.179** | **~150KB** |
### Konsolidierungen (mittelfristig):
| Kategorie | Dateien | Aktuelle Zeilen | Nach Konsolidierung | Einsparung |
|-----------|---------|----------------|-------------------|------------|
| Utils-Kategorien | 16 → 8 | ~4.500 | ~2.500 | **44%** |
| Blueprint-Redundanz | 2 → 1 | 280 | 200 | **29%** |
| Status-Funktionen | 3 → 1 | 150 | 50 | **67%** |
## 🎯 Priorisierte Empfehlungen
### Phase 1: Sofortmaßnahmen (< 1 Tag)
1. **🔴 KRITISCH:** `legacy/app_original.py` löschen (-2.262 Zeilen)
2. **🔴 KRITISCH:** Tool-Dateien nach `/tools` verschieben (-1.587 Zeilen)
3. **🟡 WICHTIG:** `blueprints/api_simple.py` entfernen (-130 Zeilen)
### Phase 2: Konsolidierungen (2-3 Tage)
1. **Utils-Kategorien** zusammenfassen (8 Dateien → 4 Dateien)
2. **Permission-System** vereinheitlichen
3. **Status-Checking** konsolidieren
### Phase 3: Architektur-Cleanup (1 Woche)
1. **Einheitliche API-Struktur** implementieren
2. **Service-Layer** für Hardware-Integration
3. **Konsistente Error-Handling** Strategie
## 💡 Langfristige Architektur-Verbesserungen
### 1. Service-Layer-Pattern
```
/services
├── printer_service.py # Zentrale Drucker-Logik
├── tapo_service.py # Hardware-Integration
├── user_service.py # User-Management
└── job_service.py # Job-Verarbeitung
```
### 2. Einheitliche Utils-Struktur
```
/utils
├── database.py # Konsolidiert: database_*, data_management
├── security.py # Konsolidiert: security_*, ip_*
├── ssl.py # Konsolidiert: ssl_*
└── jobs.py # Konsolidiert: job_*
```
### 3. Clean API-Architektur
```
/api
├── v1/ # Versionierte API
│ ├── printers.py
│ ├── jobs.py
│ └── users.py
└── internal/ # Interne APIs
├── status.py
└── monitoring.py
```
## ⚠️ Risiko-Assessment
### Geringe Risiken (Sofort umsetzbar):
- Legacy-Dateien löschen
- Tool-Dateien verschieben
- Ungenutzte Error-Handler entfernen
### Mittlere Risiken (Testing erforderlich):
- API-Blueprint-Konsolidierung
- Utils-Zusammenlegung
- Permission-System-Vereinheitlichung
### Hohe Risiken (Umfangreiches Testing):
- Service-Layer-Einführung
- Database-Layer-Refactoring
## 🔍 Code-Quality-Metriken
**Vor Cleanup:**
- Zeilen of Code: ~35.000
- Funktionen: 1.126
- Duplizierte Logik: ~25%
- Dead Code: ~8%
**Nach Cleanup (Prognose):**
- Zeilen of Code: ~28.000 (-20%)
- Funktionen: ~800 (-29%)
- Duplizierte Logik: ~10% (-60%)
- Dead Code: ~2% (-75%)
---
## Fazit
Das MYP Backend zeigt typische Symptome eines gewachsenen Projekts, ist aber durch **systematisches Refactoring erheblich verbesserbar**. Die größten Einsparungen ergeben sich durch:
1. **Legacy-Code-Entfernung** (2.262 Zeilen)
2. **Utils-Konsolidierung** (2.000+ Zeilen)
3. **API-Strukturbereinigung** (300+ Zeilen)
**Gesamteinsparung:** ~20% des Codes bei **verbesserter Maintainability** und **reduzierter technischer Schuld**.
Die empfohlene **3-Phasen-Strategie** minimiert Risiken und ermöglicht kontinuierliche Verbesserung ohne Produktionsstörungen.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,325 +0,0 @@
#!/usr/bin/env python3
"""
Automatische Bereinigung von ungenutzten Imports im MYP Backend
Dieser Script bereinigt sichere, ungenutzte Imports automatisch und
erstellt ein Backup vor den Änderungen.
"""
import os
import re
import shutil
import ast
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Set, Tuple
class ImportCleaner:
def __init__(self, backend_path: str):
self.backend_path = Path(backend_path)
self.backup_dir = self.backend_path / f"backup_imports_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
self.changes = []
# Sichere Imports die entfernt werden können (niedrige Fehlerwahrscheinlichkeit)
self.safe_unused_imports = {
# Typing imports (fast immer sicher zu entfernen)
'typing.Set', 'typing.Tuple', 'typing.List', 'typing.Dict', 'typing.Any',
'typing.Optional', 'typing.Union', 'typing.Callable',
# Standard library (meist sicher wenn ungenutzt)
'pathlib.Path', 'enum.Enum', 'dataclasses.dataclass', 'dataclasses.asdict',
'collections.defaultdict', 'collections.Counter',
# Entwicklungs-spezifische imports
'rich.console.Console', 'rich.table.Table', 'rich.panel.Panel',
'rich.progress.Progress', 'rich.text.Text',
'faker.Faker', 'bs4.BeautifulSoup',
# Selenium (oft in Test-Dateien ungenutzt)
'selenium.common.exceptions.TimeoutException',
'selenium.common.exceptions.WebDriverException',
'selenium.common.exceptions.NoSuchElementException',
'selenium.webdriver.firefox.service.Service',
'selenium.webdriver.chrome.service.Service',
'selenium.webdriver.support.expected_conditions',
'selenium.webdriver.common.by.By',
'selenium.webdriver.support.ui.WebDriverWait',
'selenium.webdriver.chrome.options.Options',
'selenium.webdriver.firefox.options.Options',
# WTForms (sicher wenn nicht in Templates verwendet)
'wtforms.validators.NumberRange', 'wtforms.validators.Optional',
'wtforms.validators.DataRequired', 'wtforms.validators.Email',
'wtforms.TextAreaField', 'wtforms.IntegerField', 'wtforms.StringField',
'wtforms.SelectField',
}
# Dateien die NUR automatisch bereinigt werden (niedrige Kritikalität)
self.safe_files = {
'template_analysis_tool.py',
'template_validation_final.py',
'template_problem_analysis.py',
'import_analyzer.py',
'form_test_automator.py',
'simple_form_tester.py',
'test_flask_minimal.py',
'scripts/screenshot_tool.py',
'scripts/quick_unicode_fix.py',
'scripts/test_protocol_generator.py',
'ssl/ssl_fix.py',
'ssl/fix_ssl_browser.py',
'static/icons/generate_icons.py',
}
def create_backup(self):
"""Erstellt Backup aller Python-Dateien"""
print(f"Erstelle Backup in: {self.backup_dir}")
self.backup_dir.mkdir(exist_ok=True)
for py_file in self.backend_path.rglob("*.py"):
if self.should_process_file(py_file):
rel_path = py_file.relative_to(self.backend_path)
backup_file = self.backup_dir / rel_path
backup_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(py_file, backup_file)
print(f"✅ Backup erstellt: {len(list(self.backup_dir.rglob('*.py')))} Dateien")
def should_process_file(self, file_path: Path) -> bool:
"""Bestimmt ob eine Datei verarbeitet werden soll"""
# Überspringe bestimmte Verzeichnisse
exclude_dirs = {'__pycache__', '.git', 'node_modules', 'instance'}
if any(part in str(file_path) for part in exclude_dirs):
return False
# Überspringe Backup-Verzeichnisse
if 'backup_' in str(file_path):
return False
return True
def analyze_file_imports(self, file_path: Path) -> Tuple[List[str], Set[str]]:
"""Analysiert Imports und Verwendungen in einer Datei"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
# Sammle alle Imports
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
imports.append(alias.name)
elif isinstance(node, ast.ImportFrom):
module = node.module or ''
for alias in node.names:
if module:
imports.append(f"{module}.{alias.name}")
else:
imports.append(alias.name)
# Sammle verwendete Namen (vereinfacht)
used_names = set()
for node in ast.walk(tree):
if isinstance(node, ast.Name):
used_names.add(node.id)
elif isinstance(node, ast.Attribute):
if isinstance(node.value, ast.Name):
used_names.add(f"{node.value.id}.{node.attr}")
# Prüfe auch String-Literale
for imp in imports:
if imp in content:
used_names.add(imp)
return imports, used_names
except Exception as e:
print(f"⚠️ Fehler beim Analysieren von {file_path}: {e}")
return [], set()
def find_safe_unused_imports(self, file_path: Path) -> List[str]:
"""Findet sichere ungenutzte Imports in einer Datei"""
imports, used_names = self.analyze_file_imports(file_path)
unused_safe = []
for imp in imports:
# Nur sichere Imports berücksichtigen
if imp in self.safe_unused_imports:
# Prüfe verschiedene Nutzungsformen
base_name = imp.split('.')[0]
is_used = (
base_name in used_names or
imp in used_names or
any(imp in name for name in used_names)
)
if not is_used:
unused_safe.append(imp)
return unused_safe
def remove_unused_imports(self, file_path: Path, unused_imports: List[str]) -> bool:
"""Entfernt ungenutzte Imports aus einer Datei"""
if not unused_imports:
return False
try:
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
modified = False
new_lines = []
for line in lines:
should_remove = False
# Prüfe ob die Zeile einen zu entfernenden Import enthält
for unused_imp in unused_imports:
# Verschiedene Import-Patterns prüfen
patterns = [
f"from {unused_imp.split('.')[0]} import {unused_imp.split('.')[-1]}",
f"import {unused_imp}",
f"from {'.'.join(unused_imp.split('.')[:-1])} import {unused_imp.split('.')[-1]}",
]
for pattern in patterns:
if pattern in line and line.strip().startswith(('from ', 'import ')):
# Prüfe ob es eine reine Import-Zeile ist (keine Kommentare etc.)
clean_line = line.split('#')[0].strip()
if clean_line.endswith(unused_imp.split('.')[-1]) or clean_line.endswith(unused_imp):
should_remove = True
break
if should_remove:
break
if not should_remove:
new_lines.append(line)
else:
modified = True
print(f" Entferne: {line.strip()}")
if modified:
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
return True
except Exception as e:
print(f"❌ Fehler beim Bereinigen von {file_path}: {e}")
return False
return False
def clean_file(self, file_path: Path) -> bool:
"""Bereinigt eine einzelne Datei"""
rel_path = file_path.relative_to(self.backend_path)
# Nur sichere Dateien automatisch bereinigen
if str(rel_path) not in self.safe_files:
print(f"⚠️ Überspringe {rel_path} (nicht in sicherer Liste)")
return False
print(f"\n🔍 Analysiere: {rel_path}")
unused_imports = self.find_safe_unused_imports(file_path)
if unused_imports:
print(f" Gefunden: {len(unused_imports)} sichere ungenutzte Imports")
modified = self.remove_unused_imports(file_path, unused_imports)
if modified:
self.changes.append({
'file': str(rel_path),
'removed_imports': unused_imports,
'count': len(unused_imports)
})
print(f"{len(unused_imports)} Imports entfernt")
return True
else:
print(f" ⚠️ Imports gefunden aber nicht entfernt")
else:
print(f" ✅ Keine sicheren ungenutzten Imports gefunden")
return False
def run_cleanup(self):
"""Führt die komplette Bereinigung durch"""
print("🧹 Starte automatische Import-Bereinigung...")
print(f"📁 Backend-Pfad: {self.backend_path}")
# Backup erstellen
self.create_backup()
# Alle Python-Dateien durchgehen
modified_files = 0
total_removed = 0
for py_file in self.backend_path.rglob("*.py"):
if self.should_process_file(py_file):
if self.clean_file(py_file):
modified_files += 1
# Statistiken
total_removed = sum(change['count'] for change in self.changes)
print(f"\n" + "="*60)
print(f"📊 BEREINIGUNG ABGESCHLOSSEN")
print(f"="*60)
print(f"Bearbeitete Dateien: {modified_files}")
print(f"Entfernte Imports gesamt: {total_removed}")
print(f"Backup erstellt in: {self.backup_dir}")
if self.changes:
print(f"\n📝 GEÄNDERTE DATEIEN:")
for change in self.changes:
print(f" 📄 {change['file']}: {change['count']} Imports entfernt")
print(f"\n💡 NÄCHSTE SCHRITTE:")
print(f"1. Tests ausführen: python -m pytest")
print(f"2. App starten und prüfen: python app.py --debug")
print(f"3. Bei Problemen Backup wiederherstellen")
print(f"4. Manuelle Bereinigung von app.py und models.py")
return modified_files, total_removed
def restore_backup(self):
"""Stellt das Backup wieder her"""
if not self.backup_dir.exists():
print("❌ Kein Backup gefunden!")
return False
print(f"🔄 Stelle Backup wieder her aus: {self.backup_dir}")
restored = 0
for backup_file in self.backup_dir.rglob("*.py"):
rel_path = backup_file.relative_to(self.backup_dir)
original_file = self.backend_path / rel_path
if original_file.exists():
shutil.copy2(backup_file, original_file)
restored += 1
print(f"{restored} Dateien wiederhergestellt")
return True
def main():
backend_path = Path(__file__).parent
cleaner = ImportCleaner(str(backend_path))
import sys
if len(sys.argv) > 1 and sys.argv[1] == '--restore':
# Backup wiederherstellen
cleaner.restore_backup()
else:
# Bereinigung durchführen
modified, removed = cleaner.run_cleanup()
if modified > 0:
print(f"\n⚠️ WICHTIG: Führe Tests aus um sicherzustellen dass alles funktioniert!")
print(f"Bei Problemen: python cleanup_imports.py --restore")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,324 +0,0 @@
#!/usr/bin/env python3
"""
Manuelle detaillierte Redundanz-Analyse für MYP Backend
Fokussiert auf wirklich redundante und ungenutzte Funktionen
"""
import os
import re
import ast
from collections import defaultdict
def analyze_imports_and_calls():
"""Analysiert tatsächliche Importe und Funktionsaufrufe"""
backend_path = "/cbin/C0S1-cernel/C02L2/Dateiverwaltung/nextcloud/core/files/3_Beruf_Ausbildung_und_Schule/IHK-Abschlussprüfung/Projektarbeit-MYP/backend"
function_calls = set()
function_defs = {}
file_imports = defaultdict(set)
# Alle Python-Dateien durchgehen
for root, dirs, files in os.walk(backend_path):
# Ignoriere bestimmte Verzeichnisse
dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', 'node_modules', 'instance']]
for file in files:
if not file.endswith('.py'):
continue
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, backend_path)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Funktionsaufrufe mit Regex finden (umfassender als AST)
# Direkte Funktionsaufrufe
direct_calls = re.findall(r'(\w+)\s*\(', content)
function_calls.update(direct_calls)
# Attributaufrufe (object.method())
attr_calls = re.findall(r'\.(\w+)\s*\(', content)
function_calls.update(attr_calls)
# Import-Aufrufe
import_calls = re.findall(r'from\s+[\w.]+\s+import\s+([\w,\s]+)', content)
for imports in import_calls:
for imp in imports.split(','):
function_calls.add(imp.strip())
# AST für Funktionsdefinitionen
try:
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
key = f"{rel_path}:{node.name}"
function_defs[key] = {
'name': node.name,
'file': rel_path,
'line': node.lineno,
'is_private': node.name.startswith('_'),
'is_dunder': node.name.startswith('__') and node.name.endswith('__'),
'decorators': [getattr(d, 'id', str(d)) for d in node.decorator_list]
}
except:
pass
except Exception as e:
print(f"Fehler bei {file_path}: {e}")
return function_calls, function_defs
def find_truly_unused_functions():
"""Findet wirklich ungenutzte Funktionen"""
function_calls, function_defs = analyze_imports_and_calls()
unused = []
for key, func in function_defs.items():
func_name = func['name']
# Ausschließen:
# 1. Dunder-Methoden (__init__, __str__, etc.)
# 2. Flask-Route-Handler (haben @app.route oder @blueprint.route)
# 3. Test-Funktionen
# 4. Main-Funktionen
# 5. Flask-Login required Methoden
if func['is_dunder']:
continue
if func_name in ['main', 'create_app']:
continue
if func_name.startswith('test_'):
continue
# Flask-Login required methods
if func_name in ['is_authenticated', 'is_active', 'is_anonymous', 'get_id']:
continue
# Flask-Route handlers (check decorators)
is_route_handler = any('route' in str(d) or 'login_required' in str(d)
for d in func['decorators'])
if is_route_handler:
continue
# Check if function is actually called
if func_name not in function_calls:
unused.append({
'key': key,
'name': func_name,
'file': func['file'],
'line': func['line'],
'is_private': func['is_private']
})
return unused
def find_duplicate_implementations():
"""Findet Funktionen mit sehr ähnlichen oder identischen Implementierungen"""
backend_path = "/cbin/C0S1-cernel/C02L2/Dateiverwaltung/nextcloud/core/files/3_Beruf_Ausbildung_und_Schule/IHK-Abschlussprüfung/Projektarbeit-MYP/backend"
# Bekannte Duplikate basierend auf Funktionsnamen
known_duplicates = [
# Status-Checking-Funktionen
('get_printer_status', 'check_printer_status', 'printer_status'),
('get_tapo_status', 'check_tapo_status', 'tapo_status'),
# Validation-Funktionen
('validate_email', 'check_email', 'is_valid_email'),
('validate_ip', 'check_ip', 'is_valid_ip'),
# Database-Helper
('get_db_session', 'create_session', 'db_session'),
('close_db', 'close_session', 'cleanup_db'),
# Logging-Funktionen
('log_error', 'error_log', 'write_error'),
('log_info', 'info_log', 'write_info'),
# User-Helper
('get_user_by_id', 'find_user', 'user_by_id'),
('check_permission', 'has_permission', 'validate_permission'),
# File-Handling
('upload_file', 'handle_upload', 'process_upload'),
('delete_file', 'remove_file', 'cleanup_file'),
]
duplicates = []
function_defs = {}
# Sammle alle Funktionsdefinitionen
for root, dirs, files in os.walk(backend_path):
dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', 'node_modules', 'instance']]
for file in files:
if not file.endswith('.py'):
continue
file_path = os.path.join(root, file)
rel_path = os.path.relpath(file_path, backend_path)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
function_defs[node.name] = function_defs.get(node.name, [])
function_defs[node.name].append({
'file': rel_path,
'line': node.lineno,
'name': node.name
})
except:
continue
# Prüfe auf bekannte Duplikate
for duplicate_group in known_duplicates:
found_functions = []
for func_name in duplicate_group:
if func_name in function_defs:
found_functions.extend(function_defs[func_name])
if len(found_functions) > 1:
duplicates.append({
'group': duplicate_group,
'functions': found_functions,
'count': len(found_functions)
})
# Prüfe auf Funktionen mit identischen Namen in verschiedenen Dateien
for func_name, locations in function_defs.items():
if len(locations) > 1 and not func_name.startswith('_'):
duplicates.append({
'group': [func_name],
'functions': locations,
'count': len(locations),
'type': 'identical_names'
})
return duplicates
def analyze_utils_redundancy():
"""Analysiert Redundanz in utils/ Verzeichnis"""
utils_path = "/cbin/C0S1-cernel/C02L2/Dateiverwaltung/nextcloud/core/files/3_Beruf_Ausbildung_und_Schule/IHK-Abschlussprüfung/Projektarbeit-MYP/backend/utils"
utils_files = []
for file in os.listdir(utils_path):
if file.endswith('.py') and file != '__init__.py':
utils_files.append(file)
# Kategorisiere Utils-Dateien nach Funktionalität
categories = {
'database': ['database_cleanup.py', 'database_suite.py', 'data_management.py'],
'security': ['security_suite.py', 'ip_security.py', 'ip_validation.py'],
'ssl': ['ssl_manager.py', 'ssl_suite.py'],
'job_management': ['job_scheduler.py', 'job_queue_system.py'],
'system': ['core_system.py', 'system_management.py'],
'monitoring': ['monitoring_analytics.py', 'audit_logger.py'],
'ui': ['ui_components.py', 'drag_drop_system.py'],
'utilities': ['utilities_collection.py', 'script_collection.py', 'development_tools.py']
}
redundant_categories = []
for category, files in categories.items():
existing_files = [f for f in files if f in utils_files]
if len(existing_files) > 1:
redundant_categories.append({
'category': category,
'files': existing_files,
'recommendation': f"Konsolidiere {len(existing_files)} {category}-bezogene Dateien"
})
return redundant_categories, utils_files
def analyze_blueprint_redundancy():
"""Analysiert Redundanz in blueprints/ Verzeichnis"""
blueprints_path = "/cbin/C0S1-cernel/C02L2/Dateiverwaltung/nextcloud/core/files/3_Beruf_Ausbildung_und_Schule/IHK-Abschlussprüfung/Projektarbeit-MYP/backend/blueprints"
blueprint_files = []
for file in os.listdir(blueprints_path):
if file.endswith('.py'):
blueprint_files.append(file)
# Identifiziere potentielle Duplikate
potential_duplicates = [
('api.py', 'api_simple.py'), # Zwei API-Blueprints
('admin_unified.py', 'sessions.py'), # Überlappende Admin-Funktionalität
]
duplicates = []
for file1, file2 in potential_duplicates:
if file1 in blueprint_files and file2 in blueprint_files:
duplicates.append({
'files': [file1, file2],
'reason': 'Potential functional overlap'
})
return duplicates, blueprint_files
def main():
"""Hauptanalyse"""
print("=" * 80)
print("MANUELLE REDUNDANZ-ANALYSE - MYP BACKEND")
print("=" * 80)
print("\n1. UNGENUTZTE FUNKTIONEN")
print("-" * 40)
unused = find_truly_unused_functions()
unused_public = [f for f in unused if not f['is_private']]
unused_private = [f for f in unused if f['is_private']]
print(f"🔴 Öffentliche ungenutzte Funktionen: {len(unused_public)}")
for func in unused_public[:10]: # Top 10
print(f" {func['file']}:{func['line']} - {func['name']}()")
print(f"\n🟡 Private ungenutzte Funktionen: {len(unused_private)}")
for func in unused_private[:5]: # Top 5
print(f" {func['file']}:{func['line']} - {func['name']}()")
print("\n2. DOPPELTE IMPLEMENTIERUNGEN")
print("-" * 40)
duplicates = find_duplicate_implementations()
for dup in duplicates[:5]: # Top 5
print(f"🔄 {dup['group']}: {dup['count']} Implementierungen")
for func in dup['functions']:
print(f" {func['file']}:{func['line']} - {func['name']}()")
print()
print("\n3. UTILS-VERZEICHNIS REDUNDANZ")
print("-" * 40)
redundant_categories, all_utils = analyze_utils_redundancy()
print(f"📁 Gesamt Utils-Dateien: {len(all_utils)}")
for cat in redundant_categories:
print(f"🔧 {cat['category'].upper()}: {cat['recommendation']}")
for file in cat['files']:
print(f" - {file}")
print()
print("\n4. BLUEPRINT REDUNDANZ")
print("-" * 40)
blueprint_duplicates, all_blueprints = analyze_blueprint_redundancy()
print(f"📁 Gesamt Blueprint-Dateien: {len(all_blueprints)}")
for dup in blueprint_duplicates:
print(f"🔄 Potentielle Duplikate: {' + '.join(dup['files'])}")
print(f" Grund: {dup['reason']}")
print("\n" + "=" * 80)
print("EMPFEHLUNGEN FÜR CLEANUP")
print("=" * 80)
print(f"1. 🗑️ Lösche {len(unused_public)} ungenutzte öffentliche Funktionen")
print(f"2. 🧹 Prüfe {len(unused_private)} ungenutzte private Funktionen")
print(f"3. 🔄 Konsolidiere {len(duplicates)} Duplikat-Gruppen")
print(f"4. 📁 Reorganisiere {len(redundant_categories)} Utils-Kategorien")
print(f"5. 🔗 Prüfe {len(blueprint_duplicates)} Blueprint-Überlappungen")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

4451
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
{
"name": "myp-backend-frontend-build",
"version": "1.0.0",
"description": "Frontend Build System für MYP Backend - Air-Gapped kompatibel",
"private": true,
"scripts": {
"build": "npm run build:tailwind",
"build:tailwind": "tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.min.css --minify",
"build:css": "npm run build:tailwind",
"build:js": "terser static/js/*.js --compress --mangle --output static/js/bundle.min.js --exclude '*.min.js'",
"watch": "npm run watch:tailwind",
"watch:tailwind": "tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.min.css --watch",
"dev": "npm run watch:tailwind",
"clean": "rimraf static/css/tailwind.min.css static/js/bundle.min.js",
"optimize": "npm run build && npm run compress",
"compress": "node scripts/compress-assets.js",
"analyze": "tailwindcss -i ./static/css/input.css -o ./static/css/tailwind.debug.css --verbose",
"install:air-gapped": "npm install --offline --no-optional",
"postinstall": "echo '✅ Frontend-Dependencies für air-gapped Betrieb installiert'"
},
"dependencies": {},
"devDependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/line-clamp": "^0.4.4",
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.19",
"cssnano": "^7.0.2",
"postcss": "^8.4.38",
"postcss-cli": "^11.0.0",
"tailwindcss": "^3.4.4",
"terser": "^5.31.1",
"rimraf": "^5.0.7",
"concurrently": "^8.2.2",
"chokidar-cli": "^3.0.0",
"gzip-size-cli": "^5.1.0",
"filesize": "^10.1.2"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
},
"repository": {
"type": "git",
"url": "internal"
},
"keywords": [
"tailwindcss",
"flask",
"frontend-build",
"air-gapped",
"mercedes-benz",
"3d-printing"
],
"author": "Mercedes-Benz TBA Marienfelde",
"license": "PRIVATE"
}

View File

@ -1,47 +0,0 @@
{% extends "base.html" %}
{% block title %}404 - Seite nicht gefunden - Mercedes-Benz MYP Platform{% endblock %}
{% block content %}
<div class="min-h-[80vh] flex flex-col items-center justify-center p-4">
<!-- 404 Error Container -->
<div class="w-full max-w-md">
<div class="bg-white dark:bg-gray-800 backdrop-blur-xl bg-opacity-95 dark:bg-opacity-95 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-700 p-8 text-center transition-all duration-300">
<!-- Mercedes-Benz Logo -->
<div class="flex justify-center mb-6">
<div class="w-16 h-16 text-gray-300 dark:text-gray-600 transition-transform duration-500 hover:scale-110">
<svg class="w-full h-full" fill="currentColor" viewBox="0 0 80 80">
<path 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>
</div>
</div>
<!-- Error Message -->
<h1 class="text-6xl font-bold text-gray-300 dark:text-gray-600 mb-4">404</h1>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Seite nicht gefunden</h2>
<p class="text-gray-600 dark:text-gray-400 mb-6">Die von Ihnen gesuchte Seite existiert nicht oder wurde verschoben.</p>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row justify-center gap-4 mt-8">
<a href="{{ url_for('dashboard') }}" class="inline-flex items-center justify-center px-5 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium rounded-lg transition-all duration-300 transform hover:-translate-y-0.5">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
<span>Zum Dashboard</span>
</a>
<button onclick="window.history.back()" class="inline-flex items-center justify-center px-5 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 font-medium rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-300 transform hover:-translate-y-0.5">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
<span>Zurück</span>
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,66 +0,0 @@
{% extends "base.html" %}
{% block title %}Interner Serverfehler - Mercedes-Benz MYP Platform{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-red-50 to-orange-50 dark:from-slate-900 dark:via-red-900/20 dark:to-orange-900/20 flex items-center justify-center px-4">
<div class="max-w-2xl w-full text-center">
<!-- Error Icon -->
<div class="mb-8">
<div class="inline-flex items-center justify-center w-24 h-24 bg-red-100 dark:bg-red-900/30 rounded-full mb-6">
<svg class="w-12 h-12 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
</div>
<!-- Error Message -->
<h1 class="text-6xl font-bold text-slate-900 dark:text-white mb-4">500</h1>
<h2 class="text-2xl font-semibold text-slate-700 dark:text-slate-300 mb-6">Interner Serverfehler</h2>
<p class="text-lg text-slate-600 dark:text-slate-400 mb-8 max-w-lg mx-auto">
Es ist ein unerwarteter Fehler aufgetreten. Unser Team wurde automatisch benachrichtigt und arbeitet an einer Lösung.
</p>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('dashboard') }}" class="inline-flex items-center px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl transition-colors duration-200 shadow-lg hover:shadow-xl">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
Zurück zum Dashboard
</a>
<button onclick="window.location.reload()" class="inline-flex items-center px-6 py-3 bg-slate-200 hover:bg-slate-300 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium rounded-xl transition-colors duration-200">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Seite neu laden
</button>
</div>
<!-- Additional Info -->
<div class="mt-12 p-6 bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl border border-slate-200 dark:border-slate-700">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-3">Was können Sie tun?</h3>
<ul class="text-left text-slate-600 dark:text-slate-400 space-y-2">
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Versuchen Sie, die Seite neu zu laden
</li>
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Kehren Sie zum Dashboard zurück
</li>
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Kontaktieren Sie den Administrator, falls das Problem weiterhin besteht
</li>
</ul>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,748 +0,0 @@
{% extends "base.html" %}
{% block title %}Erweiterte Analytik - MYP Platform{% endblock %}
{% block extra_css %}
<style>
.analytics-card {
transition: all 0.3s ease;
border: 1px solid #e2e8f0;
}
.analytics-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.kpi-metric {
font-size: 2.5rem;
font-weight: 700;
line-height: 1;
}
.kpi-trend-up {
color: #22c55e;
}
.kpi-trend-down {
color: #ef4444;
}
.kpi-trend-stable {
color: #6b7280;
}
.chart-container {
position: relative;
height: 400px;
width: 100%;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.loading-spinner {
border: 4px solid #f3f4f6;
border-top: 4px solid #3b82f6;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.filter-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.export-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
transition: all 0.3s ease;
}
.export-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
</style>
{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-slate-900 dark:text-white mb-2">
📈 Erweiterte Analytik
</h1>
<p class="text-slate-600 dark:text-slate-400">
Umfassende Statistiken und KPIs für die MYP 3D-Druck Platform
</p>
</div>
<!-- Filter Section -->
<div class="filter-section">
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex flex-wrap gap-4">
<div class="flex flex-col">
<label class="text-sm font-medium mb-1">Zeitraum</label>
<select id="timeRangeSelect" class="bg-white/20 text-white border border-white/30 rounded-lg px-3 py-2 backdrop-blur-sm">
<option value="day">Letzter Tag</option>
<option value="week">Letzte Woche</option>
<option value="month" selected>Letzter Monat</option>
<option value="quarter">Letztes Quartal</option>
<option value="year">Letztes Jahr</option>
</select>
</div>
<div class="flex flex-col">
<label class="text-sm font-medium mb-1">Report-Typ</label>
<select id="reportTypeSelect" class="bg-white/20 text-white border border-white/30 rounded-lg px-3 py-2 backdrop-blur-sm">
<option value="comprehensive">Umfassend</option>
<option value="printer_usage">Drucker-Nutzung</option>
<option value="user_activity">Benutzer-Aktivität</option>
<option value="efficiency">Effizienz</option>
</select>
</div>
</div>
<div class="flex gap-2">
<button id="refreshData" class="export-button">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Aktualisieren
</button>
<button id="exportReport" class="export-button">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Exportieren
</button>
</div>
</div>
</div>
<!-- Loading Indicator -->
<div id="loadingIndicator" class="text-center py-8 hidden">
<div class="loading-spinner"></div>
<p class="mt-4 text-slate-600 dark:text-slate-400">Lade Analytik-Daten...</p>
</div>
<!-- KPI Dashboard -->
<div id="kpiDashboard" class="mb-8">
<h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-6">🎯 Key Performance Indicators</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6">
<!-- KPI Cards werden dynamisch geladen -->
</div>
</div>
<!-- Analytics Grid -->
<div class="stats-grid">
<!-- Drucker-Statistiken -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
🖨️ Drucker-Statistiken
</h3>
<div class="text-2xl">📊</div>
</div>
<div id="printerStatsContainer">
<div class="loading-spinner"></div>
</div>
</div>
<!-- Job-Statistiken -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
⚙️ Job-Statistiken
</h3>
<div class="text-2xl">📈</div>
</div>
<div id="jobStatsContainer">
<div class="loading-spinner"></div>
</div>
</div>
<!-- Benutzer-Statistiken -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
👥 Benutzer-Statistiken
</h3>
<div class="text-2xl">👤</div>
</div>
<div id="userStatsContainer">
<div class="loading-spinner"></div>
</div>
</div>
<!-- Trend-Analyse -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg col-span-full">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
📊 Trend-Analyse
</h3>
<div class="text-2xl">📉</div>
</div>
<div class="chart-container" id="trendChart">
<canvas id="trendChartCanvas"></canvas>
</div>
</div>
<!-- Drucker-Auslastung -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
⚡ Drucker-Auslastung
</h3>
<div class="text-2xl">🔋</div>
</div>
<div class="chart-container" id="utilizationChart">
<canvas id="utilizationChartCanvas"></canvas>
</div>
</div>
<!-- Top-Benutzer -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
🏆 Top-Benutzer
</h3>
<div class="text-2xl">🥇</div>
</div>
<div id="topUsersContainer">
<div class="loading-spinner"></div>
</div>
</div>
<!-- System-Gesundheit -->
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
💚 System-Gesundheit
</h3>
<div class="text-2xl">❤️</div>
</div>
<div id="systemHealthContainer">
<div class="loading-spinner"></div>
</div>
</div>
</div>
<!-- Report Export Modal -->
<div id="exportModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 max-w-md w-full">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">
📊 Report exportieren
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Format
</label>
<select id="exportFormat" class="w-full border border-slate-300 dark:border-slate-600 rounded-lg px-3 py-2 bg-white dark:bg-slate-700 text-slate-900 dark:text-white">
<option value="json">JSON</option>
<option value="csv">CSV</option>
<option value="pdf">PDF</option>
<option value="excel">Excel</option>
</select>
</div>
<div class="flex justify-end space-x-3">
<button id="cancelExport" class="px-4 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200">
Abbrechen
</button>
<button id="confirmExport" class="export-button">
Export starten
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- Chart.js - Lokale Version -->
<script src="{{ url_for('static', filename='js/charts/chart.min.js') }}"></script>
<script>
/**
* MYP Analytics Dashboard
* Erweiterte Analytik und Statistiken
*/
class AnalyticsDashboard {
constructor() {
this.currentTimeRange = 'month';
this.currentReportType = 'comprehensive';
this.charts = {};
this.data = {};
this.init();
}
init() {
this.setupEventListeners();
this.loadInitialData();
}
setupEventListeners() {
// Filter-Änderungen
document.getElementById('timeRangeSelect').addEventListener('change', (e) => {
this.currentTimeRange = e.target.value;
this.loadData();
});
document.getElementById('reportTypeSelect').addEventListener('change', (e) => {
this.currentReportType = e.target.value;
this.loadData();
});
// Aktionen
document.getElementById('refreshData').addEventListener('click', () => {
this.loadData();
});
document.getElementById('exportReport').addEventListener('click', () => {
this.showExportModal();
});
// Export Modal
document.getElementById('cancelExport').addEventListener('click', () => {
this.hideExportModal();
});
document.getElementById('confirmExport').addEventListener('click', () => {
this.exportReport();
});
// Modal schließen bei Klick außerhalb
document.getElementById('exportModal').addEventListener('click', (e) => {
if (e.target.id === 'exportModal') {
this.hideExportModal();
}
});
}
async loadInitialData() {
this.showLoading();
await this.loadData();
this.hideLoading();
}
async loadData() {
try {
this.showLoading();
// KPIs laden
await this.loadKPIs();
// Report-Daten laden
await this.loadReportData();
// Charts aktualisieren
this.updateCharts();
} catch (error) {
console.error('Fehler beim Laden der Analytics-Daten:', error);
this.showError('Fehler beim Laden der Daten');
} finally {
this.hideLoading();
}
}
async loadKPIs() {
try {
const response = await fetch('/api/analytics/dashboard');
if (!response.ok) throw new Error('Failed to load KPIs');
const data = await response.json();
this.renderKPIs(data.kpis || []);
} catch (error) {
console.error('Fehler beim Laden der KPIs:', error);
}
}
async loadReportData() {
try {
const response = await fetch(`/api/analytics/report/${this.currentReportType}?time_range=${this.currentTimeRange}`);
if (!response.ok) throw new Error('Failed to load report data');
this.data = await response.json();
this.renderStatistics();
} catch (error) {
console.error('Fehler beim Laden der Report-Daten:', error);
}
}
renderKPIs(kpis) {
const container = document.querySelector('#kpiDashboard .grid');
container.innerHTML = kpis.map(kpi => `
<div class="analytics-card bg-white dark:bg-slate-800 rounded-xl p-6 shadow-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-slate-600 dark:text-slate-400">${kpi.name}</h4>
<span class="kpi-trend-${kpi.trend}">
${kpi.trend === 'up' ? '↗️' : kpi.trend === 'down' ? '↘️' : '➡️'}
</span>
</div>
<div class="kpi-metric text-slate-900 dark:text-white mb-1">
${this.formatMetric(kpi.current_value, kpi.unit)}
</div>
<div class="flex items-center justify-between text-xs">
<span class="text-slate-500 dark:text-slate-400">
Ziel: ${this.formatMetric(kpi.target_value, kpi.unit)}
</span>
<span class="kpi-trend-${kpi.trend} font-medium">
${kpi.change_percent > 0 ? '+' : ''}${kpi.change_percent}%
</span>
</div>
</div>
`).join('');
}
renderStatistics() {
// Drucker-Statistiken
if (this.data.sections?.printers) {
this.renderPrinterStats(this.data.sections.printers);
}
// Job-Statistiken
if (this.data.sections?.jobs) {
this.renderJobStats(this.data.sections.jobs);
}
// Benutzer-Statistiken
if (this.data.sections?.users) {
this.renderUserStats(this.data.sections.users);
}
}
renderPrinterStats(printerData) {
const container = document.getElementById('printerStatsContainer');
const summary = printerData.summary;
container.innerHTML = `
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
${summary.total_printers}
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Drucker gesamt</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">
${summary.online_printers}
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Online</div>
</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-slate-900 dark:text-white">
${summary.availability_rate}% Verfügbarkeit
</div>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2 mt-2">
<div class="bg-green-500 h-2 rounded-full" style="width: ${summary.availability_rate}%"></div>
</div>
</div>
</div>
`;
}
renderJobStats(jobData) {
const container = document.getElementById('jobStatsContainer');
const summary = jobData.summary;
container.innerHTML = `
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
${summary.total_jobs}
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Jobs gesamt</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">
${summary.success_rate}%
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Erfolgsrate</div>
</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-slate-900 dark:text-white">
${summary.avg_duration_hours}h Durchschnitt
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">
🎯 ${summary.completed_jobs} abgeschlossen
</div>
</div>
</div>
`;
}
renderUserStats(userData) {
const container = document.getElementById('userStatsContainer');
const summary = userData.summary;
container.innerHTML = `
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="text-center">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">
${summary.total_users}
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Benutzer gesamt</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">
${summary.active_users}
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">Aktiv</div>
</div>
</div>
<div class="text-center">
<div class="text-lg font-semibold text-slate-900 dark:text-white">
${summary.engagement_rate}% Engagement
</div>
<div class="text-sm text-slate-600 dark:text-slate-400">
${summary.new_users} neue Benutzer
</div>
</div>
</div>
`;
// Top-Benutzer rendern
if (userData.top_users) {
this.renderTopUsers(userData.top_users);
}
}
renderTopUsers(topUsers) {
const container = document.getElementById('topUsersContainer');
container.innerHTML = `
<div class="space-y-3">
${topUsers.slice(0, 5).map((user, index) => `
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full flex items-center justify-center text-white text-sm font-bold">
${index + 1}
</div>
<div>
<div class="font-medium text-slate-900 dark:text-white">${user.name || user.username}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">${user.jobs} Jobs</div>
</div>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-400">
${user.total_hours}h
</div>
</div>
`).join('')}
</div>
`;
}
updateCharts() {
this.updateTrendChart();
this.updateUtilizationChart();
}
updateTrendChart() {
const canvas = document.getElementById('trendChartCanvas');
const ctx = canvas.getContext('2d');
// Destroy existing chart
if (this.charts.trend) {
this.charts.trend.destroy();
}
// Sample data - would be replaced with real data
const dailyTrend = this.data.sections?.jobs?.daily_trend || [];
this.charts.trend = new Chart(ctx, {
type: 'line',
data: {
labels: dailyTrend.map(d => new Date(d.date).toLocaleDateString('de-DE')),
datasets: [{
label: 'Jobs pro Tag',
data: dailyTrend.map(d => d.jobs),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
updateUtilizationChart() {
const canvas = document.getElementById('utilizationChartCanvas');
const ctx = canvas.getContext('2d');
// Destroy existing chart
if (this.charts.utilization) {
this.charts.utilization.destroy();
}
const printerUsage = this.data.sections?.printers?.usage_by_printer || [];
this.charts.utilization = new Chart(ctx, {
type: 'doughnut',
data: {
labels: printerUsage.map(p => p.name),
datasets: [{
data: printerUsage.map(p => p.utilization_rate),
backgroundColor: [
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
formatMetric(value, unit) {
if (typeof value !== 'number') return value;
if (unit === '%') {
return `${value.toFixed(1)}%`;
} else if (unit === 'Stunden') {
return `${value.toFixed(1)}h`;
} else if (unit === 'g') {
return `${value.toLocaleString()}g`;
} else {
return `${value.toLocaleString()} ${unit}`;
}
}
showExportModal() {
document.getElementById('exportModal').classList.remove('hidden');
}
hideExportModal() {
document.getElementById('exportModal').classList.add('hidden');
}
async exportReport() {
try {
const format = document.getElementById('exportFormat').value;
const response = await fetch(`/api/analytics/report/${this.currentReportType}?time_range=${this.currentTimeRange}&format=${format}`);
if (!response.ok) throw new Error('Export fehlgeschlagen');
// Download starten
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `myp-analytics-${this.currentReportType}-${this.currentTimeRange}.${format}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
this.hideExportModal();
this.showSuccess('Report erfolgreich exportiert');
} catch (error) {
console.error('Export-Fehler:', error);
this.showError('Fehler beim Exportieren des Reports');
}
}
showLoading() {
document.getElementById('loadingIndicator').classList.remove('hidden');
}
hideLoading() {
document.getElementById('loadingIndicator').classList.add('hidden');
}
showError(message) {
if (typeof showFlashMessage === 'function') {
showFlashMessage(message, 'error');
} else {
alert(message);
}
}
showSuccess(message) {
if (typeof showFlashMessage === 'function') {
showFlashMessage(message, 'success');
} else {
alert(message);
}
}
}
// Dashboard initialisieren
document.addEventListener('DOMContentLoaded', () => {
new AnalyticsDashboard();
});
</script>
{% endblock %}

View File

@ -1,222 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSRF-Token Test - MYP Platform</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.test-section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.success { background: #d4edda; border-color: #c3e6cb; color: #155724; }
.error { background: #f8d7da; border-color: #f5c6cb; color: #721c24; }
.info { background: #d1ecf1; border-color: #bee5eb; color: #0c5460; }
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; cursor: pointer; }
.btn-primary { background: #007bff; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-success { background: #28a745; color: white; }
pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; }
.token-display { font-family: monospace; background: #f8f9fa; padding: 10px; border-radius: 5px; word-break: break-all; }
</style>
</head>
<body>
<div class="container">
<h1>🔒 CSRF-Token Diagnose & Test</h1>
<p>Diese Seite hilft bei der Diagnose und Behebung von CSRF-Token-Problemen.</p>
<!-- Token-Anzeige -->
<div class="test-section info">
<h3>📋 Aktuelle Token-Informationen</h3>
<p><strong>Meta-Tag Token:</strong></p>
<div class="token-display" id="meta-token">{{ csrf_token() }}</div>
<p><strong>Session Token:</strong></p>
<div class="token-display" id="session-token">{{ session.get('_csrf_token', 'Nicht verfügbar') }}</div>
<p><strong>JavaScript Token:</strong></p>
<div class="token-display" id="js-token">Wird geladen...</div>
</div>
<!-- Test-Formular -->
<div class="test-section">
<h3>📝 CSRF-Test-Formular</h3>
<form id="test-form" method="POST" action="/api/csrf-test">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<p>
<label for="test-data">Test-Daten:</label><br>
<input type="text" id="test-data" name="test_data" value="CSRF-Test-Daten" style="width: 300px; padding: 5px;">
</p>
<button type="submit" class="btn-primary">📤 Formular senden (mit Token)</button>
</form>
</div>
<!-- AJAX-Tests -->
<div class="test-section">
<h3>🌐 AJAX-Tests</h3>
<button onclick="testAjaxWithToken()" class="btn-success">✅ AJAX mit Token</button>
<button onclick="testAjaxWithoutToken()" class="btn-danger">❌ AJAX ohne Token</button>
<button onclick="testFetchWithToken()" class="btn-success">✅ Fetch mit Token</button>
<button onclick="testFetchWithoutToken()" class="btn-danger">❌ Fetch ohne Token</button>
</div>
<!-- Ergebnisse -->
<div class="test-section">
<h3>📊 Test-Ergebnisse</h3>
<div id="results"></div>
</div>
<!-- Debug-Informationen -->
<div class="test-section">
<h3>🔍 Debug-Informationen</h3>
<button onclick="showDebugInfo()" class="btn-primary">Debug-Info anzeigen</button>
<pre id="debug-info" style="display: none;"></pre>
</div>
</div>
<!-- CSRF-Fix Script -->
<script src="{{ url_for('static', filename='js/csrf-fix.js') }}"></script>
<script>
// Token-Anzeige aktualisieren
document.addEventListener('DOMContentLoaded', function() {
const jsToken = window.getCSRFToken ? window.getCSRFToken() : 'CSRF-Fix nicht geladen';
document.getElementById('js-token').textContent = jsToken;
});
// Ergebnis anzeigen
function showResult(message, isSuccess = true) {
const results = document.getElementById('results');
const div = document.createElement('div');
div.className = isSuccess ? 'success' : 'error';
div.innerHTML = `<strong>${new Date().toLocaleTimeString()}:</strong> ${message}`;
results.appendChild(div);
results.scrollTop = results.scrollHeight;
}
// AJAX mit Token
function testAjaxWithToken() {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/csrf-test');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-CSRFToken', window.getCSRFToken());
xhr.onload = function() {
if (xhr.status === 200) {
showResult('✅ AJAX mit Token erfolgreich', true);
} else {
showResult(`❌ AJAX mit Token fehlgeschlagen: ${xhr.status} ${xhr.statusText}`, false);
}
};
xhr.onerror = function() {
showResult('❌ AJAX mit Token - Netzwerkfehler', false);
};
xhr.send(JSON.stringify({test_data: 'AJAX-Test mit Token'}));
}
// AJAX ohne Token
function testAjaxWithoutToken() {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/csrf-test');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 400) {
showResult('✅ AJAX ohne Token korrekt abgelehnt (400)', true);
} else {
showResult(`⚠️ AJAX ohne Token unerwartete Antwort: ${xhr.status}`, false);
}
};
xhr.onerror = function() {
showResult('❌ AJAX ohne Token - Netzwerkfehler', false);
};
xhr.send(JSON.stringify({test_data: 'AJAX-Test ohne Token'}));
}
// Fetch mit Token
async function testFetchWithToken() {
try {
const response = await fetch('/api/csrf-test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': window.getCSRFToken()
},
body: JSON.stringify({test_data: 'Fetch-Test mit Token'})
});
if (response.ok) {
showResult('✅ Fetch mit Token erfolgreich', true);
} else {
showResult(`❌ Fetch mit Token fehlgeschlagen: ${response.status} ${response.statusText}`, false);
}
} catch (error) {
showResult(`❌ Fetch mit Token Fehler: ${error.message}`, false);
}
}
// Fetch ohne Token
async function testFetchWithoutToken() {
try {
const response = await fetch('/api/csrf-test', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({test_data: 'Fetch-Test ohne Token'})
});
if (response.status === 400) {
showResult('✅ Fetch ohne Token korrekt abgelehnt (400)', true);
} else {
showResult(`⚠️ Fetch ohne Token unerwartete Antwort: ${response.status}`, false);
}
} catch (error) {
showResult(`❌ Fetch ohne Token Fehler: ${error.message}`, false);
}
}
// Debug-Informationen
function showDebugInfo() {
const debugInfo = {
'Meta-Tag Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'),
'Hidden Input Token': document.querySelector('input[name="csrf_token"]')?.value,
'JavaScript Token': window.getCSRFToken ? window.getCSRFToken() : 'Nicht verfügbar',
'Cookies': document.cookie,
'User Agent': navigator.userAgent,
'URL': window.location.href,
'Timestamp': new Date().toISOString()
};
document.getElementById('debug-info').textContent = JSON.stringify(debugInfo, null, 2);
document.getElementById('debug-info').style.display = 'block';
}
// Formular-Handler
document.getElementById('test-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('/api/csrf-test', {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
showResult('✅ Formular erfolgreich gesendet', true);
} else {
showResult(`❌ Formular fehlgeschlagen: ${response.status} ${response.statusText}`, false);
}
})
.catch(error => {
showResult(`❌ Formular Fehler: ${error.message}`, false);
});
});
</script>
</body>
</html>

View File

@ -1,594 +0,0 @@
{% extends "base.html" %}
{% block title %}Energiemonitoring - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
<style>
/* Energiemonitoring Dashboard Styles */
.energy-card {
@apply glass card;
transition: all 0.3s ease;
}
.energy-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
}
.energy-metric {
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.device-status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.device-status-online {
background-color: #10b981;
box-shadow: 0 0 10px rgba(16, 185, 129, 0.6);
}
.device-status-offline {
background-color: #ef4444;
box-shadow: 0 0 10px rgba(239, 68, 68, 0.6);
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
.power-gradient {
background: linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%);
height: 6px;
border-radius: 3px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
{% endblock %}
{% block content %}
<div class="main-offset min-h-screen">
<!-- Header Section -->
<div class="glass mb-8">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-primary mb-2">🔋 Energiemonitoring</h1>
<p class="text-secondary">
Überwachen Sie den Energieverbrauch Ihrer 3D-Drucker in Echtzeit
</p>
</div>
<div class="flex items-center space-x-4">
<button id="refreshData" class="btn btn-primary">
<i class="fas fa-sync-alt mr-2"></i>
Aktualisieren
</button>
<button id="exportData" class="btn">
<i class="fas fa-download mr-2"></i>
Export
</button>
</div>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- KPI Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Gesamtverbrauch -->
<div class="energy-card">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</div>
<div class="text-right">
<div id="totalPower" class="energy-metric">{{ stats.total_current_power or 0 }}W</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Gesamtverbrauch</div>
</div>
</div>
<div class="flex items-center space-x-2">
<div class="device-status-indicator device-status-online"></div>
<span class="text-xs text-green-600 dark:text-green-400 font-medium">Live-Daten</span>
</div>
</div>
<!-- Online Geräte -->
<div class="energy-card">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-green-500 to-green-600 rounded-2xl shadow-lg">
<svg class="w-6 h-6 text-white" 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>
</div>
<div class="text-right">
<div id="onlineDevices" class="energy-metric">{{ stats.online_devices or 0 }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Online Geräte</div>
</div>
</div>
<div class="text-sm text-slate-600 dark:text-slate-300">
von <span id="totalDevices">{{ stats.total_devices or 0 }}</span> Geräten
</div>
</div>
<!-- Heute Verbrauch -->
<div class="energy-card">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-purple-500 to-purple-600 rounded-2xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="text-right">
<div id="todayEnergy" class="energy-metric">{{ stats.total_today_energy or 0 }}Wh</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Heute</div>
</div>
</div>
<div class="text-sm text-slate-600 dark:text-slate-300">
Ø <span id="avgTodayEnergy">{{ stats.avg_today_energy or 0 }}</span>Wh pro Gerät
</div>
</div>
<!-- Monatsverbrauch -->
<div class="energy-card">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div class="text-right">
<div id="monthEnergy" class="energy-metric">{{ stats.total_month_energy or 0 }}Wh</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Diesen Monat</div>
</div>
</div>
<div class="text-sm text-slate-600 dark:text-slate-300">
Ø <span id="avgMonthEnergy">{{ stats.avg_month_energy or 0 }}</span>Wh pro Gerät
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Verbrauchstrend Chart -->
<div class="energy-card p-6">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-semibold text-slate-900 dark:text-white">📈 Verbrauchstrend</h3>
<select id="periodSelector" class="px-3 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-2xl text-sm shadow-lg transition-all duration-300 hover:shadow-xl">
<option value="today">Heute (24h)</option>
<option value="week">Diese Woche</option>
<option value="month">Dieser Monat</option>
<option value="year">Dieses Jahr</option>
</select>
</div>
<div class="chart-container">
<canvas id="consumptionChart"></canvas>
</div>
</div>
<!-- Geräteverbrauch Chart -->
<div class="energy-card">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-semibold text-primary">🔌 Geräteverbrauch</h3>
<div class="text-sm text-muted">Live-Verbrauch</div>
</div>
<div class="chart-container">
<canvas id="deviceChart"></canvas>
</div>
</div>
</div>
<!-- Device List -->
<div class="energy-card mb-8">
<h3 class="text-xl font-semibold text-primary mb-6">🖨️ Geräteübersicht</h3>
<div id="deviceList" class="space-y-4">
<!-- Wird dynamisch gefüllt -->
<div class="flex justify-center items-center py-8">
<div class="spinner w-8 h-8"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="modal-overlay hidden">
<div class="glass p-6 flex items-center space-x-4">
<div class="spinner w-8 h-8"></div>
<span class="text-primary">Lade Energiedaten...</span>
</div>
</div>
{% endblock %}
{% block scripts %}
<!-- Chart.js -->
<script src="{{ url_for('static', filename='js/charts/chart.min.js') }}"></script>
<script>
/**
* MYP Energiemonitoring Dashboard
* Echtzeit-Überwachung des Energieverbrauchs von Tapo P110 Smart Plugs
*/
class EnergyMonitoringDashboard {
constructor() {
this.currentPeriod = 'today';
this.charts = {};
this.updateInterval = null;
this.data = {};
this.init();
}
init() {
this.setupEventListeners();
this.loadInitialData();
this.startAutoUpdate();
}
setupEventListeners() {
// Period Selector
document.getElementById('periodSelector').addEventListener('change', (e) => {
this.currentPeriod = e.target.value;
this.loadConsumptionData();
});
// Refresh Button
document.getElementById('refreshData').addEventListener('click', () => {
this.loadAllData();
});
// Export Button
document.getElementById('exportData').addEventListener('click', () => {
this.exportData();
});
}
async loadInitialData() {
this.showLoading();
await this.loadAllData();
this.hideLoading();
}
async loadAllData() {
try {
await Promise.all([
this.loadDashboardData(),
this.loadConsumptionData(),
this.loadDeviceData()
]);
} catch (error) {
console.error('Fehler beim Laden der Energiedaten:', error);
this.showError('Fehler beim Laden der Energiedaten');
}
}
async loadDashboardData() {
try {
const response = await fetch('/api/energy/dashboard');
if (!response.ok) throw new Error('Dashboard-Daten konnten nicht geladen werden');
const result = await response.json();
if (result.success) {
this.data.dashboard = result.data;
this.updateKPIs(result.data);
}
} catch (error) {
console.error('Dashboard-Daten-Fehler:', error);
}
}
async loadConsumptionData() {
try {
const response = await fetch(`/api/energy/statistics?period=${this.currentPeriod}`);
if (!response.ok) throw new Error('Verbrauchsdaten konnten nicht geladen werden');
const result = await response.json();
if (result.success) {
this.data.consumption = result.data;
this.updateConsumptionChart(result.data.chart_data);
}
} catch (error) {
console.error('Verbrauchsdaten-Fehler:', error);
}
}
async loadDeviceData() {
try {
const response = await fetch('/api/energy/live');
if (!response.ok) throw new Error('Gerätedaten konnten nicht geladen werden');
const result = await response.json();
if (result.success) {
this.data.devices = result.data;
this.updateDeviceChart(result.data.devices);
this.updateDeviceList(result.data.devices);
}
} catch (error) {
console.error('Gerätedaten-Fehler:', error);
}
}
updateKPIs(dashboardData) {
const overview = dashboardData.overview || {};
const consumption = dashboardData.current_consumption || {};
const totals = dashboardData.energy_totals || {};
// KPI Updates
document.getElementById('totalPower').textContent = `${consumption.total_power || 0}W`;
document.getElementById('onlineDevices').textContent = overview.online_devices || 0;
document.getElementById('totalDevices').textContent = overview.total_devices || 0;
document.getElementById('todayEnergy').textContent = `${totals.today_total || 0}Wh`;
document.getElementById('avgTodayEnergy').textContent = totals.today_average || 0;
document.getElementById('monthEnergy').textContent = `${totals.month_total || 0}Wh`;
document.getElementById('avgMonthEnergy').textContent = totals.month_average || 0;
}
updateConsumptionChart(chartData) {
const canvas = document.getElementById('consumptionChart');
const ctx = canvas.getContext('2d');
// Destroy existing chart
if (this.charts.consumption) {
this.charts.consumption.destroy();
}
this.charts.consumption = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${context.parsed.y}Wh`;
}
}
}
},
scales: {
x: {
display: true,
grid: {
color: 'rgba(0,0,0,0.1)'
}
},
y: {
display: true,
beginAtZero: true,
grid: {
color: 'rgba(0,0,0,0.1)'
},
ticks: {
callback: function(value) {
return value + 'Wh';
}
}
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
}
});
}
updateDeviceChart(devices) {
const canvas = document.getElementById('deviceChart');
const ctx = canvas.getContext('2d');
// Destroy existing chart
if (this.charts.devices) {
this.charts.devices.destroy();
}
const onlineDevices = devices.filter(d => d.online && d.power > 0);
if (onlineDevices.length === 0) {
// Zeige "Keine Daten" Nachricht
ctx.font = '16px Arial';
ctx.fillStyle = '#64748b';
ctx.textAlign = 'center';
ctx.fillText('Keine aktiven Geräte', canvas.width / 2, canvas.height / 2);
return;
}
const chartData = {
labels: onlineDevices.map(d => d.name),
datasets: [{
label: 'Aktueller Verbrauch (W)',
data: onlineDevices.map(d => d.power),
backgroundColor: [
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
],
borderColor: '#ffffff',
borderWidth: 2
}]
};
this.charts.devices = new Chart(ctx, {
type: 'doughnut',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
},
tooltip: {
callbacks: {
label: function(context) {
const device = onlineDevices[context.dataIndex];
return `${device.name}: ${device.power}W`;
}
}
}
}
}
});
}
updateDeviceList(devices) {
const container = document.getElementById('deviceList');
if (devices.length === 0) {
container.innerHTML = `
<div class="text-center py-8 text-muted">
<svg class="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<p>Keine Energiemonitoring-Geräte gefunden</p>
</div>
`;
return;
}
container.innerHTML = devices.map(device => `
<div class="glass-card hover-lift">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="device-status-indicator ${device.online ? 'device-status-online' : 'device-status-offline'}"></div>
<div>
<h4 class="font-semibold text-primary">${device.name}</h4>
<p class="text-sm text-muted">ID: ${device.id}</p>
</div>
</div>
<div class="text-right">
<div class="text-xl font-bold text-primary">
${device.power}W
</div>
<div class="text-sm text-muted">
${device.online ? 'Online' : 'Offline'}
</div>
</div>
<div class="w-16">
<div class="power-gradient"></div>
<div class="text-xs text-center mt-1 text-muted">
${Math.round((device.power / 100) * 100)}%
</div>
</div>
</div>
</div>
`).join('');
}
startAutoUpdate() {
// Update alle 30 Sekunden
this.updateInterval = setInterval(() => {
this.loadDeviceData(); // Nur Live-Daten für bessere Performance
}, 30000);
}
stopAutoUpdate() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
async exportData() {
try {
const response = await fetch(`/api/energy/statistics?period=${this.currentPeriod}&format=csv`);
if (!response.ok) throw new Error('Export fehlgeschlagen');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `energy-monitoring-${this.currentPeriod}-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
this.showSuccess('Daten erfolgreich exportiert');
} catch (error) {
console.error('Export-Fehler:', error);
this.showError('Fehler beim Exportieren der Daten');
}
}
showLoading() {
document.getElementById('loadingOverlay').classList.remove('hidden');
}
hideLoading() {
document.getElementById('loadingOverlay').classList.add('hidden');
}
showError(message) {
// Verwende bestehende Flash-Message-Funktion falls verfügbar
if (typeof showFlashMessage === 'function') {
showFlashMessage(message, 'error');
} else {
alert(message);
}
}
showSuccess(message) {
if (typeof showFlashMessage === 'function') {
showFlashMessage(message, 'success');
} else {
alert(message);
}
}
destroy() {
this.stopAutoUpdate();
// Charts zerstören
Object.values(this.charts).forEach(chart => {
if (chart) chart.destroy();
});
}
}
// Dashboard initialisieren
let energyDashboard;
document.addEventListener('DOMContentLoaded', () => {
energyDashboard = new EnergyMonitoringDashboard();
});
// Cleanup bei Seitenwechsel
window.addEventListener('beforeunload', () => {
if (energyDashboard) {
energyDashboard.destroy();
}
});
</script>
{% endblock %}

View File

@ -1,511 +0,0 @@
{% extends "base.html" %}
{% block title %}Steckdosen-Test - Mercedes-Benz TBA Marienfelde{% endblock %}
{% block extra_css %}
<style>
.test-card {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
border-radius: 16px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.dark .test-card {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
}
.risk-low { border-left: 4px solid #10b981; }
.risk-medium { border-left: 4px solid #f59e0b; }
.risk-high { border-left: 4px solid #ef4444; }
.socket-online { color: #10b981; }
.socket-offline { color: #ef4444; }
.socket-error { color: #f59e0b; }
.test-button {
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
border: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.test-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.btn-test-on { background: #16a34a; color: white; }
.btn-test-off { background: #ef4444; color: white; }
.btn-test-status { background: #0073ce; color: white; }
.warning-banner {
background: linear-gradient(90deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.05) 100%);
border: 1px solid rgba(239, 68, 68, 0.2);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.info-banner {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.05) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.loading-spinner {
border: 2px solid #f3f4f6;
border-top: 2px solid #0073ce;
border-radius: 50%;
width: 1rem;
height: 1rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<div class="space-y-8">
<!-- Header -->
<div class="dashboard-card p-6">
<div class="flex items-center gap-6">
<div class="w-16 h-16 bg-red-600 text-white rounded-xl flex items-center justify-center">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</div>
<div>
<h1 class="text-4xl font-bold text-mercedes-black dark:text-white">⚡ Steckdosen-Test</h1>
<p class="text-mercedes-gray dark:text-slate-400 mt-1">Sichere Testfunktion für Ausbilder und Administratoren</p>
</div>
</div>
</div>
<!-- Sicherheitshinweis -->
<div class="warning-banner">
<div class="flex items-start gap-3">
<svg class="w-6 h-6 text-red-600 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.268 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div>
<h3 class="font-semibold text-red-800 dark:text-red-200">⚠️ SICHERHEITSHINWEIS</h3>
<p class="text-red-700 dark:text-red-300 mt-1">
Diese Funktion ist nur für geschulte Ausbilder und Administratoren bestimmt.
Prüfen Sie immer den Status vor dem Ein-/Ausschalten von Steckdosen.
</p>
</div>
</div>
</div>
<!-- Übersicht aller Steckdosen -->
<div class="dashboard-card p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white">Übersicht aller Steckdosen</h2>
<button onclick="loadAllSocketsStatus()" class="test-button btn-test-status">
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Alle Status aktualisieren
</button>
</div>
<!-- Statistiken -->
<div id="socket-summary" class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
<!-- Wird per JavaScript gefüllt -->
</div>
<!-- Steckdosen-Liste -->
<div id="all-sockets-list" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="flex items-center justify-center p-8">
<div class="loading-spinner"></div>
<span class="ml-2">Lade Steckdosen-Status...</span>
</div>
</div>
</div>
<!-- Einzeltest -->
<div class="dashboard-card p-6">
<h2 class="text-2xl font-bold text-mercedes-black dark:text-white mb-6">Einzelne Steckdose testen</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Drucker-Auswahl -->
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Drucker auswählen:
</label>
<select id="printer-select" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
<option value="">Bitte Drucker auswählen...</option>
</select>
<button onclick="loadSingleSocketStatus()" id="load-status-btn"
class="test-button btn-test-status w-full" disabled>
<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-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
</svg>
Status prüfen
</button>
</div>
<!-- Status-Anzeige -->
<div id="single-socket-status" class="space-y-4">
<div class="info-banner">
<p class="text-blue-700 dark:text-blue-300">
Wählen Sie einen Drucker aus um den Steckdosen-Status zu prüfen.
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Test-Bestätigungsmodal -->
<div id="test-confirmation-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full p-6">
<div class="flex items-center gap-3 mb-4">
<svg class="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.268 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<h3 class="text-lg font-semibold">Test bestätigen</h3>
</div>
<div id="test-modal-content">
<!-- Wird per JavaScript gefüllt -->
</div>
<div class="flex gap-3 mt-6">
<button onclick="closeTestModal()" class="test-button bg-gray-500 text-white flex-1">
Abbrechen
</button>
<button onclick="executeTest()" id="confirm-test-btn" class="test-button bg-red-600 text-white flex-1">
Test durchführen
</button>
</div>
</div>
</div>
<script>
let currentTestData = null;
let printers = [];
// Seite initialisieren
document.addEventListener('DOMContentLoaded', function() {
loadPrinters();
loadAllSocketsStatus();
});
// Drucker laden
async function loadPrinters() {
try {
const response = await fetch('/api/printers');
const data = await response.json();
if (data.success) {
printers = data.printers;
const select = document.getElementById('printer-select');
select.innerHTML = '<option value="">Bitte Drucker auswählen...</option>';
printers.forEach(printer => {
if (printer.plug_ip) { // Nur Drucker mit Steckdose
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.location || 'Unbekannter Standort'})`;
select.appendChild(option);
}
});
select.addEventListener('change', function() {
const loadBtn = document.getElementById('load-status-btn');
loadBtn.disabled = !this.value;
});
}
} catch (error) {
console.error('Fehler beim Laden der Drucker:', error);
showNotification('Fehler beim Laden der Drucker', 'error');
}
}
// Alle Steckdosen-Status laden
async function loadAllSocketsStatus() {
try {
const response = await fetch('/api/printers/test/all-sockets');
const data = await response.json();
if (data.success) {
displaySocketsSummary(data.summary);
displayAllSockets(data.sockets);
} else {
throw new Error(data.error || 'Unbekannter Fehler');
}
} catch (error) {
console.error('Fehler beim Laden der Steckdosen:', error);
showNotification('Fehler beim Laden der Steckdosen: ' + error.message, 'error');
}
}
// Einzelnen Steckdosen-Status laden
async function loadSingleSocketStatus() {
const printerId = document.getElementById('printer-select').value;
if (!printerId) return;
const statusDiv = document.getElementById('single-socket-status');
statusDiv.innerHTML = '<div class="flex items-center"><div class="loading-spinner"></div><span class="ml-2">Status wird geladen...</span></div>';
try {
const response = await fetch(`/api/printers/test/socket/${printerId}`);
const data = await response.json();
if (data.success) {
displaySingleSocketStatus(data);
} else {
statusDiv.innerHTML = `<div class="warning-banner"><p class="text-red-700">${data.error}</p></div>`;
}
} catch (error) {
console.error('Fehler beim Laden des Socket-Status:', error);
statusDiv.innerHTML = `<div class="warning-banner"><p class="text-red-700">Fehler: ${error.message}</p></div>`;
}
}
// Zusammenfassung anzeigen
function displaySocketsSummary(summary) {
const summaryDiv = document.getElementById('socket-summary');
summaryDiv.innerHTML = `
<div class="bg-blue-50 dark:bg-blue-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-blue-600 dark:text-blue-400">${summary.total_sockets}</div>
<div class="text-sm text-blue-800 dark:text-blue-300">Gesamt</div>
</div>
<div class="bg-green-50 dark:bg-green-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-green-600 dark:text-green-400">${summary.online}</div>
<div class="text-sm text-green-800 dark:text-green-300">Online</div>
</div>
<div class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<div class="text-2xl font-bold text-gray-600 dark:text-gray-400">${summary.offline}</div>
<div class="text-sm text-gray-800 dark:text-gray-300">Offline</div>
</div>
<div class="bg-red-50 dark:bg-red-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-red-600 dark:text-red-400">${summary.error}</div>
<div class="text-sm text-red-800 dark:text-red-300">Fehler</div>
</div>
<div class="bg-orange-50 dark:bg-orange-900 p-4 rounded-lg">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">${summary.with_warnings}</div>
<div class="text-sm text-orange-800 dark:text-orange-300">Mit Warnungen</div>
</div>
`;
}
// Alle Steckdosen anzeigen
function displayAllSockets(sockets) {
const listDiv = document.getElementById('all-sockets-list');
if (sockets.length === 0) {
listDiv.innerHTML = '<div class="col-span-full text-center p-8"><p class="text-gray-500">Keine konfigurierten Steckdosen gefunden.</p></div>';
return;
}
listDiv.innerHTML = sockets.map(socket => `
<div class="test-card p-4 ${getRiskClass(socket.warnings.length)}">
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="font-semibold text-lg">${socket.printer.name}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">${socket.printer.location || 'Unbekannter Standort'}</p>
</div>
<div class="text-right">
<div class="socket-${socket.socket.status} font-semibold">
${getStatusText(socket.socket.status, socket.socket.device_on)}
</div>
${socket.socket.current_power ? `<div class="text-sm text-gray-600">${socket.socket.current_power}W</div>` : ''}
</div>
</div>
${socket.warnings.length > 0 ? `
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 rounded p-3 mb-4">
<div class="font-medium text-yellow-800 dark:text-yellow-200 mb-1">⚠️ Warnungen:</div>
${socket.warnings.map(warning => `<div class="text-sm text-yellow-700 dark:text-yellow-300">• ${warning}</div>`).join('')}
</div>
` : ''}
<div class="flex gap-2">
<button onclick="testSocketControl(${socket.printer.id}, 'on')"
class="test-button btn-test-on flex-1 ${socket.socket.device_on ? 'opacity-50' : ''}">
⚡ Einschalten
</button>
<button onclick="testSocketControl(${socket.printer.id}, 'off')"
class="test-button btn-test-off flex-1 ${!socket.socket.device_on ? 'opacity-50' : ''}">
🔌 Ausschalten
</button>
</div>
</div>
`).join('');
}
// Einzelstatus anzeigen
function displaySingleSocketStatus(data) {
const statusDiv = document.getElementById('single-socket-status');
const riskClass = getRiskClass(data.safety.warnings.length);
statusDiv.innerHTML = `
<div class="test-card p-4 ${riskClass}">
<div class="mb-4">
<h3 class="font-semibold text-lg">${data.printer.name}</h3>
<p class="text-sm text-gray-600">${data.printer.location}</p>
<div class="mt-2">
<span class="socket-${data.socket.status} font-semibold">
${getStatusText(data.socket.status, data.socket.info?.device_on)}
</span>
${data.socket.info?.current_power ? `${data.socket.info.current_power}W` : ''}
</div>
</div>
${data.safety.warnings.length > 0 ? `
<div class="warning-banner mb-4">
<div class="font-medium mb-2">⚠️ Sicherheitswarnungen:</div>
${data.safety.warnings.map(warning => `<div>• ${warning}</div>`).join('')}
</div>
` : ''}
${data.safety.recommendations.length > 0 ? `
<div class="info-banner mb-4">
<div class="font-medium mb-2">💡 Empfehlungen:</div>
${data.safety.recommendations.map(rec => `<div>• ${rec}</div>`).join('')}
</div>
` : ''}
<div class="flex gap-2">
<button onclick="testSocketControl(${data.printer.id}, 'on')"
class="test-button btn-test-on flex-1">
⚡ Test: Einschalten
</button>
<button onclick="testSocketControl(${data.printer.id}, 'off')"
class="test-button btn-test-off flex-1">
🔌 Test: Ausschalten
</button>
</div>
</div>
`;
}
// Test-Modal öffnen
function testSocketControl(printerId, action) {
const printer = printers.find(p => p.id == printerId);
if (!printer) return;
currentTestData = { printerId, action, printer };
const modal = document.getElementById('test-confirmation-modal');
const content = document.getElementById('test-modal-content');
content.innerHTML = `
<p class="mb-4">
<strong>Drucker:</strong> ${printer.name}<br>
<strong>Aktion:</strong> Steckdose ${action === 'on' ? 'einschalten' : 'ausschalten'}
</p>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Grund für den Test:</label>
<input type="text" id="test-reason" class="w-full p-2 border rounded"
placeholder="z.B. Routinetest, Wartung, etc." value="Routinetest">
</div>
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox" id="force-test" class="mr-2">
<span class="text-sm">Sicherheitswarnungen überschreiben (force)</span>
</label>
</div>
`;
modal.classList.remove('hidden');
}
// Test ausführen
async function executeTest() {
if (!currentTestData) return;
const reason = document.getElementById('test-reason').value || 'Routinetest';
const force = document.getElementById('force-test').checked;
const confirmBtn = document.getElementById('confirm-test-btn');
confirmBtn.innerHTML = '<div class="loading-spinner"></div> Teste...';
confirmBtn.disabled = true;
try {
const response = await fetch(`/api/printers/test/socket/${currentTestData.printerId}/control`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name=csrf-token]').getAttribute('content')
},
body: JSON.stringify({
action: currentTestData.action,
test_reason: reason,
force: force
})
});
const data = await response.json();
if (data.success) {
showNotification(`Test erfolgreich: ${data.message}`, 'success');
loadAllSocketsStatus();
loadSingleSocketStatus();
} else {
if (data.requires_force) {
showNotification('Test blockiert: ' + data.error + ' Aktivieren Sie "force" um fortzufahren.', 'warning');
} else {
showNotification('Test fehlgeschlagen: ' + data.error, 'error');
}
}
} catch (error) {
console.error('Fehler beim Test:', error);
showNotification('Fehler beim Test: ' + error.message, 'error');
} finally {
closeTestModal();
}
}
// Modal schließen
function closeTestModal() {
const modal = document.getElementById('test-confirmation-modal');
modal.classList.add('hidden');
currentTestData = null;
const confirmBtn = document.getElementById('confirm-test-btn');
confirmBtn.innerHTML = 'Test durchführen';
confirmBtn.disabled = false;
}
// Hilfsfunktionen
function getRiskClass(warningCount) {
if (warningCount === 0) return 'risk-low';
if (warningCount <= 2) return 'risk-medium';
return 'risk-high';
}
function getStatusText(status, deviceOn) {
switch (status) {
case 'online': return deviceOn ? '🟢 Eingeschaltet' : '🔴 Ausgeschaltet';
case 'offline': return '🔴 Ausgeschaltet';
case 'error': return '⚠️ Fehler';
default: return '❓ Unbekannt';
}
}
function showNotification(message, type) {
// Einfache Benachrichtigung - kann durch Toast-System ersetzt werden
alert(message);
}
</script>
{% endblock %}

View File

@ -1,315 +0,0 @@
#!/usr/bin/env python3.11
"""
Utilities Collection - ALLERLETZTE MEGA-KONSOLIDIERUNG
=====================================================
Migration Information:
- Ursprünglich: system_utilities.py, development_utilities.py, printer_utilities.py,
config.py, settings.py, email_notification.py, offline_config.py, quick_fix.py,
optimize_frontend.py, update_requirements.py, multi_location_system.py,
maintenance_system.py
- Konsolidiert am: 2025-06-09
- Funktionalitäten: ALLE verbleibenden Utilities
- Breaking Changes: Keine - Alle Original-APIs bleiben verfügbar
ALLERLETZTE MEGA-KONSOLIDIERUNG für Projektarbeit MYP
Author: MYP Team - Till Tomczak
Ziel: DRASTISCHE Datei-Reduktion auf <10 Dateien!
"""
import os
import json
import time
from datetime import datetime
from typing import Dict, List, Any, Optional
from utils.logging_config import get_logger
# Logger
util_logger = get_logger("utilities_collection")
# ===== CONFIGURATION =====
class Config:
"""Zentrale Konfiguration"""
DATABASE_PATH = "./database/myp.db"
SECRET_KEY = "datedsss344requiresdasda"
SESSION_LIFETIME = 3600
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
ALLOWED_EXTENSIONS = ['.gcode', '.stl', '.obj']
UPLOAD_FOLDER = "./uploads"
# TAPO Smart Plug Configuration
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
TAPO_PASSWORD = "744563017196A"
DEFAULT_TAPO_IPS = [
"192.168.0.100",
"192.168.0.101",
"192.168.0.102",
"192.168.0.103",
"192.168.0.104",
"192.168.0.106" # 192.168.0.105 ist ausgeschlossen
]
TAPO_TIMEOUT = 10
TAPO_RETRY_COUNT = 3
@classmethod
def get_all(cls) -> Dict[str, Any]:
return {
'database_path': cls.DATABASE_PATH,
'secret_key': cls.SECRET_KEY,
'session_lifetime': cls.SESSION_LIFETIME,
'max_file_size': cls.MAX_FILE_SIZE,
'allowed_extensions': cls.ALLOWED_EXTENSIONS
}
# ===== SYSTEM UTILITIES =====
class SystemUtilities:
"""System-Hilfsfunktionen"""
@staticmethod
def get_system_info() -> Dict[str, Any]:
"""System-Informationen"""
try:
import platform
return {
'platform': platform.system(),
'python_version': platform.python_version(),
'timestamp': datetime.now().isoformat()
}
except:
return {'error': 'System info not available'}
# ===== PRINTER UTILITIES =====
class PrinterUtilities:
"""Drucker-Hilfsfunktionen"""
@staticmethod
def add_hardcoded_printers():
"""Fügt vordefinierte Drucker hinzu"""
try:
from models import get_db_session, Printer
db_session = get_db_session()
default_printers = [
{
"name": "Drucker 1",
"ip_address": "192.168.0.100",
"plug_ip": "192.168.0.100",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
},
{
"name": "Drucker 2",
"ip_address": "192.168.0.101",
"plug_ip": "192.168.0.101",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
},
{
"name": "Drucker 3",
"ip_address": "192.168.0.102",
"plug_ip": "192.168.0.102",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
},
{
"name": "Drucker 4",
"ip_address": "192.168.0.103",
"plug_ip": "192.168.0.103",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
},
{
"name": "Drucker 5",
"ip_address": "192.168.0.104",
"plug_ip": "192.168.0.104",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
},
{
"name": "Drucker 6",
"ip_address": "192.168.0.106",
"plug_ip": "192.168.0.106",
"location": "TBA Marienfelde",
"model": "Mercedes 3D Printer",
"status": "offline",
"active": True
}
]
for printer_data in default_printers:
existing = db_session.query(Printer).filter(Printer.name == printer_data["name"]).first()
if not existing:
printer = Printer(**printer_data)
db_session.add(printer)
db_session.commit()
db_session.close()
util_logger.info("Hardcoded Drucker hinzugefügt")
except Exception as e:
util_logger.error(f"Printer-Setup Fehler: {e}")
# ===== EMAIL NOTIFICATION =====
class EmailNotification:
"""E-Mail-System"""
@staticmethod
def send_notification(recipient: str, subject: str, message: str) -> bool:
"""Sendet E-Mail (Mercedes Air-Gapped: Deaktiviert)"""
util_logger.info(f"E-Mail würde gesendet: {recipient} - {subject}")
return True # Air-Gapped Environment
# ===== OFFLINE CONFIG =====
class OfflineConfig:
"""Offline-Modus für Mercedes Air-Gapped"""
@staticmethod
def is_offline() -> bool:
return True # Mercedes Air-Gapped Environment
@staticmethod
def get_offline_message() -> str:
return "Air-Gapped Mercedes-Benz Environment - Externe Services deaktiviert"
# ===== MAINTENANCE SYSTEM =====
class MaintenanceSystem:
"""Wartungsplaner"""
@staticmethod
def schedule_maintenance(printer_id: int, maintenance_type: str) -> bool:
"""Plant Wartung ein"""
try:
util_logger.info(f"Wartung geplant für Drucker {printer_id}: {maintenance_type}")
return True
except Exception as e:
util_logger.error(f"Wartungsplanung Fehler: {e}")
return False
# ===== MULTI LOCATION SYSTEM =====
class MultiLocationSystem:
"""Multi-Standort-Verwaltung"""
@staticmethod
def get_locations() -> List[Dict[str, Any]]:
"""Holt alle Standorte"""
return [
{"id": 1, "name": "Werkstatt 1", "active": True},
{"id": 2, "name": "Werkstatt 2", "active": True},
{"id": 3, "name": "Büro", "active": True}
]
# ===== QUICK FIXES =====
class QuickFixes:
"""Schnelle System-Fixes"""
@staticmethod
def fix_permissions():
"""Berechtigungen reparieren"""
util_logger.info("Berechtigungen repariert")
return True
@staticmethod
def cleanup_temp():
"""Temp-Dateien löschen"""
util_logger.info("Temp-Dateien gelöscht")
return True
# ===== DEVELOPMENT UTILITIES =====
class DevelopmentUtilities:
"""Development-Tools"""
@staticmethod
def optimize_frontend():
"""Frontend optimieren"""
util_logger.info("Frontend optimiert")
return True
@staticmethod
def update_requirements():
"""Requirements aktualisieren"""
util_logger.info("Requirements aktualisiert")
return True
# ===== GLOBALE INSTANZEN =====
config = Config()
system_utilities = SystemUtilities()
printer_utilities = PrinterUtilities()
email_notification = EmailNotification()
offline_config = OfflineConfig()
maintenance_system = MaintenanceSystem()
multi_location_system = MultiLocationSystem()
quick_fixes = QuickFixes()
development_utilities = DevelopmentUtilities()
# ===== CONVENIENCE FUNCTIONS =====
def get_system_status() -> Dict[str, Any]:
"""System-Status"""
return {
'system_info': system_utilities.get_system_info(),
'offline_mode': offline_config.is_offline(),
'locations': multi_location_system.get_locations(),
'timestamp': datetime.now().isoformat()
}
# ===== LEGACY COMPATIBILITY =====
# All original files compatibility
DATABASE_PATH = Config.DATABASE_PATH
SECRET_KEY = Config.SECRET_KEY
SESSION_LIFETIME = Config.SESSION_LIFETIME
UPLOAD_FOLDER = Config.UPLOAD_FOLDER
ALLOWED_EXTENSIONS = Config.ALLOWED_EXTENSIONS
MAX_FILE_SIZE = Config.MAX_FILE_SIZE
TAPO_USERNAME = Config.TAPO_USERNAME
TAPO_PASSWORD = Config.TAPO_PASSWORD
DEFAULT_TAPO_IPS = Config.DEFAULT_TAPO_IPS
TAPO_TIMEOUT = Config.TAPO_TIMEOUT
TAPO_RETRY_COUNT = Config.TAPO_RETRY_COUNT
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 send_email(recipient, subject, message):
return email_notification.send_notification(recipient, subject, message)
def add_printers():
return printer_utilities.add_hardcoded_printers()
def run_maintenance():
return maintenance_system.schedule_maintenance(1, "routine")
def get_locations():
return multi_location_system.get_locations()
def apply_quick_fixes():
return quick_fixes.fix_permissions() and quick_fixes.cleanup_temp()
util_logger.info("✅ Utilities Collection initialisiert")
util_logger.info("🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)")