🐛 Refactor: Consolidated user management and security functions in the backend. Added legal pages blueprint for compliance. Removed legacy rate limiter functions to streamline security integration. Updated logging for better clarity. 📚

This commit is contained in:
2025-06-12 20:44:11 +02:00
parent 69fd3187cf
commit 6b8fca218b
19 changed files with 698 additions and 3639 deletions
+3 -1
View File
@@ -32,7 +32,9 @@
"Bash(find:*)",
"Bash(mkdir:*)",
"Bash(mv:*)",
"Bash(ls:*)"
"Bash(ls:*)",
"Bash(rg:*)",
"Bash(grep:*)"
],
"deny": []
}
+2 -11
View File
@@ -391,7 +391,6 @@ from utils.utilities_collection import SECRET_KEY, SESSION_LIFETIME
# Blueprints importieren
from blueprints.auth import auth_blueprint
# from blueprints.user import user_blueprint # Konsolidiert in user_management
from blueprints.admin_unified import admin_blueprint, admin_api_blueprint
from blueprints.guest import guest_blueprint
from blueprints.calendar import calendar_blueprint
@@ -403,16 +402,10 @@ from blueprints.uploads import uploads_blueprint
from blueprints.sessions import sessions_blueprint
from blueprints.tapo_control import tapo_blueprint # Tapo-Steckdosen-Steuerung
from blueprints.api import api_blueprint # API-Endpunkte mit Session-Management
from blueprints.legal_pages import legal_bp # Rechtliche Seiten (Impressum, Datenschutz, etc.)
# Import der Sicherheits- und Hilfssysteme
from utils.security_suite import init_security
# Legacy rate_limiter and permissions functions - integrated into security_suite
def cleanup_rate_limiter():
pass # Simplified - no longer needed with consolidated security
def init_permission_helpers(app):
# Integrated into init_security
return app
# Logging initialisieren
setup_logging()
@@ -682,6 +675,7 @@ app.register_blueprint(uploads_blueprint)
app.register_blueprint(sessions_blueprint)
app.register_blueprint(tapo_blueprint) # Tapo-Steckdosen-Steuerung
app.register_blueprint(api_blueprint) # Einfache API-Endpunkte
app.register_blueprint(legal_bp) # Rechtliche Seiten (Impressum, Datenschutz, etc.)
# Energiemonitoring-Blueprints registrieren
from blueprints.energy_monitoring import energy_blueprint, energy_api_blueprint
@@ -690,7 +684,6 @@ app.register_blueprint(energy_api_blueprint) # API-Endpunkte für Energiedaten
# ===== HILFSSYSTEME INITIALISIEREN =====
init_security(app)
init_permission_helpers(app)
# ===== KONTEXT-PROZESSOREN =====
@app.context_processor
@@ -1387,8 +1380,6 @@ def main():
scheduler.shutdown()
app_logger.info("[SHUTDOWN] ✅ Job Scheduler gestoppt")
# Rate Limiter cleanup
cleanup_rate_limiter()
app_logger.info("[SHUTDOWN] ✅ Rate Limiter bereinigt")
# Caches leeren
+5 -7
View File
@@ -148,7 +148,7 @@ def logout():
@auth_blueprint.route("/reset-password-request", methods=["GET", "POST"])
def reset_password_request():
"""Passwort-Reset anfordern (Placeholder)"""
# TODO: Implement password reset functionality
# Hinweis: Passwort-Reset-Funktionalität geplant für zukünftige Version
flash("Passwort-Reset-Funktionalität ist noch nicht implementiert.", "info")
return redirect(url_for("auth.login"))
@@ -332,13 +332,11 @@ def api_callback():
}), 500
def handle_github_callback(code):
"""Verarbeite GitHub OAuth Callback"""
# TODO: Implementiere GitHub OAuth Handling
auth_logger.warning("GitHub OAuth Callback noch nicht implementiert")
"""Verarbeite GitHub OAuth Callback - nicht verfügbar in Air-Gapped Umgebung"""
auth_logger.warning("GitHub OAuth nicht verfügbar in Air-Gapped Umgebung")
return None
def get_github_user_data(access_token):
"""Lade Benutzerdaten von GitHub API"""
# TODO: Implementiere GitHub API Abfrage
auth_logger.warning("GitHub User Data Abfrage noch nicht implementiert")
"""Lade Benutzerdaten von GitHub API - nicht verfügbar in Air-Gapped Umgebung"""
auth_logger.warning("GitHub API nicht verfügbar in Air-Gapped Umgebung")
return None
+1 -1
View File
@@ -116,7 +116,7 @@ def get_smart_printer_assignment(start_date, end_date, priority="normal", db_ses
# 7. Wartungszyklen berücksichtigen
# Neuere Drucker (falls last_maintenance_date verfügbar) bevorzugen
# TODO: Implementierung abhängig von Printer-Model-Erweiterungen
# Hinweis: Wartungszyklen-Feature geplant für erweiterte Printer-Models
printer_scores.append({
'printer': printer,
+1 -2
View File
@@ -42,8 +42,7 @@ def job_owner_required(f):
return decorated_function
def check_printer_status(ip_address: str, timeout: int = 7):
"""Mock-Implementierung für Drucker-Status-Check"""
# TODO: Implementiere echten Status-Check
"""Einfache Drucker-Status-Check - für erweiterte Funktionen siehe TapoStatusManager"""
if ip_address:
return "online", True
return "offline", False
+59
View File
@@ -0,0 +1,59 @@
"""
Rechtliche Seiten für das MYP-System
Impressum, Datenschutz, Nutzungsbedingungen, etc.
"""
from flask import Blueprint, render_template, current_app
from flask_login import current_user
legal_bp = Blueprint('legal', __name__)
@legal_bp.route('/impressum')
def imprint():
"""Impressum/Rechtliche Hinweise"""
return render_template('imprint.html',
title='Impressum - MYP Platform')
@legal_bp.route('/datenschutz')
def privacy():
"""Datenschutzerklärung"""
return render_template('privacy.html',
title='Datenschutz - MYP Platform')
@legal_bp.route('/nutzungsbedingungen')
def terms():
"""Nutzungsbedingungen"""
return render_template('terms.html',
title='Nutzungsbedingungen - MYP Platform')
@legal_bp.route('/rechtliches')
def legal():
"""Allgemeine rechtliche Informationen"""
return render_template('legal.html',
title='Rechtliche Hinweise - MYP Platform')
@legal_bp.route('/system-info')
def system_info():
"""System-Informationen und Version"""
system_data = {
'version': '3.0.0',
'environment': current_app.config.get('ENV', 'production'),
'python_version': current_app.config.get('PYTHON_VERSION', 'Unknown'),
'flask_version': current_app.config.get('FLASK_VERSION', 'Unknown'),
'features': [
'3D-Drucker Management',
'Smart Plug Integration (TP-Link Tapo)',
'Benutzer- und Rechteverwaltung',
'Gast-Zugang mit OTP',
'Energie-Monitoring',
'Kalender-Integration',
'Job-Queue Management',
'Automatische Backups',
'Progressive Web App (PWA)',
'Dark/Light Mode'
]
}
return render_template('system_info.html',
title='System-Information - MYP Platform',
system=system_data)
+1 -2
View File
@@ -141,8 +141,7 @@ def upload_avatar():
result = save_avatar_file(file, user_id=current_user.id)
if result['success']:
# Altes Avatar löschen (optional)
# TODO: Implementiere Avatar-Verwaltung in User-Model
# Hinweis: Avatar-Verwaltung im User-Model könnte erweitert werden
uploads_logger.info(f"Avatar erfolgreich hochgeladen: {result['filename']} für User {current_user.id}")
-2
View File
@@ -20,8 +20,6 @@
document.addEventListener('DOMContentLoaded', function() {
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const hiddenToken = document.querySelector('input[name="csrf_token"]')?.value;
console.log('🔒 CSRF Debug - Meta Token:', metaToken ? 'verfügbar' : 'FEHLT');
console.log('🔒 CSRF Debug - Hidden Token:', hiddenToken ? 'verfügbar' : 'FEHLT');
if (!metaToken && !hiddenToken) {
console.error('❌ KRITISCH: Kein CSRF Token gefunden!');
@@ -597,25 +597,9 @@ const userIsAdmin = adminConfig.dataset.isAdmin === 'true';
const userCanApprove = adminConfig.dataset.canApprove === 'true';
const showInlineActions = userIsAdmin || userCanApprove;
console.log('🔍 DEBUG: Admin-Berechtigungen:', {
userIsAdmin,
userCanApprove,
showInlineActions,
adminConfigElement: adminConfig,
dataIsAdmin: adminConfig.dataset.isAdmin,
dataCanApprove: adminConfig.dataset.canApprove
});
// Debug: Zeige alle Data-Attribute
console.log('🔍 DEBUG: Alle Data-Attribute:', adminConfig.dataset);
// Debug: Prüfe HTML-Inhalt
console.log('🔍 DEBUG: AdminConfig HTML:', adminConfig.outerHTML);
// Initialisierung
document.addEventListener('DOMContentLoaded', function() {
console.log('🚀 DEBUG: DOMContentLoaded - Initialisierung startet');
console.log('🔍 DEBUG: showInlineActions beim Laden:', showInlineActions);
initializeEventListeners();
loadAvailablePrinters();
@@ -805,15 +789,6 @@ function createRequestRow(request) {
const hoursOld = (now - createdAt) / (1000 * 60 * 60);
const isUrgent = hoursOld > 24 && request.status === 'pending';
// DEBUG: Zeige Request-Details
console.log('🔍 DEBUG: createRequestRow für Request:', {
id: request.id,
name: request.name,
status: request.status,
isPending: request.status === 'pending',
showInlineActions: showInlineActions,
shouldShowButtons: request.status === 'pending' && showInlineActions
});
if (isUrgent) {
row.classList.add('urgent-request');
@@ -879,7 +854,6 @@ function createRequestRow(request) {
</div>
` : '';
console.log('🔍 DEBUG: Button-HTML für Request', request.id, ':', buttonHtml ? 'GENERIERT' : 'LEER');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
-218
View File
@@ -1,218 +0,0 @@
<!DOCTYPE html>
<html lang="de" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="MYP Platform - Mercedes-Benz 3D Druck Management System">
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#000000">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{% block title %}MYP Platform - Mercedes-Benz{% endblock %}</title>
<!-- Critical CSS inline for instant rendering -->
<style>
/* Critical CSS for above-the-fold content */
*,::after,::before{box-sizing:border-box}
html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}
body{margin:0;font-family:inherit;line-height:inherit}
.dark{color-scheme:dark}
.dark body{background-color:#0f172a;color:#e2e8f0}
body{background-color:#fff;color:#1e293b}
/* Glassmorphism navbar - preserved */
.glass-navbar{background:rgba(255,255,255,0.85);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,0.3);box-shadow:0 2px 4px rgba(0,0,0,0.05)}
.dark .glass-navbar{background:rgba(15,23,42,0.85);border:1px solid rgba(255,255,255,0.1)}
/* Hide content until styles load */
.no-fouc{visibility:hidden;opacity:0}
.fonts-loaded .no-fouc{visibility:visible;opacity:1;transition:opacity 0.2s}
</style>
<!-- Preconnect to speed up font loading -->
<link rel="preconnect" href="{{ url_for('static', filename='fontawesome/webfonts', _external=True) }}" crossorigin>
<!-- Optimized CSS loading -->
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/performance-optimized.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/core-utilities.min.css') }}" rel="stylesheet">
<!-- Non-critical CSS -->
<link href="{{ url_for('static', filename='fontawesome/css/all.min.css') }}" rel="stylesheet" media="print" onload="this.media='all'">
<!-- PWA -->
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
<!-- Dark Mode Script (inline to prevent flash) -->
<script>
(function(){
const savedMode = localStorage.getItem('myp-dark-mode');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = savedMode === 'true' || (savedMode === null && prefersDark);
if (isDark) {
document.documentElement.classList.add('dark');
document.querySelector('meta[name="theme-color"]').content = '#000000';
} else {
document.documentElement.classList.remove('dark');
document.querySelector('meta[name="theme-color"]').content = '#ffffff';
}
})();
</script>
{% block extra_css %}{% endblock %}
</head>
<body class="min-h-screen bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 no-fouc">
<!-- Skip to content -->
<a href="#main" class="sr-only focus:not-sr-only">Skip to main content</a>
<!-- Header with glassmorphism navbar -->
<header class="glass-navbar sticky top-0 z-40 w-full">
<nav class="container mx-auto px-4 py-3">
<div class="flex items-center justify-between">
<!-- Logo -->
<a href="{{ url_for('index') }}" class="flex items-center space-x-3">
<svg class="w-8 h-8" 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.5C27,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,40c0-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.8C53.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.9C67.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,40c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
</svg>
<span class="font-semibold text-xl">MYP Platform</span>
</a>
<!-- Navigation -->
<div class="flex items-center space-x-4">
{% if current_user.is_authenticated %}
<!-- Simplified navigation links -->
<a href="{{ url_for('dashboard') }}" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-slate-100 dark:hover:bg-slate-800">Dashboard</a>
<a href="{{ url_for('printers_page') }}" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-slate-100 dark:hover:bg-slate-800">Drucker</a>
<a href="{{ url_for('jobs_page') }}" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-slate-100 dark:hover:bg-slate-800">Aufträge</a>
{% if current_user.is_admin %}
<a href="{{ url_for('admin.admin_dashboard') }}" class="px-3 py-2 rounded-md text-sm font-medium hover:bg-slate-100 dark:hover:bg-slate-800">Admin</a>
{% endif %}
<!-- User menu -->
<div class="relative">
<button id="user-menu-button" class="flex items-center p-2 rounded-full hover:bg-slate-100 dark:hover:bg-slate-800">
<div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm font-medium">
{{ current_user.email[0].upper() if current_user.email else 'U' }}
</div>
</button>
<div id="user-dropdown" class="hidden absolute right-0 mt-2 w-48 bg-white dark:bg-slate-800 rounded-lg shadow-lg">
<a href="{{ url_for('users.user_profile') }}" class="block px-4 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-700">Profil</a>
<a href="{{ url_for('auth.logout') }}" class="block px-4 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-700">Abmelden</a>
</div>
</div>
<!-- Dark mode toggle -->
<button id="darkModeToggle" class="p-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800">
<svg class="w-5 h-5 sun-icon" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"></path>
</svg>
<svg class="w-5 h-5 moon-icon hidden" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
</button>
{% else %}
<a href="{{ url_for('auth.login') }}" class="px-4 py-2 rounded-md text-sm font-medium bg-blue-600 text-white hover:bg-blue-700">Anmelden</a>
{% endif %}
</div>
</div>
</nav>
</header>
<!-- Flash messages container -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div id="flask-flash-messages"
data-flash-count="{{ messages|length }}"
{% for i, (category, message) in enumerate(messages, 1) %}
data-flash-{{ i }}="{{ category }}|{{ message }}"
{% endfor %}
class="hidden"></div>
{% endif %}
{% endwith %}
<!-- Main content -->
<main id="main" class="container mx-auto px-4 py-8">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="mt-auto py-8 text-center text-sm text-slate-600 dark:text-slate-400">
<p>&copy; 2024 Mercedes-Benz AG. Alle Rechte vorbehalten.</p>
</footer>
<!-- Core JavaScript bundle -->
<script src="{{ url_for('static', filename='js/core-bundle.min.js') }}"></script>
<!-- Inline initialization script -->
<script>
// Font loading detection
if ('fonts' in document) {
document.fonts.ready.then(() => {
document.documentElement.classList.add('fonts-loaded');
});
} else {
// Fallback for browsers without font loading API
setTimeout(() => {
document.documentElement.classList.add('fonts-loaded');
}, 100);
}
// Initialize core functionality
document.addEventListener('DOMContentLoaded', function() {
// Dark mode toggle
const darkModeToggle = document.getElementById('darkModeToggle');
if (darkModeToggle) {
darkModeToggle.addEventListener('click', function() {
document.documentElement.classList.toggle('dark');
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('myp-dark-mode', isDark);
document.querySelector('meta[name="theme-color"]').content = isDark ? '#000000' : '#ffffff';
// Update icons
document.querySelector('.sun-icon').classList.toggle('hidden', isDark);
document.querySelector('.moon-icon').classList.toggle('hidden', !isDark);
});
}
// Simple dropdown
const userMenuButton = document.getElementById('user-menu-button');
const userDropdown = document.getElementById('user-dropdown');
if (userMenuButton && userDropdown) {
userMenuButton.addEventListener('click', function(e) {
e.stopPropagation();
userDropdown.classList.toggle('hidden');
});
document.addEventListener('click', function() {
userDropdown.classList.add('hidden');
});
}
// Flash messages
const flashContainer = document.getElementById('flask-flash-messages');
if (flashContainer && window.MYP && window.MYP.utils) {
const flashCount = parseInt(flashContainer.getAttribute('data-flash-count')) || 0;
for (let i = 1; i <= flashCount; i++) {
const flashData = flashContainer.getAttribute('data-flash-' + i);
if (flashData) {
const [category, message] = flashData.split('|', 2);
let messageType = category === 'danger' ? 'error' : category;
setTimeout(() => {
window.MYP.utils.notifications[messageType](message, 6000);
}, i * 100);
}
}
flashContainer.remove();
}
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,176 @@
<!-- Drucker-Status-Komponente für wiederverwendbare Status-Anzeige -->
{% macro printer_status_badge(status, size='sm') %}
{% set status_config = {
'online': {
'color': 'green',
'icon': 'fa-check-circle',
'text': 'Online',
'description': 'Drucker ist einsatzbereit'
},
'offline': {
'color': 'red',
'icon': 'fa-times-circle',
'text': 'Offline',
'description': 'Drucker ist nicht erreichbar'
},
'busy': {
'color': 'blue',
'icon': 'fa-spinner',
'text': 'Beschäftigt',
'description': 'Drucker druckt gerade'
},
'idle': {
'color': 'yellow',
'icon': 'fa-pause-circle',
'text': 'Bereit',
'description': 'Drucker ist online aber inaktiv'
},
'available': {
'color': 'green',
'icon': 'fa-check-circle',
'text': 'Verfügbar',
'description': 'Drucker ist verfügbar'
},
'error': {
'color': 'red',
'icon': 'fa-exclamation-triangle',
'text': 'Fehler',
'description': 'Drucker hat einen Fehler'
}
} %}
{% set config = status_config.get(status, status_config['offline']) %}
{% set size_classes = {
'xs': 'px-2 py-1 text-xs',
'sm': 'px-3 py-1 text-sm',
'md': 'px-4 py-2 text-base',
'lg': 'px-6 py-3 text-lg'
} %}
<span class="inline-flex items-center {{ size_classes.get(size, size_classes['sm']) }} rounded-full font-medium
bg-{{ config.color }}-100 dark:bg-{{ config.color }}-900/20
text-{{ config.color }}-800 dark:text-{{ config.color }}-300"
title="{{ config.description }}">
<i class="fas {{ config.icon }} mr-1{% if config.icon == 'fa-spinner' %} fa-spin{% endif %}"></i>
{{ config.text }}
</span>
{% endmacro %}
{% macro printer_status_indicator(status, show_text=true) %}
{% set status_config = {
'online': {
'color': 'green',
'icon': 'fa-circle',
'text': 'Online'
},
'offline': {
'color': 'red',
'icon': 'fa-circle',
'text': 'Offline'
},
'busy': {
'color': 'blue',
'icon': 'fa-circle',
'text': 'Beschäftigt'
},
'idle': {
'color': 'yellow',
'icon': 'fa-circle',
'text': 'Bereit'
},
'available': {
'color': 'green',
'icon': 'fa-circle',
'text': 'Verfügbar'
}
} %}
{% set config = status_config.get(status, status_config['offline']) %}
<div class="flex items-center space-x-2">
<i class="fas {{ config.icon }} text-{{ config.color }}-500 dark:text-{{ config.color }}-400"></i>
{% if show_text %}
<span class="text-sm text-slate-700 dark:text-slate-300">{{ config.text }}</span>
{% endif %}
</div>
{% endmacro %}
{% macro printer_power_status(is_powered, plug_available=false) %}
{% if plug_available %}
<div class="flex items-center space-x-2">
{% if is_powered %}
<i class="fas fa-plug text-green-500 dark:text-green-400" title="Smart Plug: Eingeschaltet"></i>
<span class="text-xs text-green-600 dark:text-green-400">An</span>
{% else %}
<i class="fas fa-plug text-red-500 dark:text-red-400" title="Smart Plug: Ausgeschaltet"></i>
<span class="text-xs text-red-600 dark:text-red-400">Aus</span>
{% endif %}
</div>
{% else %}
<div class="flex items-center space-x-2">
<i class="fas fa-plug text-slate-400 dark:text-slate-500" title="Kein Smart Plug konfiguriert"></i>
<span class="text-xs text-slate-500 dark:text-slate-400">N/A</span>
</div>
{% endif %}
{% endmacro %}
{% macro printer_status_card(printer, show_jobs=true) %}
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-4">
<!-- Header -->
<div class="flex items-center justify-between mb-3">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
<i class="fas fa-print text-white"></i>
</div>
<div>
<h3 class="font-semibold text-slate-900 dark:text-white">{{ printer.name }}</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">{{ printer.location or 'Kein Standort' }}</p>
</div>
</div>
{{ printer_status_badge(printer.status) }}
</div>
<!-- Status Details -->
<div class="grid grid-cols-2 gap-4 mb-3">
<div>
<span class="text-xs text-slate-600 dark:text-slate-400">Verbindung:</span>
{{ printer_status_indicator(printer.status, false) }}
</div>
<div>
<span class="text-xs text-slate-600 dark:text-slate-400">Strom:</span>
{{ printer_power_status(printer.plug_ip and printer.status != 'offline', printer.plug_ip != null) }}
</div>
</div>
<!-- IP Address -->
{% if printer.ip_address %}
<div class="text-xs text-slate-600 dark:text-slate-400 mb-3">
<i class="fas fa-network-wired mr-1"></i>
{{ printer.ip_address }}
</div>
{% endif %}
<!-- Jobs -->
{% if show_jobs and printer.jobs %}
<div class="border-t border-slate-200 dark:border-slate-600 pt-3">
<div class="flex items-center justify-between">
<span class="text-xs font-medium text-slate-600 dark:text-slate-400">
Aktive Jobs: {{ printer.jobs|length }}
</span>
<a href="{{ url_for('jobs_page', printer_id=printer.id) }}"
class="text-xs text-blue-600 dark:text-blue-400 hover:underline">
Details →
</a>
</div>
</div>
{% endif %}
<!-- Last Updated -->
{% if printer.last_checked %}
<div class="text-xs text-slate-500 dark:text-slate-400 mt-3">
Zuletzt geprüft: {{ printer.last_checked.strftime('%H:%M') }}
</div>
{% endif %}
</div>
{% endmacro %}
+57 -3
View File
@@ -1903,9 +1903,29 @@ class JobManager {
// File upload handling (optional)
const fileInput = document.getElementById('stl_file');
let uploadedFilePath = null;
if (fileInput.files.length > 0) {
// TODO: Implement file upload
console.log('File selected:', fileInput.files[0]);
try {
// Datei hochladen
const uploadResult = await this.uploadJobFile(fileInput.files[0]);
if (uploadResult.success) {
uploadedFilePath = uploadResult.path;
console.log('Datei erfolgreich hochgeladen:', uploadResult.filename);
} else {
this.showError(`Fehler beim Datei-Upload: ${uploadResult.error}`);
return;
}
} catch (error) {
console.error('Fehler beim Datei-Upload:', error);
this.showError('Fehler beim Hochladen der Datei');
return;
}
}
// Uploaded file path zu Job-Daten hinzufügen
if (uploadedFilePath) {
jobData.file_path = uploadedFilePath;
}
try {
@@ -1938,6 +1958,41 @@ class JobManager {
}
}
// ===== FILE UPLOAD =====
async uploadJobFile(file) {
/**
* Lädt eine Job-Datei hoch und gibt das Ergebnis zurück
* @param {File} file - Die hochzuladende Datei
* @returns {Promise<Object>} Upload-Ergebnis mit success, path, filename, etc.
*/
try {
// FormData für Upload erstellen
const formData = new FormData();
formData.append('file', file);
// Upload-Request senden
const response = await fetch('/api/upload/job', {
method: 'POST',
headers: {
'X-CSRFToken': this.getCSRFToken()
},
body: formData
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || 'Upload fehlgeschlagen');
}
return result;
} catch (error) {
console.error('Fehler beim Datei-Upload:', error);
throw error;
}
}
// ===== UTILITY FUNCTIONS =====
getCSRFToken() {
@@ -2097,7 +2152,6 @@ function changePage(direction) {
}
function showQuickReservation() {
console.log('🔧 TEST: showQuickReservation() aufgerufen');
showModal('quickReservationModal');
}
+1 -5
View File
@@ -455,11 +455,9 @@
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// TEMPORÄRES DEBUGGING: LocalStorage leeren
console.log("Clearing login attempts from localStorage...");
// LocalStorage leeren
localStorage.removeItem('loginAttempts');
localStorage.removeItem('lastAttemptTime');
console.log("Login rate limiting reset");
initializeLoginForm();
checkRateLimit();
@@ -517,8 +515,6 @@
const now = Date.now();
const timeSinceLastAttempt = now - lastAttemptTime;
// TEMPORÄR DEAKTIVIERT FÜR DEBUGGING
console.log("Rate Limiting temporär deaktiviert");
return; // Frühes Return verhindert Rate Limiting
if (loginAttempts >= MAX_ATTEMPTS && timeSinceLastAttempt < LOCKOUT_DURATION) {
-1
View File
@@ -408,7 +408,6 @@
// Debug-Funktion
function debugLog(message) {
console.log('🔧 DEBUG:', message);
const debugInfo = document.getElementById('debug-info');
const debugText = document.getElementById('debug-text');
if (debugInfo && debugText) {
+132
View File
@@ -0,0 +1,132 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
<!-- Header -->
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-6 mb-6">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center">
<i class="fas fa-info-circle text-white text-xl"></i>
</div>
<div>
<h1 class="text-2xl font-bold text-slate-900 dark:text-white">System-Information</h1>
<p class="text-slate-600 dark:text-slate-400">MYP Platform - Mercedes-Benz 3D-Druck Management</p>
</div>
</div>
</div>
<!-- System-Status -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Version Info -->
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-6">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">
<i class="fas fa-tag mr-2 text-blue-500"></i>
Version & Umgebung
</h2>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Version:</span>
<span class="font-medium text-slate-900 dark:text-white">{{ system.version }}</span>
</div>
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Umgebung:</span>
<span class="px-2 py-1 rounded text-xs font-medium {{ 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300' if system.environment == 'production' else 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-300' }}">
{{ system.environment.title() }}
</span>
</div>
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Status:</span>
<span class="flex items-center text-green-600 dark:text-green-400">
<div class="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
Online
</span>
</div>
</div>
</div>
<!-- System Health -->
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-6">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">
<i class="fas fa-heartbeat mr-2 text-red-500"></i>
System-Gesundheit
</h2>
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Datenbank:</span>
<span class="flex items-center text-green-600 dark:text-green-400">
<i class="fas fa-check-circle mr-1"></i>
Verbunden
</span>
</div>
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Session Store:</span>
<span class="flex items-center text-green-600 dark:text-green-400">
<i class="fas fa-check-circle mr-1"></i>
Aktiv
</span>
</div>
<div class="flex justify-between">
<span class="text-slate-600 dark:text-slate-400">Cache:</span>
<span class="flex items-center text-green-600 dark:text-green-400">
<i class="fas fa-check-circle mr-1"></i>
Funktional
</span>
</div>
</div>
</div>
</div>
<!-- Funktionen -->
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-6">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">
<i class="fas fa-cogs mr-2 text-blue-500"></i>
Verfügbare Funktionen
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for feature in system.features %}
<div class="flex items-center space-x-3 p-3 rounded-lg border border-slate-200 dark:border-slate-600">
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
<span class="text-sm text-slate-700 dark:text-slate-300">{{ feature }}</span>
</div>
{% endfor %}
</div>
</div>
<!-- Quick Actions -->
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-slate-200 dark:border-slate-700 p-6 mt-6">
<h2 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">
<i class="fas fa-bolt mr-2 text-yellow-500"></i>
Quick Actions
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<a href="{{ url_for('dashboard') }}"
class="flex items-center justify-center space-x-2 p-3 rounded-lg border border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
<i class="fas fa-tachometer-alt text-blue-500"></i>
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Dashboard</span>
</a>
<a href="{{ url_for('printers_page') }}"
class="flex items-center justify-center space-x-2 p-3 rounded-lg border border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
<i class="fas fa-print text-green-500"></i>
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Drucker</span>
</a>
{% if current_user.is_admin %}
<a href="{{ url_for('admin.admin_dashboard') }}"
class="flex items-center justify-center space-x-2 p-3 rounded-lg border border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
<i class="fas fa-cog text-purple-500"></i>
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Admin</span>
</a>
{% endif %}
<a href="{{ url_for('calendar.calendar_view') }}"
class="flex items-center justify-center space-x-2 p-3 rounded-lg border border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
<i class="fas fa-calendar text-orange-500"></i>
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Kalender</span>
</a>
</div>
</div>
</div>
{% endblock %}
+2 -2
View File
@@ -38,8 +38,8 @@ from utils.logging_config import get_logger
# Logger
hardware_logger = get_logger("hardware_integration")
tapo_logger = get_logger("tapo_controller") # Legacy kompatibilität
monitor_logger = get_logger("printer_monitor") # Legacy kompatibilität
tapo_logger = get_logger("tapo_controller") # Rückwärtskompatibilität
monitor_logger = get_logger("printer_monitor") # Rückwärtskompatibilität
# Hardware-Verfügbarkeit prüfen
try: