🐛 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:
@ -32,7 +32,9 @@
|
||||
"Bash(find:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(mv:*)",
|
||||
"Bash(ls:*)"
|
||||
"Bash(ls:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(grep:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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,
|
||||
|
@ -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
backend/blueprints/legal_pages.py
Normal file
59
backend/blueprints/legal_pages.py
Normal 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)
|
@ -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}")
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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>© 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
176
backend/templates/components/printer_status.html
Normal file
176
backend/templates/components/printer_status.html
Normal file
@ -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 %}
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
backend/templates/system_info.html
Normal file
132
backend/templates/system_info.html
Normal 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 %}
|
@ -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:
|
||||
|
Reference in New Issue
Block a user