🗑️ Refactor: Remove obsolete printer check scripts and update app logic
**Änderungen:** - ✅ check_printer_ips.py und check_printers.py: Entfernt nicht mehr benötigte Skripte zur Überprüfung von Drucker-IP-Adressen. - ✅ DRUCKER_STATUS_REQUIREMENTS.md: Veraltete Anforderungen entfernt. - ✅ setup_standard_printers.py: Anpassungen zur Vereinheitlichung der Drucker-IP. - ✅ app.py: Logik zur Filterung offline/unreachable Drucker aktualisiert. **Ergebnis:** - Bereinigung des Codes durch Entfernen nicht mehr benötigter Dateien. - Optimierte Logik zur Handhabung von Druckerstatus in der Anwendung. 🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
@ -199,6 +199,15 @@
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-overlay.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.dark .modal-overlay {
|
||||
@ -211,6 +220,18 @@
|
||||
border-radius: 20px;
|
||||
box-shadow: var(--shadow-modal);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
@ -222,6 +243,88 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Mercedes Modal Specific Styling */
|
||||
.mercedes-modal {
|
||||
background: var(--gradient-modal);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 20px;
|
||||
box-shadow: var(--shadow-modal);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 115, 206, 0.2) transparent;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mercedes-modal.show {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mercedes-modal::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.mercedes-modal::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mercedes-modal::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 115, 206, 0.2);
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0, 115, 206, 0.4);
|
||||
}
|
||||
|
||||
.dark .mercedes-modal::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dark .mercedes-modal::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
/* Modal Animation Classes */
|
||||
.modal-enter {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
}
|
||||
|
||||
.modal-enter-active {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.modal-exit {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
|
||||
.modal-exit-active {
|
||||
opacity: 0;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Modal Overlay Click-to-Close Functionality */
|
||||
.modal-overlay[data-closable="true"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-overlay[data-closable="true"] .mercedes-modal {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* === FORM ELEMENTS === */
|
||||
.form-input {
|
||||
@apply w-full px-4 py-3 rounded-lg transition-all duration-200;
|
||||
@ -420,10 +523,6 @@
|
||||
}
|
||||
|
||||
/* === LEGACY COMPATIBILITY === */
|
||||
.mercedes-modal {
|
||||
@extend .modal;
|
||||
}
|
||||
|
||||
.glass-modal {
|
||||
@extend .modal;
|
||||
@extend .glass-card;
|
||||
|
160
backend/static/js/csrf-fix.js
Normal file
160
backend/static/js/csrf-fix.js
Normal file
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* MYP Platform - Globaler CSRF-Token-Fix
|
||||
* Automatisches Hinzufügen von CSRF-Token zu allen AJAX-Anfragen
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// CSRF-Token aus verschiedenen Quellen abrufen
|
||||
function getCSRFToken() {
|
||||
// Methode 1: Meta-Tag (bevorzugt)
|
||||
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
if (metaToken && metaToken.trim()) {
|
||||
console.log('🔒 CSRF-Token aus Meta-Tag geladen');
|
||||
return metaToken.trim();
|
||||
}
|
||||
|
||||
// Methode 2: Hidden Input
|
||||
const hiddenInput = document.querySelector('input[name="csrf_token"]')?.value;
|
||||
if (hiddenInput && hiddenInput.trim()) {
|
||||
console.log('🔒 CSRF-Token aus Hidden Input geladen');
|
||||
return hiddenInput.trim();
|
||||
}
|
||||
|
||||
// Methode 3: Cookie (falls verfügbar)
|
||||
const cookieMatch = document.cookie.match(/csrf_token=([^;]+)/);
|
||||
if (cookieMatch && cookieMatch[1]) {
|
||||
console.log('🔒 CSRF-Token aus Cookie geladen');
|
||||
return cookieMatch[1];
|
||||
}
|
||||
|
||||
console.error('❌ CSRF-Token konnte nicht gefunden werden!');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Token beim Laden der Seite abrufen
|
||||
let csrfToken = getCSRFToken();
|
||||
|
||||
// Token regelmäßig aktualisieren (alle 30 Minuten)
|
||||
setInterval(() => {
|
||||
const newToken = getCSRFToken();
|
||||
if (newToken && newToken !== csrfToken) {
|
||||
csrfToken = newToken;
|
||||
console.log('🔄 CSRF-Token aktualisiert');
|
||||
}
|
||||
}, 30 * 60 * 1000);
|
||||
|
||||
// Globale Funktion für andere Scripts
|
||||
window.getCSRFToken = function() {
|
||||
return csrfToken || getCSRFToken();
|
||||
};
|
||||
|
||||
// Fetch API abfangen und CSRF-Token hinzufügen
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(url, options = {}) {
|
||||
// Nur für POST, PUT, DELETE, PATCH Anfragen
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
const needsCSRF = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method);
|
||||
|
||||
if (needsCSRF) {
|
||||
const token = window.getCSRFToken();
|
||||
if (token) {
|
||||
// Headers initialisieren falls nicht vorhanden
|
||||
options.headers = options.headers || {};
|
||||
|
||||
// CSRF-Token hinzufügen (mehrere Methoden für Kompatibilität)
|
||||
options.headers['X-CSRFToken'] = token;
|
||||
options.headers['X-CSRF-Token'] = token;
|
||||
|
||||
// Für FormData: Token als Feld hinzufügen
|
||||
if (options.body instanceof FormData) {
|
||||
options.body.append('csrf_token', token);
|
||||
}
|
||||
// Für JSON: Token in Headers (bereits oben gemacht)
|
||||
else if (options.headers['Content-Type']?.includes('application/json')) {
|
||||
// Token ist bereits in Headers
|
||||
}
|
||||
// Für URL-encoded: Token hinzufügen
|
||||
else if (typeof options.body === 'string' && options.headers['Content-Type']?.includes('application/x-www-form-urlencoded')) {
|
||||
options.body += (options.body ? '&' : '') + 'csrf_token=' + encodeURIComponent(token);
|
||||
}
|
||||
|
||||
console.log(`🔒 CSRF-Token zu ${method} ${url} hinzugefügt`);
|
||||
} else {
|
||||
console.warn(`⚠️ Kein CSRF-Token für ${method} ${url} verfügbar`);
|
||||
}
|
||||
}
|
||||
|
||||
return originalFetch.call(this, url, options);
|
||||
};
|
||||
|
||||
// XMLHttpRequest abfangen (für ältere AJAX-Bibliotheken)
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
|
||||
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
|
||||
this._method = method.toUpperCase();
|
||||
this._url = url;
|
||||
return originalXHROpen.call(this, method, url, async, user, password);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function(data) {
|
||||
const needsCSRF = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(this._method);
|
||||
|
||||
if (needsCSRF) {
|
||||
const token = window.getCSRFToken();
|
||||
if (token) {
|
||||
// Header hinzufügen
|
||||
this.setRequestHeader('X-CSRFToken', token);
|
||||
this.setRequestHeader('X-CSRF-Token', token);
|
||||
|
||||
// Für FormData: Token hinzufügen
|
||||
if (data instanceof FormData) {
|
||||
data.append('csrf_token', token);
|
||||
}
|
||||
// Für String-Data: Token hinzufügen
|
||||
else if (typeof data === 'string' && !this.getResponseHeader('Content-Type')?.includes('application/json')) {
|
||||
data += (data ? '&' : '') + 'csrf_token=' + encodeURIComponent(token);
|
||||
}
|
||||
|
||||
console.log(`🔒 CSRF-Token zu XHR ${this._method} ${this._url} hinzugefügt`);
|
||||
} else {
|
||||
console.warn(`⚠️ Kein CSRF-Token für XHR ${this._method} ${this._url} verfügbar`);
|
||||
}
|
||||
}
|
||||
|
||||
return originalXHRSend.call(this, data);
|
||||
};
|
||||
|
||||
// jQuery AJAX Setup (falls jQuery verwendet wird)
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.jQuery) {
|
||||
jQuery.ajaxSetup({
|
||||
beforeSend: function(xhr, settings) {
|
||||
const needsCSRF = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(settings.type?.toUpperCase());
|
||||
|
||||
if (needsCSRF && !this.crossDomain) {
|
||||
const token = window.getCSRFToken();
|
||||
if (token) {
|
||||
xhr.setRequestHeader('X-CSRFToken', token);
|
||||
xhr.setRequestHeader('X-CSRF-Token', token);
|
||||
console.log(`🔒 CSRF-Token zu jQuery ${settings.type} ${settings.url} hinzugefügt`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Debug-Funktion für Entwicklung
|
||||
window.debugCSRF = function() {
|
||||
console.log('🔍 CSRF Debug Info:');
|
||||
console.log('Meta-Tag:', document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'));
|
||||
console.log('Hidden Input:', document.querySelector('input[name="csrf_token"]')?.value);
|
||||
console.log('Current Token:', window.getCSRFToken());
|
||||
console.log('Cookie:', document.cookie);
|
||||
};
|
||||
|
||||
console.log('🔒 CSRF-Fix geladen - automatisches Token-Management aktiv');
|
||||
})();
|
308
backend/static/sw.js
Normal file
308
backend/static/sw.js
Normal file
@ -0,0 +1,308 @@
|
||||
/**
|
||||
* MYP Service Worker
|
||||
* =================
|
||||
*
|
||||
* Service Worker für das Mercedes-Benz 3D-Druck-Management-System
|
||||
* Bietet Offline-Funktionalität und Caching für bessere Performance
|
||||
*/
|
||||
|
||||
const CACHE_NAME = 'myp-v1.0.0';
|
||||
const STATIC_CACHE_NAME = 'myp-static-v1.0.0';
|
||||
const DYNAMIC_CACHE_NAME = 'myp-dynamic-v1.0.0';
|
||||
|
||||
// Statische Ressourcen, die immer gecacht werden sollen
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/static/css/tailwind.min.css',
|
||||
'/static/css/dark-light-unified.css',
|
||||
'/static/fontawesome/css/all.min.css',
|
||||
'/static/js/jobs-safety-fix.js',
|
||||
'/static/manifest.json',
|
||||
'/static/favicon.svg',
|
||||
'/static/icons/icon-192.png',
|
||||
'/static/icons/icon-512.png'
|
||||
];
|
||||
|
||||
// API-Endpunkte, die gecacht werden können
|
||||
const CACHEABLE_APIS = [
|
||||
'/api/printers',
|
||||
'/api/jobs',
|
||||
'/api/stats'
|
||||
];
|
||||
|
||||
// Installation des Service Workers
|
||||
self.addEventListener('install', event => {
|
||||
console.log('🔧 MYP Service Worker wird installiert...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(STATIC_CACHE_NAME)
|
||||
.then(cache => {
|
||||
console.log('📦 Statische Assets werden gecacht...');
|
||||
return cache.addAll(STATIC_ASSETS);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('✅ MYP Service Worker erfolgreich installiert');
|
||||
return self.skipWaiting();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Fehler bei Service Worker Installation:', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Aktivierung des Service Workers
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('🚀 MYP Service Worker wird aktiviert...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
// Alte Caches löschen
|
||||
if (cacheName !== STATIC_CACHE_NAME &&
|
||||
cacheName !== DYNAMIC_CACHE_NAME &&
|
||||
cacheName !== CACHE_NAME) {
|
||||
console.log('🗑️ Lösche alten Cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('✅ MYP Service Worker aktiviert');
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch-Event Handler für Caching-Strategien
|
||||
self.addEventListener('fetch', event => {
|
||||
const request = event.request;
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Nur GET-Requests cachen
|
||||
if (request.method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Statische Assets: Cache First
|
||||
if (STATIC_ASSETS.some(asset => url.pathname.endsWith(asset) || url.pathname === asset)) {
|
||||
event.respondWith(cacheFirst(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// API-Endpunkte: Network First mit Cache Fallback
|
||||
if (CACHEABLE_APIS.some(api => url.pathname.startsWith(api))) {
|
||||
event.respondWith(networkFirst(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Statische Dateien (CSS, JS, Bilder): Cache First
|
||||
if (url.pathname.startsWith('/static/')) {
|
||||
event.respondWith(cacheFirst(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML-Seiten: Network First
|
||||
if (request.headers.get('accept').includes('text/html')) {
|
||||
event.respondWith(networkFirst(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Alle anderen Requests: Network Only
|
||||
event.respondWith(fetch(request));
|
||||
});
|
||||
|
||||
/**
|
||||
* Cache First Strategie
|
||||
* Versucht zuerst aus dem Cache zu laden, dann aus dem Netzwerk
|
||||
*/
|
||||
async function cacheFirst(request) {
|
||||
try {
|
||||
const cache = await caches.open(STATIC_CACHE_NAME);
|
||||
const cachedResponse = await cache.match(request);
|
||||
|
||||
if (cachedResponse) {
|
||||
console.log('📦 Aus Cache geladen:', request.url);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
const networkResponse = await fetch(request);
|
||||
|
||||
if (networkResponse.ok) {
|
||||
cache.put(request, networkResponse.clone());
|
||||
console.log('🌐 Aus Netzwerk geladen und gecacht:', request.url);
|
||||
}
|
||||
|
||||
return networkResponse;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Cache First Fehler:', error);
|
||||
|
||||
// Fallback für kritische Ressourcen
|
||||
if (request.url.includes('tailwind.min.css')) {
|
||||
return new Response('/* Offline CSS Fallback */', {
|
||||
headers: { 'Content-Type': 'text/css' }
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network First Strategie
|
||||
* Versucht zuerst aus dem Netzwerk zu laden, dann aus dem Cache
|
||||
*/
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const networkResponse = await fetch(request);
|
||||
|
||||
if (networkResponse.ok) {
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
cache.put(request, networkResponse.clone());
|
||||
console.log('🌐 Aus Netzwerk geladen:', request.url);
|
||||
return networkResponse;
|
||||
}
|
||||
|
||||
throw new Error(`Network response not ok: ${networkResponse.status}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log('📦 Netzwerk nicht verfügbar, versuche Cache:', request.url);
|
||||
|
||||
const cache = await caches.open(DYNAMIC_CACHE_NAME);
|
||||
const cachedResponse = await cache.match(request);
|
||||
|
||||
if (cachedResponse) {
|
||||
console.log('📦 Aus Cache geladen (Offline):', request.url);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Offline-Fallback für API-Requests
|
||||
if (request.url.includes('/api/')) {
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
error: 'Offline - Keine Verbindung zum Server',
|
||||
offline: true
|
||||
}), {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// Offline-Fallback für HTML-Seiten
|
||||
if (request.headers.get('accept').includes('text/html')) {
|
||||
const offlineHtml = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MYP - Offline</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
|
||||
.offline-message { max-width: 500px; margin: 0 auto; }
|
||||
.icon { font-size: 64px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="offline-message">
|
||||
<div class="icon">📡</div>
|
||||
<h1>Offline</h1>
|
||||
<p>Sie sind derzeit offline. Bitte überprüfen Sie Ihre Internetverbindung.</p>
|
||||
<button onclick="window.location.reload()">Erneut versuchen</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return new Response(offlineHtml, {
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Background Sync für Offline-Aktionen
|
||||
self.addEventListener('sync', event => {
|
||||
console.log('🔄 Background Sync Event:', event.tag);
|
||||
|
||||
if (event.tag === 'background-sync-jobs') {
|
||||
event.waitUntil(syncJobs());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Synchronisiert Jobs im Hintergrund
|
||||
*/
|
||||
async function syncJobs() {
|
||||
try {
|
||||
console.log('🔄 Synchronisiere Jobs im Hintergrund...');
|
||||
|
||||
// Hier könnten offline gespeicherte Aktionen synchronisiert werden
|
||||
const response = await fetch('/api/jobs');
|
||||
|
||||
if (response.ok) {
|
||||
console.log('✅ Jobs erfolgreich synchronisiert');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Fehler bei Job-Synchronisation:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Push-Benachrichtigungen
|
||||
self.addEventListener('push', event => {
|
||||
console.log('📬 Push-Benachrichtigung erhalten');
|
||||
|
||||
const options = {
|
||||
body: 'Sie haben eine neue Benachrichtigung',
|
||||
icon: '/static/icons/icon-192.png',
|
||||
badge: '/static/icons/icon-192.png',
|
||||
vibrate: [100, 50, 100],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
primaryKey: 1
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: 'explore',
|
||||
title: 'Anzeigen',
|
||||
icon: '/static/icons/icon-192.png'
|
||||
},
|
||||
{
|
||||
action: 'close',
|
||||
title: 'Schließen',
|
||||
icon: '/static/icons/icon-192.png'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (event.data) {
|
||||
const data = event.data.json();
|
||||
options.body = data.message || options.body;
|
||||
options.title = data.title || 'MYP Benachrichtigung';
|
||||
}
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification('MYP System', options)
|
||||
);
|
||||
});
|
||||
|
||||
// Benachrichtigung-Click Handler
|
||||
self.addEventListener('notificationclick', event => {
|
||||
console.log('🔔 Benachrichtigung geklickt:', event.action);
|
||||
|
||||
event.notification.close();
|
||||
|
||||
if (event.action === 'explore') {
|
||||
event.waitUntil(
|
||||
clients.openWindow('/')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('🚀 MYP Service Worker geladen');
|
Reference in New Issue
Block a user