🔧 Update: Enhanced error handling and logging across various modules

**Änderungen:**
-  app.py: Hinzugefügt, um CSRF-Fehler zu behandeln
-  models.py: Fehlerprotokollierung bei der Suche nach Gastanfragen per OTP
-  api.py: Fehlerprotokollierung beim Markieren von Benachrichtigungen als gelesen
-  calendar.py: Fallback-Daten zurückgeben, wenn keine Kalenderereignisse vorhanden sind
-  guest.py: Status-Check-Seite für Gäste aktualisiert
-  hardware_integration.py: Debugging-Informationen für erweiterte Geräteinformationen hinzugefügt
-  tapo_status_manager.py: Rückgabewert für Statusabfrage hinzugefügt

**Ergebnis:**
- Verbesserte Fehlerbehandlung und Protokollierung für eine robustere Anwendung
- Bessere Nachverfolgbarkeit von Fehlern und Systemverhalten

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-06-15 22:45:20 +02:00
parent 7e156099d5
commit 956c24d8ca
552 changed files with 11252 additions and 2424 deletions

View File

@ -4,9 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="description" content="MYP Platform - Mercedes-Benz 3D Druck Management System">
<meta name="author" content="Mercedes-Benz Group AG">
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#000000" id="theme-color">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta id="theme-color" name="theme-color" content="#ffffff">
<meta name="csrf-token" content="{{ csrf_token() if csrf_token else session.get('_csrf_token', '') }}">
<title>{% block title %}MYP - Mercedes-Benz{% endblock %}</title>
@ -20,6 +21,9 @@
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='fontawesome/css/all.min.css') }}" rel="stylesheet">
<!-- Unified Dark/Light Mode System -->
<link href="{{ url_for('static', filename='css/dark-light-unified.css') }}" rel="stylesheet">
<!-- Modern Styles with Glassmorphism -->
<style>
/* Root Variables */
@ -251,10 +255,30 @@
<div class="flex items-center space-x-2">
{% if current_user.is_authenticated %}
<!-- Notifications -->
<button class="p-2 rounded-lg hover:bg-white/10 dark:hover:bg-black/10 relative">
<i class="fas fa-bell"></i>
<span class="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full hidden"></span>
</button>
<div class="relative">
<button id="notificationToggle" class="p-2 rounded-lg hover:bg-white/10 dark:hover:bg-black/10 relative">
<i class="fas fa-bell"></i>
<span id="notificationBadge" class="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full hidden"></span>
</button>
<!-- Notification Dropdown -->
<div id="notificationDropdown" class="hidden absolute right-0 mt-2 w-80 glass rounded-xl overflow-hidden z-50">
<div class="p-4 border-b border-white/10">
<div class="flex items-center justify-between">
<h3 class="font-semibold">Benachrichtigungen</h3>
<button id="markAllRead" class="text-xs text-blue-500 hover:text-blue-400">
Alle als gelesen markieren
</button>
</div>
</div>
<div id="notificationList" class="max-h-96 overflow-y-auto">
<div class="p-4 text-center text-slate-500 dark:text-slate-400">
<i class="fas fa-bell-slash text-2xl mb-2"></i>
<p>Keine neuen Benachrichtigungen</p>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Dark Mode Toggle -->
@ -498,26 +522,304 @@
<!-- JavaScript -->
<script>
// Dark Mode Toggle
// Dark Mode Toggle - Vereinfachte Version ohne Konflikte
const darkModeToggle = document.getElementById('darkModeToggle');
const sunIcon = document.querySelector('.sun-icon');
const moonIcon = document.querySelector('.moon-icon');
const STORAGE_KEY = 'myp-dark-mode';
function updateDarkMode() {
function updateDarkModeIcons() {
const isDark = document.documentElement.classList.contains('dark');
sunIcon.classList.toggle('hidden', isDark);
moonIcon.classList.toggle('hidden', !isDark);
document.getElementById('theme-color').setAttribute('content', isDark ? '#1e293b' : '#ffffff');
if (sunIcon && moonIcon) {
sunIcon.classList.toggle('hidden', isDark);
moonIcon.classList.toggle('hidden', !isDark);
}
// Meta theme color aktualisieren
const themeColorMeta = document.getElementById('theme-color');
if (themeColorMeta) {
themeColorMeta.setAttribute('content', isDark ? '#1e293b' : '#ffffff');
}
}
function setDarkMode(isDark) {
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem(STORAGE_KEY, isDark.toString());
updateDarkModeIcons();
// Custom Event für andere Komponenten
window.dispatchEvent(new CustomEvent('darkModeChanged', {
detail: { isDark: isDark }
}));
console.log(`🎨 Theme gewechselt zu: ${isDark ? 'Dark Mode' : 'Light Mode'}`);
}
// Toggle Event Listener
darkModeToggle?.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('myp-dark-mode', isDark);
updateDarkMode();
const currentIsDark = document.documentElement.classList.contains('dark');
setDarkMode(!currentIsDark);
});
updateDarkMode();
// Initial setup
updateDarkModeIcons();
// Notification System
class NotificationManager {
constructor() {
this.notifications = [];
this.isOpen = false;
this.initializeElements();
this.setupEventListeners();
this.loadNotifications();
// Auto-refresh alle 30 Sekunden
setInterval(() => this.loadNotifications(), 30000);
}
initializeElements() {
this.toggle = document.getElementById('notificationToggle');
this.dropdown = document.getElementById('notificationDropdown');
this.badge = document.getElementById('notificationBadge');
this.list = document.getElementById('notificationList');
this.markAllRead = document.getElementById('markAllRead');
}
setupEventListeners() {
this.toggle?.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleDropdown();
});
this.markAllRead?.addEventListener('click', () => {
this.markAllAsRead();
});
// Schließen bei Klick außerhalb
document.addEventListener('click', (e) => {
if (!this.dropdown?.contains(e.target) && !this.toggle?.contains(e.target)) {
this.closeDropdown();
}
});
}
async loadNotifications() {
try {
const response = await fetch('/api/notifications');
const data = await response.json();
if (data.success) {
this.notifications = data.notifications || [];
this.updateUI();
}
} catch (error) {
console.error('Fehler beim Laden der Benachrichtigungen:', error);
}
}
updateUI() {
this.updateBadge();
this.updateList();
}
updateBadge() {
const unreadCount = this.notifications.filter(n => !n.is_read).length;
if (this.badge) {
this.badge.classList.toggle('hidden', unreadCount === 0);
}
}
updateList() {
if (!this.list) return;
if (this.notifications.length === 0) {
this.list.innerHTML = `
<div class="p-4 text-center text-slate-500 dark:text-slate-400">
<i class="fas fa-bell-slash text-2xl mb-2"></i>
<p>Keine neuen Benachrichtigungen</p>
</div>
`;
return;
}
const notificationHTML = this.notifications.map(notification => {
const isUnread = !notification.is_read;
const timeAgo = this.formatTimeAgo(new Date(notification.created_at));
return `
<div class="notification-item p-4 border-b border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors ${isUnread ? 'bg-blue-50 dark:bg-blue-900/20' : ''}"
data-notification-id="${notification.id}">
<div class="flex items-start space-x-3">
<div class="flex-shrink-0">
${this.getNotificationIcon(notification.type)}
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between">
<p class="text-sm font-medium text-slate-900 dark:text-white">
${notification.title || this.getNotificationTitle(notification.type)}
</p>
${isUnread ? '<div class="w-2 h-2 bg-blue-500 rounded-full"></div>' : ''}
</div>
<p class="text-sm text-slate-600 dark:text-slate-400 mt-1">
${notification.message || this.getNotificationMessage(notification)}
</p>
<p class="text-xs text-slate-500 dark:text-slate-500 mt-2">
${timeAgo}
</p>
${this.getNotificationActions(notification)}
</div>
</div>
</div>
`;
}).join('');
this.list.innerHTML = notificationHTML;
// Event Listeners für Aktionen hinzufügen
this.setupNotificationActions();
}
getNotificationIcon(type) {
const icons = {
'guest_request': '<i class="fas fa-user-plus text-blue-500"></i>',
'job_completed': '<i class="fas fa-check-circle text-green-500"></i>',
'job_failed': '<i class="fas fa-exclamation-triangle text-red-500"></i>',
'system': '<i class="fas fa-cog text-gray-500"></i>'
};
return icons[type] || '<i class="fas fa-bell text-blue-500"></i>';
}
getNotificationTitle(type) {
const titles = {
'guest_request': 'Neue Gastanfrage',
'job_completed': 'Job abgeschlossen',
'job_failed': 'Job fehlgeschlagen',
'system': 'System-Benachrichtigung'
};
return titles[type] || 'Benachrichtigung';
}
getNotificationMessage(notification) {
if (notification.message) return notification.message;
try {
const payload = JSON.parse(notification.payload || '{}');
if (notification.type === 'guest_request') {
return `Gastanfrage von ${payload.name || 'Unbekannt'} wartet auf Genehmigung.`;
}
} catch (e) {
console.warn('Fehler beim Parsen der Notification-Payload:', e);
}
return 'Neue Benachrichtigung verfügbar.';
}
getNotificationActions(notification) {
if (notification.type === 'guest_request') {
try {
const payload = JSON.parse(notification.payload || '{}');
return `
<div class="mt-3 flex space-x-2">
<button onclick="window.notificationManager.viewGuestRequest(${payload.request_id})"
class="px-3 py-1 bg-blue-500 text-white text-xs rounded hover:bg-blue-600">
Anzeigen
</button>
<button onclick="window.notificationManager.markAsRead(${notification.id})"
class="px-3 py-1 bg-gray-500 text-white text-xs rounded hover:bg-gray-600">
Als gelesen markieren
</button>
</div>
`;
} catch (e) {
return '';
}
}
return '';
}
setupNotificationActions() {
// Event Listeners werden über onclick direkt gesetzt
}
async viewGuestRequest(requestId) {
// Weiterleitung zur Admin-Gastanfragen-Seite
window.location.href = `/admin/guest-requests?highlight=${requestId}`;
}
async markAsRead(notificationId) {
try {
const response = await fetch(`/api/notifications/${notificationId}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
// Benachrichtigung als gelesen markieren
const notification = this.notifications.find(n => n.id === notificationId);
if (notification) {
notification.is_read = true;
}
this.updateUI();
}
} catch (error) {
console.error('Fehler beim Markieren als gelesen:', error);
}
}
async markAllAsRead() {
try {
const response = await fetch('/api/notifications/mark-all-read', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
// Alle Benachrichtigungen als gelesen markieren
this.notifications.forEach(n => n.is_read = true);
this.updateUI();
}
} catch (error) {
console.error('Fehler beim Markieren aller als gelesen:', error);
}
}
toggleDropdown() {
if (this.dropdown) {
this.isOpen = !this.isOpen;
this.dropdown.classList.toggle('hidden', !this.isOpen);
if (this.isOpen) {
this.loadNotifications(); // Aktualisieren beim Öffnen
}
}
}
closeDropdown() {
if (this.dropdown && this.isOpen) {
this.isOpen = false;
this.dropdown.classList.add('hidden');
}
}
formatTimeAgo(date) {
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'Gerade eben';
if (diffInSeconds < 3600) return `vor ${Math.floor(diffInSeconds / 60)} Min.`;
if (diffInSeconds < 86400) return `vor ${Math.floor(diffInSeconds / 3600)} Std.`;
return `vor ${Math.floor(diffInSeconds / 86400)} Tag(en)`;
}
}
// Mobile Menu
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
@ -526,14 +828,14 @@
const mobileMenuOverlay = document.getElementById('mobile-menu-overlay');
function openMobileMenu() {
mobileMenu.classList.add('active');
mobileMenuOverlay.classList.remove('hidden');
mobileMenu?.classList.add('active');
mobileMenuOverlay?.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function closeMobileMenu() {
mobileMenu.classList.remove('active');
mobileMenuOverlay.classList.add('hidden');
mobileMenu?.classList.remove('active');
mobileMenuOverlay?.classList.add('hidden');
document.body.style.overflow = '';
}
@ -547,7 +849,7 @@
userMenuBtn?.addEventListener('click', (e) => {
e.stopPropagation();
userDropdown.classList.toggle('hidden');
userDropdown?.classList.toggle('hidden');
});
document.addEventListener('click', () => {
@ -589,8 +891,18 @@
}
});
});
// Initialize Notification Manager
document.addEventListener('DOMContentLoaded', () => {
if (document.getElementById('notificationToggle')) {
window.notificationManager = new NotificationManager();
}
});
</script>
<!-- Jobs Safety Fix laden -->
<script src="{{ url_for('static', filename='js/jobs-safety-fix.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>