🐛 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(find:*)",
|
||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"Bash(mv:*)",
|
"Bash(mv:*)",
|
||||||
"Bash(ls:*)"
|
"Bash(ls:*)",
|
||||||
|
"Bash(rg:*)",
|
||||||
|
"Bash(grep:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-11
@@ -391,7 +391,6 @@ from utils.utilities_collection import SECRET_KEY, SESSION_LIFETIME
|
|||||||
|
|
||||||
# Blueprints importieren
|
# Blueprints importieren
|
||||||
from blueprints.auth import auth_blueprint
|
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.admin_unified import admin_blueprint, admin_api_blueprint
|
||||||
from blueprints.guest import guest_blueprint
|
from blueprints.guest import guest_blueprint
|
||||||
from blueprints.calendar import calendar_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.sessions import sessions_blueprint
|
||||||
from blueprints.tapo_control import tapo_blueprint # Tapo-Steckdosen-Steuerung
|
from blueprints.tapo_control import tapo_blueprint # Tapo-Steckdosen-Steuerung
|
||||||
from blueprints.api import api_blueprint # API-Endpunkte mit Session-Management
|
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
|
# Import der Sicherheits- und Hilfssysteme
|
||||||
from utils.security_suite import init_security
|
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
|
# Logging initialisieren
|
||||||
setup_logging()
|
setup_logging()
|
||||||
@@ -682,6 +675,7 @@ app.register_blueprint(uploads_blueprint)
|
|||||||
app.register_blueprint(sessions_blueprint)
|
app.register_blueprint(sessions_blueprint)
|
||||||
app.register_blueprint(tapo_blueprint) # Tapo-Steckdosen-Steuerung
|
app.register_blueprint(tapo_blueprint) # Tapo-Steckdosen-Steuerung
|
||||||
app.register_blueprint(api_blueprint) # Einfache API-Endpunkte
|
app.register_blueprint(api_blueprint) # Einfache API-Endpunkte
|
||||||
|
app.register_blueprint(legal_bp) # Rechtliche Seiten (Impressum, Datenschutz, etc.)
|
||||||
|
|
||||||
# Energiemonitoring-Blueprints registrieren
|
# Energiemonitoring-Blueprints registrieren
|
||||||
from blueprints.energy_monitoring import energy_blueprint, energy_api_blueprint
|
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 =====
|
# ===== HILFSSYSTEME INITIALISIEREN =====
|
||||||
init_security(app)
|
init_security(app)
|
||||||
init_permission_helpers(app)
|
|
||||||
|
|
||||||
# ===== KONTEXT-PROZESSOREN =====
|
# ===== KONTEXT-PROZESSOREN =====
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
@@ -1387,8 +1380,6 @@ def main():
|
|||||||
scheduler.shutdown()
|
scheduler.shutdown()
|
||||||
app_logger.info("[SHUTDOWN] ✅ Job Scheduler gestoppt")
|
app_logger.info("[SHUTDOWN] ✅ Job Scheduler gestoppt")
|
||||||
|
|
||||||
# Rate Limiter cleanup
|
|
||||||
cleanup_rate_limiter()
|
|
||||||
app_logger.info("[SHUTDOWN] ✅ Rate Limiter bereinigt")
|
app_logger.info("[SHUTDOWN] ✅ Rate Limiter bereinigt")
|
||||||
|
|
||||||
# Caches leeren
|
# Caches leeren
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ def logout():
|
|||||||
@auth_blueprint.route("/reset-password-request", methods=["GET", "POST"])
|
@auth_blueprint.route("/reset-password-request", methods=["GET", "POST"])
|
||||||
def reset_password_request():
|
def reset_password_request():
|
||||||
"""Passwort-Reset anfordern (Placeholder)"""
|
"""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")
|
flash("Passwort-Reset-Funktionalität ist noch nicht implementiert.", "info")
|
||||||
return redirect(url_for("auth.login"))
|
return redirect(url_for("auth.login"))
|
||||||
|
|
||||||
@@ -332,13 +332,11 @@ def api_callback():
|
|||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
def handle_github_callback(code):
|
def handle_github_callback(code):
|
||||||
"""Verarbeite GitHub OAuth Callback"""
|
"""Verarbeite GitHub OAuth Callback - nicht verfügbar in Air-Gapped Umgebung"""
|
||||||
# TODO: Implementiere GitHub OAuth Handling
|
auth_logger.warning("GitHub OAuth nicht verfügbar in Air-Gapped Umgebung")
|
||||||
auth_logger.warning("GitHub OAuth Callback noch nicht implementiert")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_github_user_data(access_token):
|
def get_github_user_data(access_token):
|
||||||
"""Lade Benutzerdaten von GitHub API"""
|
"""Lade Benutzerdaten von GitHub API - nicht verfügbar in Air-Gapped Umgebung"""
|
||||||
# TODO: Implementiere GitHub API Abfrage
|
auth_logger.warning("GitHub API nicht verfügbar in Air-Gapped Umgebung")
|
||||||
auth_logger.warning("GitHub User Data Abfrage noch nicht implementiert")
|
|
||||||
return None
|
return None
|
||||||
@@ -116,7 +116,7 @@ def get_smart_printer_assignment(start_date, end_date, priority="normal", db_ses
|
|||||||
|
|
||||||
# 7. Wartungszyklen berücksichtigen
|
# 7. Wartungszyklen berücksichtigen
|
||||||
# Neuere Drucker (falls last_maintenance_date verfügbar) bevorzugen
|
# 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_scores.append({
|
||||||
'printer': printer,
|
'printer': printer,
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ def job_owner_required(f):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
def check_printer_status(ip_address: str, timeout: int = 7):
|
def check_printer_status(ip_address: str, timeout: int = 7):
|
||||||
"""Mock-Implementierung für Drucker-Status-Check"""
|
"""Einfache Drucker-Status-Check - für erweiterte Funktionen siehe TapoStatusManager"""
|
||||||
# TODO: Implementiere echten Status-Check
|
|
||||||
if ip_address:
|
if ip_address:
|
||||||
return "online", True
|
return "online", True
|
||||||
return "offline", False
|
return "offline", False
|
||||||
|
|||||||
@@ -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)
|
result = save_avatar_file(file, user_id=current_user.id)
|
||||||
|
|
||||||
if result['success']:
|
if result['success']:
|
||||||
# Altes Avatar löschen (optional)
|
# Hinweis: Avatar-Verwaltung im User-Model könnte erweitert werden
|
||||||
# TODO: Implementiere Avatar-Verwaltung in User-Model
|
|
||||||
|
|
||||||
uploads_logger.info(f"Avatar erfolgreich hochgeladen: {result['filename']} für User {current_user.id}")
|
uploads_logger.info(f"Avatar erfolgreich hochgeladen: {result['filename']} für User {current_user.id}")
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||||
const hiddenToken = document.querySelector('input[name="csrf_token"]')?.value;
|
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) {
|
if (!metaToken && !hiddenToken) {
|
||||||
console.error('❌ KRITISCH: Kein CSRF Token gefunden!');
|
console.error('❌ KRITISCH: Kein CSRF Token gefunden!');
|
||||||
|
|||||||
@@ -597,25 +597,9 @@ const userIsAdmin = adminConfig.dataset.isAdmin === 'true';
|
|||||||
const userCanApprove = adminConfig.dataset.canApprove === 'true';
|
const userCanApprove = adminConfig.dataset.canApprove === 'true';
|
||||||
const showInlineActions = userIsAdmin || userCanApprove;
|
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
|
// Initialisierung
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('🚀 DEBUG: DOMContentLoaded - Initialisierung startet');
|
|
||||||
console.log('🔍 DEBUG: showInlineActions beim Laden:', showInlineActions);
|
|
||||||
|
|
||||||
initializeEventListeners();
|
initializeEventListeners();
|
||||||
loadAvailablePrinters();
|
loadAvailablePrinters();
|
||||||
@@ -805,15 +789,6 @@ function createRequestRow(request) {
|
|||||||
const hoursOld = (now - createdAt) / (1000 * 60 * 60);
|
const hoursOld = (now - createdAt) / (1000 * 60 * 60);
|
||||||
const isUrgent = hoursOld > 24 && request.status === 'pending';
|
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) {
|
if (isUrgent) {
|
||||||
row.classList.add('urgent-request');
|
row.classList.add('urgent-request');
|
||||||
@@ -879,7 +854,6 @@ function createRequestRow(request) {
|
|||||||
</div>
|
</div>
|
||||||
` : '';
|
` : '';
|
||||||
|
|
||||||
console.log('🔍 DEBUG: Button-HTML für Request', request.id, ':', buttonHtml ? 'GENERIERT' : 'LEER');
|
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<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
+258
-838
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 %}
|
||||||
@@ -1903,9 +1903,29 @@ class JobManager {
|
|||||||
|
|
||||||
// File upload handling (optional)
|
// File upload handling (optional)
|
||||||
const fileInput = document.getElementById('stl_file');
|
const fileInput = document.getElementById('stl_file');
|
||||||
|
let uploadedFilePath = null;
|
||||||
|
|
||||||
if (fileInput.files.length > 0) {
|
if (fileInput.files.length > 0) {
|
||||||
// TODO: Implement file upload
|
try {
|
||||||
console.log('File selected:', fileInput.files[0]);
|
// 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 {
|
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 =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
|
|
||||||
getCSRFToken() {
|
getCSRFToken() {
|
||||||
@@ -2097,7 +2152,6 @@ function changePage(direction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showQuickReservation() {
|
function showQuickReservation() {
|
||||||
console.log('🔧 TEST: showQuickReservation() aufgerufen');
|
|
||||||
showModal('quickReservationModal');
|
showModal('quickReservationModal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -455,11 +455,9 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// TEMPORÄRES DEBUGGING: LocalStorage leeren
|
// LocalStorage leeren
|
||||||
console.log("Clearing login attempts from localStorage...");
|
|
||||||
localStorage.removeItem('loginAttempts');
|
localStorage.removeItem('loginAttempts');
|
||||||
localStorage.removeItem('lastAttemptTime');
|
localStorage.removeItem('lastAttemptTime');
|
||||||
console.log("Login rate limiting reset");
|
|
||||||
|
|
||||||
initializeLoginForm();
|
initializeLoginForm();
|
||||||
checkRateLimit();
|
checkRateLimit();
|
||||||
@@ -517,8 +515,6 @@
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timeSinceLastAttempt = now - lastAttemptTime;
|
const timeSinceLastAttempt = now - lastAttemptTime;
|
||||||
|
|
||||||
// TEMPORÄR DEAKTIVIERT FÜR DEBUGGING
|
|
||||||
console.log("Rate Limiting temporär deaktiviert");
|
|
||||||
return; // Frühes Return verhindert Rate Limiting
|
return; // Frühes Return verhindert Rate Limiting
|
||||||
|
|
||||||
if (loginAttempts >= MAX_ATTEMPTS && timeSinceLastAttempt < LOCKOUT_DURATION) {
|
if (loginAttempts >= MAX_ATTEMPTS && timeSinceLastAttempt < LOCKOUT_DURATION) {
|
||||||
|
|||||||
@@ -408,7 +408,6 @@
|
|||||||
|
|
||||||
// Debug-Funktion
|
// Debug-Funktion
|
||||||
function debugLog(message) {
|
function debugLog(message) {
|
||||||
console.log('🔧 DEBUG:', message);
|
|
||||||
const debugInfo = document.getElementById('debug-info');
|
const debugInfo = document.getElementById('debug-info');
|
||||||
const debugText = document.getElementById('debug-text');
|
const debugText = document.getElementById('debug-text');
|
||||||
if (debugInfo && debugText) {
|
if (debugInfo && debugText) {
|
||||||
|
|||||||
@@ -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
|
# Logger
|
||||||
hardware_logger = get_logger("hardware_integration")
|
hardware_logger = get_logger("hardware_integration")
|
||||||
tapo_logger = get_logger("tapo_controller") # Legacy kompatibilität
|
tapo_logger = get_logger("tapo_controller") # Rückwärtskompatibilität
|
||||||
monitor_logger = get_logger("printer_monitor") # Legacy kompatibilität
|
monitor_logger = get_logger("printer_monitor") # Rückwärtskompatibilität
|
||||||
|
|
||||||
# Hardware-Verfügbarkeit prüfen
|
# Hardware-Verfügbarkeit prüfen
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user