🔧 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:
@ -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>
|
Reference in New Issue
Block a user