Till Tomczak 2d33753b94 feat: Major updates to backend structure and security enhancements
- Removed `COMMON_ERRORS.md` file to streamline documentation.
- Added `Flask-Limiter` for rate limiting and `redis` for session management in `requirements.txt`.
- Expanded `ROADMAP.md` to include completed security features and planned enhancements for version 2.2.
- Enhanced `setup_myp.sh` for ultra-secure kiosk installation, including system hardening and security configurations.
- Updated `app.py` to integrate CSRF protection and improved logging setup.
- Refactored user model to include username and active status for better user management.
- Improved job scheduler with uptime tracking and task management features.
- Updated various templates for a more cohesive user interface and experience.
2025-05-25 20:33:38 +02:00

278 lines
9.7 KiB
JavaScript

/**
* MYP Platform Dark Mode Handler
* Version: 6.0.0
*/
// Sofort ausführen, um FOUC zu vermeiden (Flash of Unstyled Content)
(function() {
"use strict";
// Speicherort für Dark Mode-Einstellung
const STORAGE_KEY = 'myp-dark-mode';
// DOM-Elemente
let darkModeToggle;
const html = document.documentElement;
// Initialisierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', initialize);
// Prüft System-Präferenz und gespeicherte Einstellung
function shouldUseDarkMode() {
// Lokale Speichereinstellung prüfen
const savedMode = localStorage.getItem(STORAGE_KEY);
// Prüfen ob es eine gespeicherte Einstellung gibt
if (savedMode !== null) {
return savedMode === 'true';
}
// Ansonsten Systemeinstellung verwenden
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
// Setzt Dark/Light Mode
function setDarkMode(enable) {
// Deaktiviere Übergänge temporär, um Flackern zu vermeiden
html.classList.add('disable-transitions');
// Dark Mode Klasse am HTML-Element setzen
if (enable) {
html.classList.add('dark');
html.setAttribute('data-theme', 'dark');
html.style.colorScheme = 'dark';
} else {
html.classList.remove('dark');
html.setAttribute('data-theme', 'light');
html.style.colorScheme = 'light';
}
// Speichern in LocalStorage
localStorage.setItem(STORAGE_KEY, enable);
// Update ThemeColor Meta-Tag
updateMetaThemeColor(enable);
// Wenn Toggle existiert, aktualisiere Icon
if (darkModeToggle) {
updateDarkModeToggle(enable);
}
// Event für andere Komponenten
window.dispatchEvent(new CustomEvent('darkModeChanged', {
detail: {
isDark: enable,
source: 'dark-mode-toggle',
timestamp: new Date().toISOString()
}
}));
// Event auch als eigenen Event-Typ versenden (rückwärtskompatibel)
const eventName = enable ? 'darkModeEnabled' : 'darkModeDisabled';
window.dispatchEvent(new CustomEvent(eventName, {
detail: { timestamp: new Date().toISOString() }
}));
// Übergänge nach kurzer Verzögerung wieder aktivieren
setTimeout(function() {
html.classList.remove('disable-transitions');
}, 100);
// Erfolgsmeldung in die Konsole
console.log(`Dark Mode ${enable ? 'aktiviert' : 'deaktiviert'}`);
}
// Aktualisiert das Theme-Color Meta-Tag
function updateMetaThemeColor(isDark) {
// Alle Theme-Color Meta-Tags aktualisieren
const metaTags = [
document.getElementById('metaThemeColor'),
document.querySelector('meta[name="theme-color"]'),
document.querySelector('meta[name="theme-color"][media="(prefers-color-scheme: light)"]'),
document.querySelector('meta[name="theme-color"][media="(prefers-color-scheme: dark)"]')
];
// CSS-Variablen für konsistente Farben verwenden
const darkColor = getComputedStyle(document.documentElement).getPropertyValue('--color-bg') || '#0f172a';
const lightColor = getComputedStyle(document.documentElement).getPropertyValue('--color-bg') || '#ffffff';
metaTags.forEach(tag => {
if (tag) {
// Für Media-spezifische Tags die entsprechende Farbe setzen
if (tag.getAttribute('media') === '(prefers-color-scheme: dark)') {
tag.setAttribute('content', darkColor);
} else if (tag.getAttribute('media') === '(prefers-color-scheme: light)') {
tag.setAttribute('content', lightColor);
} else {
// Für nicht-Media-spezifische Tags die aktuelle Farbe setzen
tag.setAttribute('content', isDark ? darkColor : lightColor);
}
}
});
}
// Aktualisiert das Aussehen des Toggle-Buttons
function updateDarkModeToggle(isDark) {
// Aria-Attribute für Barrierefreiheit
darkModeToggle.setAttribute('aria-pressed', isDark.toString());
darkModeToggle.title = isDark ? "Light Mode aktivieren" : "Dark Mode aktivieren";
// Stile aktualisieren mit Tailwind-Klassen
if (isDark) {
darkModeToggle.classList.remove('bg-indigo-600', 'hover:bg-indigo-700');
darkModeToggle.classList.add('bg-slate-800', 'hover:bg-slate-700', 'text-amber-400');
darkModeToggle.setAttribute('data-tooltip', 'Light Mode aktivieren');
} else {
darkModeToggle.classList.remove('bg-slate-800', 'hover:bg-slate-700', 'text-amber-400');
darkModeToggle.classList.add('bg-indigo-600', 'hover:bg-indigo-700');
darkModeToggle.setAttribute('data-tooltip', 'Dark Mode aktivieren');
}
// Icon aktualisieren - ohne innerHTML für CSP-Kompatibilität
const icon = darkModeToggle.querySelector('svg');
if (icon) {
// Animationsklasse hinzufügen
icon.classList.add('animate-spin-once');
// Nach Animation wieder entfernen
setTimeout(() => {
icon.classList.remove('animate-spin-once');
}, 300);
const pathElement = icon.querySelector('path');
if (pathElement) {
// Sonnen- oder Mond-Symbol
if (isDark) {
pathElement.setAttribute("d", "M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z");
} else {
pathElement.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
}
}
}
}
// Initialisierungsfunktion
function initialize() {
// Toggle-Button finden
darkModeToggle = document.getElementById('darkModeToggle');
// Wenn kein Toggle existiert, erstelle einen
if (!darkModeToggle) {
createDarkModeToggle();
}
// Event-Listener für Dark Mode Toggle
if (darkModeToggle) {
darkModeToggle.addEventListener('click', function() {
const isDark = !shouldUseDarkMode();
setDarkMode(isDark);
});
}
// Tastenkombination: Strg+Shift+D
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
const isDark = !shouldUseDarkMode();
setDarkMode(isDark);
e.preventDefault();
}
});
// Auf Systemeinstellungsänderungen reagieren
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
// Moderne Event-API verwenden
try {
darkModeMediaQuery.addEventListener('change', function(e) {
// Nur anwenden, wenn keine benutzerdefinierte Einstellung gespeichert ist
if (localStorage.getItem(STORAGE_KEY) === null) {
setDarkMode(e.matches);
}
});
} catch (error) {
// Fallback für ältere Browser
darkModeMediaQuery.addListener(function(e) {
if (localStorage.getItem(STORAGE_KEY) === null) {
setDarkMode(e.matches);
}
});
}
// Initialer Zustand
setDarkMode(shouldUseDarkMode());
// Animation für den korrekten Modus hinzufügen
const animClass = shouldUseDarkMode() ? 'dark-mode-transition' : 'light-mode-transition';
document.body.classList.add(animClass);
// Animation entfernen nach Abschluss
setTimeout(() => {
document.body.classList.remove(animClass);
}, 300);
}
// Erstellt ein Toggle-Element, falls keines existiert
function createDarkModeToggle() {
// Bestehende Header-Elemente finden
const header = document.querySelector('header');
const nav = document.querySelector('nav');
const container = document.querySelector('.dark-mode-container') || header || nav;
if (!container) return;
// Toggle-Button erstellen
darkModeToggle = document.createElement('button');
darkModeToggle.id = 'darkModeToggle';
darkModeToggle.className = 'p-2 sm:p-3 rounded-full bg-indigo-600 text-white transition-all duration-300';
darkModeToggle.setAttribute('aria-label', 'Dark Mode umschalten');
darkModeToggle.setAttribute('title', 'Dark Mode aktivieren');
darkModeToggle.setAttribute('data-tooltip', 'Dark Mode aktivieren');
// SVG-Icon erstellen (ohne innerHTML für Content Security Policy)
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("class", "w-4 h-4 sm:w-5 sm:h-5");
svg.setAttribute("fill", "none");
svg.setAttribute("stroke", "currentColor");
svg.setAttribute("viewBox", "0 0 24 24");
// Path für das Icon
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("stroke-linecap", "round");
path.setAttribute("stroke-linejoin", "round");
path.setAttribute("stroke-width", "2");
path.setAttribute("d", "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z");
// Elemente zusammenfügen
svg.appendChild(path);
darkModeToggle.appendChild(svg);
// Screenreader-Text hinzufügen
const srText = document.createElement('span');
srText.className = 'sr-only';
srText.textContent = 'Dark Mode umschalten';
darkModeToggle.appendChild(srText);
// Zum Container hinzufügen
container.appendChild(darkModeToggle);
}
// Sofort Dark/Light Mode anwenden (vor DOMContentLoaded)
const isDark = shouldUseDarkMode();
setDarkMode(isDark);
})();
// Animationen für Spin-Effekt
if (!document.querySelector('style#dark-mode-animations')) {
const styleTag = document.createElement('style');
styleTag.id = 'dark-mode-animations';
styleTag.textContent = `
@keyframes spin-once {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin-once {
animation: spin-once 0.3s ease-in-out;
}
`;
document.head.appendChild(styleTag);
}