1541 lines
89 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Ausbilder-Bereich - Mercedes-Benz TBA Marienfelde{% endblock %}
{% block head %}
{{ super() }}
<!-- CSRF Token für AJAX-Anfragen -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- Verstecktes CSRF-Input als Fallback -->
<form id="csrf-form" style="display: none;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
<!-- Konsolidierte Admin JavaScript Datei - verhindert Event-Handler-Konflikte -->
<script src="{{ url_for('static', filename='js/admin-unified.js') }}" defer></script>
<!-- Debug Script für CSRF Token -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const metaToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
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) {
console.error('❌ KRITISCH: Kein CSRF Token gefunden!');
}
});
</script>
<!-- Loading Overlay -->
<div id="loading-overlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center hidden">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl">
<div class="flex items-center space-x-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span class="text-lg font-medium text-gray-900 dark:text-white">Wird geladen...</span>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<!-- Modernes Admin Panel mit Real-Time Updates -->
<div class="min-h-screen">
<!-- Hero Header mit Real-Time Anzeige -->
<div class="relative overflow-hidden bg-gradient-to-r from-slate-900 via-blue-900 to-indigo-900 text-white rounded-3xl mx-4 mt-4">
<div class="absolute inset-0 bg-black/20"></div>
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent"></div>
<!-- Live Status Indicator -->
<div class="absolute top-4 right-4 flex items-center space-x-2">
<div class="flex items-center space-x-2 bg-white/10 backdrop-blur-sm border border-white/20 rounded-full px-3 py-1">
<div id="live-indicator" class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span class="text-sm font-medium">Live</span>
</div>
<div class="bg-white/10 backdrop-blur-sm border border-white/20 rounded-full px-3 py-1">
<span id="live-time" class="text-sm font-medium"></span>
</div>
</div>
<!-- Animated Background Pattern -->
<div class="absolute inset-0 opacity-10 rounded-3xl overflow-hidden">
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 25% 25%, white 2px, transparent 2px), radial-gradient(circle at 75% 75%, white 2px, transparent 2px); background-size: 50px 50px;"></div>
</div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<div class="text-center">
<!-- Mercedes-Benz Logo -->
<div class="inline-flex items-center justify-center w-20 h-20 bg-white/10 backdrop-blur-sm rounded-full mb-6 border border-white/20">
<svg class="w-10 h-10 text-white" viewBox="0 0 80 80" fill="currentColor">
<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.5
C27,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,40
c0-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.8
C53.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.9
C67.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,40
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
</svg>
</div>
<h1 class="text-5xl md:text-6xl font-bold mb-4 tracking-tight">
<span class="bg-gradient-to-r from-white to-blue-200 bg-clip-text text-transparent">
TBA Ausbilder-Bereich
</span>
</h1>
<p class="text-xl md:text-2xl text-blue-100 max-w-3xl mx-auto leading-relaxed">
Verwaltung des Steckdosen-Steuerungssystems für die Technische Berufsausbildung Werk Marienfelde
</p>
<!-- Real-Time Quick Actions -->
<div class="flex flex-wrap justify-center gap-4 mt-8">
<button id="system-status-btn" class="inline-flex items-center px-6 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl text-white hover:bg-white/20 transition-all duration-300 hover:scale-105">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
System Status
</button>
<button id="analytics-btn" class="inline-flex items-center px-6 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl text-white hover:bg-white/20 transition-all duration-300 hover:scale-105">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
Live Analytics
</button>
<button id="maintenance-btn" class="inline-flex items-center px-6 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl text-white hover:bg-white/20 transition-all duration-300 hover:scale-105">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
Wartung
</button>
</div>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10">
<!-- Real-Time Stats Dashboard mit Live-Updates -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
<!-- Live Benutzer Stats -->
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
<div class="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-indigo-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
</svg>
</div>
<div class="text-right">
<div id="live-users-count" class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_users or 0 }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Registrierte Benutzer</div>
</div>
</div>
<div class="flex items-center space-x-2 mb-2">
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span class="text-xs text-green-600 dark:text-green-400 font-medium">Live-Daten</span>
</div>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
<div id="users-progress" class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full transition-all duration-1000" style="width: {{ (stats.total_users / 10 * 100) if stats.total_users else 0 }}%"></div>
</div>
</div>
</div>
<!-- Live Drucker Stats -->
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
<div class="absolute inset-0 bg-gradient-to-br from-green-500/10 to-emerald-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-green-500 to-green-600 rounded-xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
</svg>
</div>
<div class="text-right">
<div id="live-printers-count" class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_printers or 0 }}</div>
<div id="live-printers-online" class="text-sm text-green-500">{{ stats.online_printers or 0 }} online</div>
</div>
</div>
<div class="flex items-center space-x-2 mb-2">
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span class="text-xs text-green-600 dark:text-green-400 font-medium">Echtzeit-Status</span>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Verfügbare Drucker</div>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
<div id="printers-progress" class="bg-gradient-to-r from-green-500 to-green-600 h-2 rounded-full transition-all duration-1000" style="width: {{ ((stats.online_printers / stats.total_printers) * 100) if stats.total_printers else 0 }}%"></div>
</div>
</div>
</div>
<!-- Live Jobs Stats -->
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-violet-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
</div>
<div class="text-right">
<div id="live-jobs-active" class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.active_jobs or 0 }}</div>
<div id="live-jobs-queued" class="text-sm text-slate-500 dark:text-slate-400">{{ stats.queued_jobs or 0 }} in Warteschlange</div>
</div>
</div>
<div class="flex items-center space-x-2 mb-2">
<div class="w-2 h-2 bg-purple-400 rounded-full animate-pulse"></div>
<span class="text-xs text-purple-600 dark:text-purple-400 font-medium">Live-Aufträge</span>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Aktive Druckaufträge</div>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
<div id="jobs-progress" class="bg-gradient-to-r from-purple-500 to-purple-600 h-2 rounded-full transition-all duration-1000" style="width: {{ (stats.active_jobs * 10) if stats.active_jobs else 0 }}%"></div>
</div>
</div>
</div>
<!-- Live Erfolgsrate Stats -->
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
<div class="absolute inset-0 bg-gradient-to-br from-orange-500/10 to-red-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<div class="p-3 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl shadow-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="text-right">
<div id="live-success-rate" class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.success_rate or 0 }}%</div>
<div id="success-trend" class="text-sm text-green-500">
<span class="inline-flex items-center">
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"/>
</svg>
Stabil
</span>
</div>
</div>
</div>
<div class="flex items-center space-x-2 mb-2">
<div class="w-2 h-2 bg-orange-400 rounded-full animate-pulse"></div>
<span class="text-xs text-orange-600 dark:text-orange-400 font-medium">Live-Erfolgsrate</span>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Erfolgreiche Druckaufträge</div>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
<div id="success-progress" class="bg-gradient-to-r from-orange-500 to-orange-600 h-2 rounded-full transition-all duration-1000" style="width: {{ stats.success_rate or 0 }}%"></div>
</div>
</div>
</div>
</div>
<!-- Navigation Tabs -->
<div class="bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-4 mb-12 shadow-xl">
<nav class="flex space-x-2" aria-label="Tabs">
<a href="{{ url_for('admin_page', tab='users') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'users' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'users' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
</svg>
Benutzer
</a>
<a href="{{ url_for('admin_page', tab='printers') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'printers' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'printers' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
Drucker-Steckdosen
</a>
<a href="{{ url_for('admin_page', tab='jobs') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'jobs' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'jobs' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
Reservierungen
</a>
<a href="{{ url_for('admin_page', tab='system') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'system' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'system' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
System
</a>
<a href="{{ url_for('admin_page', tab='logs') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'logs' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'logs' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
Logs
</a>
<a href="{{ url_for('admin_guest_requests') }}"
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'guest_requests' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'guest_requests' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
TBA-Anträge
</a>
</nav>
</div>
<!-- Tab Content -->
<!-- Critical Errors Alert System -->
<div id="critical-errors-alert" class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-6 mb-8 hidden">
<div class="flex items-start space-x-4">
<div class="flex-shrink-0">
<svg class="w-8 h-8 text-red-500 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-lg font-semibold text-red-800 dark:text-red-200 mb-2">
🚨 Kritische Systemfehler erkannt
</h3>
<div id="error-list" class="space-y-2">
<!-- Error items will be populated here -->
</div>
<div class="mt-4 flex space-x-3">
<button id="fix-errors-btn" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm font-medium">
🔧 Automatisch reparieren
</button>
<button id="dismiss-errors-btn" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors text-sm font-medium">
❌ Verwerfen
</button>
<button id="view-error-details-btn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium">
📊 Details anzeigen
</button>
</div>
</div>
</div>
</div>
<!-- Database Health Status -->
<div id="db-health-status" class="bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 mb-8 shadow-lg">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">🗄️ Datenbank-Gesundheitsstatus</h3>
<div class="flex items-center space-x-2">
<div id="db-status-indicator" class="w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
<span id="db-status-text" class="text-sm font-medium text-slate-600 dark:text-slate-400">Gesund</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Letzte Migration</div>
<div id="last-migration" class="text-lg font-semibold text-slate-900 dark:text-white">Lädt...</div>
</div>
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Schema-Integrität</div>
<div id="schema-integrity" class="text-lg font-semibold text-green-600 dark:text-green-400">Lädt...</div>
</div>
<div class="bg-white/40 dark:bg-slate-700/40 rounded-lg p-4">
<div class="text-sm text-slate-500 dark:text-slate-400">Letzte Fehler</div>
<div id="recent-errors-count" class="text-lg font-semibold text-slate-900 dark:text-white">Lädt...</div>
</div>
</div>
</div>
<!-- Tab Content -->
<div class="bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 shadow-xl overflow-hidden">
{% if active_tab == 'users' %}
<!-- Benutzer Tab -->
<div class="p-12">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Benutzerverwaltung</h2>
<button id="add-user-btn" class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
Neuer Benutzer
</button>
</div>
<!-- Benutzer Tabelle -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
<thead class="bg-slate-50 dark:bg-slate-900/50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Benutzer</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">E-Mail</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Rolle</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Letzte Aktivität</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Aktionen</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-slate-800/50 divide-y divide-slate-200 dark:divide-slate-700">
{% for user in users %}
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200">
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
{{ user.username[0].upper() }}
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-slate-900 dark:text-white">{{ user.username }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">{{ user.first_name }} {{ user.last_name }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ user.email }}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full {{ 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200' if user.is_admin else 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' }}">
{{ 'Administrator' if user.is_admin else 'Benutzer' }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full {{ 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' if user.is_active else 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' }}">
<span class="w-2 h-2 mr-1 rounded-full {{ 'bg-green-400' if user.is_active else 'bg-red-400' }}"></span>
{{ 'Aktiv' if user.is_active else 'Inaktiv' }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
{{ user.last_login | format_datetime if user.last_login else 'Nie' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div class="flex space-x-2">
<button class="edit-user-btn text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-user-id="{{ user.id }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</button>
<button class="delete-user-btn text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors" data-user-id="{{ user.id }}" data-user-name="{{ user.username }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elif active_tab == 'printers' %}
<!-- Drucker Tab -->
<div class="p-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Drucker-Steckdosen-Verwaltung</h2>
<button id="add-printer-btn" class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-lg hover:shadow-xl">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
Steckdose hinzufügen
</button>
</div>
<!-- Drucker Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for printer in printers %}
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">{{ printer.name }}</h3>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
{{ printer.status.title() }}
</span>
</div>
<div class="space-y-3">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Modell:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ printer.model }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Standort:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ printer.location }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Aktuelle Aufgabe:</span>
<span class="text-slate-900 dark:text-white font-medium">
{% if printer.current_job %}
{{ printer.current_job.filename[:20] }}...
{% else %}
Keine
{% endif %}
</span>
</div>
{% if printer.current_job %}
<div class="mt-4">
<div class="flex justify-between text-sm mb-1">
<span class="text-slate-600 dark:text-slate-400">Fortschritt:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ printer.current_job.progress }}%</span>
</div>
<div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2">
<div class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full transition-all duration-500" style="width: {{ printer.current_job.progress }}%"></div>
</div>
</div>
{% endif %}
</div>
<div class="flex space-x-2 mt-6">
<button class="manage-printer-btn flex-1 px-3 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium" data-printer-id="{{ printer.id }}">
Verwalten
</button>
<!-- Smart-Plug Ein/Aus Button für Admins -->
<button class="toggle-printer-power-btn px-3 py-2 bg-gradient-to-r from-orange-500 to-red-500 text-white rounded-lg hover:from-orange-600 hover:to-red-600 transition-all duration-300 text-sm font-medium flex items-center space-x-1"
data-printer-id="{{ printer.id }}"
data-printer-name="{{ printer.name }}"
title="Steckdose ein/ausschalten">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
<span class="hidden lg:inline">Ein/Aus</span>
</button>
<button class="settings-printer-btn px-3 py-2 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors text-sm" data-printer-id="{{ printer.id }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</button>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- System Actions -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Wartung</h3>
<div class="space-y-3">
<button id="clear-cache-btn" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium">
Cache leeren
</button>
<button id="optimize-db-btn" class="w-full px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">
Datenbank optimieren
</button>
<button id="create-backup-btn" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium">
Backup erstellen
</button>
<button id="force-init-printers-btn" class="w-full px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors text-sm font-medium">
Drucker-Initialisierung erzwingen
</button>
</div>
</div>
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">System-Steuerung</h3>
<div class="space-y-3">
<button id="kiosk-restart-btn" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
<span>Kiosk neustarten</span>
</button>
<button id="restart-system-btn" class="w-full px-4 py-2 bg-orange-500 text-white rounded-lg hover:bg-orange-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span>System neustarten</span>
</button>
<button id="shutdown-system-btn" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18.364 5.636M5.636 18.364l12.728-12.728"/>
</svg>
<span>System herunterfahren</span>
</button>
<button id="system-status-btn" class="w-full px-4 py-2 bg-slate-500 text-white rounded-lg hover:bg-slate-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span>System-Status</span>
</button>
</div>
</div>
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3>
<div class="space-y-3">
<button id="edit-settings-btn" class="w-full px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors text-sm font-medium">
Einstellungen bearbeiten
</button>
<button id="update-printers-btn" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
Drucker aktualisieren
</button>
<button id="error-recovery-toggle-btn" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Fehlerresilienz</span>
</button>
</div>
</div>
</div>
</div>
{% elif active_tab == 'logs' %}
<!-- Logs Tab -->
<div class="p-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">System Logs</h2>
<div class="flex space-x-3">
<select id="log-level-filter" class="px-4 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
<option value="all">Alle Logs</option>
<option value="error">Fehler</option>
<option value="warning">Warnungen</option>
<option value="info">Info</option>
<option value="debug">Debug</option>
</select>
<button id="refresh-logs-btn" class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-lg hover:shadow-xl">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Aktualisieren
</button>
<button id="export-logs-btn" class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-slate-500 to-slate-600 text-white rounded-xl hover:from-slate-600 hover:to-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Export
</button>
</div>
</div>
<!-- Log Entries Container -->
<div id="logs-container" class="space-y-2 max-h-96 overflow-y-auto">
<!-- Logs werden hier dynamisch geladen -->
<div class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 dark:border-blue-400"></div>
</div>
</div>
</div>
{% else %}
<!-- Default Tab -->
<div class="p-8 text-center">
<div class="max-w-md mx-auto">
<svg class="w-16 h-16 mx-auto text-slate-400 dark:text-slate-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Willkommen im Admin Panel</h3>
<p class="text-slate-500 dark:text-slate-400">Wählen Sie einen Tab aus, um zu beginnen.</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Loading Overlay -->
<div id="loading-overlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
<div class="flex items-center justify-center h-full">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl">
<div class="flex items-center space-x-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span class="text-slate-900 dark:text-white font-medium">Wird geladen...</span>
</div>
</div>
</div>
</div>
<!-- Wartungs-Modal -->
<div id="maintenance-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
<div class="flex items-center justify-center h-full p-4">
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl max-w-md w-full max-h-90vh overflow-y-auto scrollbar-thin scrollbar-thumb-rounded">
<div class="text-center mb-6">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 dark:bg-blue-900 mb-4">
<svg class="h-6 w-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</div>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">System-Wartung</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">Wählen Sie eine Wartungsoption:</p>
</div>
<div class="space-y-3 mb-6">
<button id="modal-clear-cache" class="w-full px-4 py-3 bg-blue-500 text-white rounded-xl hover:bg-blue-600 transition-all duration-300 text-sm font-medium">
🗑️ Cache leeren
</button>
<button id="modal-optimize-db" class="w-full px-4 py-3 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-all duration-300 text-sm font-medium">
🔧 Datenbank optimieren
</button>
<button id="modal-create-backup" class="w-full px-4 py-3 bg-purple-500 text-white rounded-xl hover:bg-purple-600 transition-all duration-300 text-sm font-medium">
💾 Backup erstellen
</button>
<button id="modal-advanced-settings" class="w-full px-4 py-3 bg-slate-500 text-white rounded-xl hover:bg-slate-600 transition-all duration-300 text-sm font-medium">
⚙️ Erweiterte Einstellungen
</button>
</div>
<div class="flex justify-end">
<button id="close-maintenance-modal" class="px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors text-sm font-medium">
Schließen
</button>
</div>
</div>
</div>
</div>
<script>
// ===== WARTUNGS-FUNKTIONALITÄT =====
// Wartungs-Modal Verwaltung
class MaintenanceModal {
constructor() {
this.modal = document.getElementById('maintenance-modal');
this.triggerBtn = document.getElementById('maintenance-btn');
this.closeBtn = document.getElementById('close-maintenance-modal');
this.isOpen = false;
this.isLoading = false;
this.isInitialized = false;
// Verhindere doppelte Initialisierung
if (window.maintenanceModalInstance) {
return window.maintenanceModalInstance;
}
this.initializeEventListeners();
this.isInitialized = true;
window.maintenanceModalInstance = this;
}
initializeEventListeners() {
// Modal öffnen - mit Event-Delegation und Konflikt-Vermeidung
if (this.triggerBtn) {
// Entferne alle existierenden Event-Listener
this.triggerBtn.removeEventListener('click', this.handleTriggerClick);
// Füge neuen Event-Listener hinzu
this.handleTriggerClick = (e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log('🛠️ Wartungs-Modal wird geöffnet...');
this.openModal();
};
this.triggerBtn.addEventListener('click', this.handleTriggerClick, { capture: true });
}
// Modal schließen
if (this.closeBtn) {
this.closeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.closeModal();
});
}
// Modal schließen bei Klick außerhalb - aber nur auf das Overlay
if (this.modal) {
this.modal.addEventListener('click', (e) => {
// Nur schließen wenn direkt auf das Modal-Overlay geklickt wird
if (e.target === this.modal) {
this.closeModal();
}
});
}
// ESC-Taste zum Schließen
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
e.preventDefault();
this.closeModal();
}
});
// Wartungs-Aktionen
this.initializeMaintenanceActions();
}
initializeMaintenanceActions() {
const actions = [
{ id: 'modal-clear-cache', handler: (e) => {
e.preventDefault();
e.stopPropagation();
this.executeAction('clearCache');
}},
{ id: 'modal-optimize-db', handler: (e) => {
e.preventDefault();
e.stopPropagation();
this.executeAction('optimizeDatabase');
}},
{ id: 'modal-create-backup', handler: (e) => {
e.preventDefault();
e.stopPropagation();
this.executeAction('createBackup');
}},
{ id: 'modal-advanced-settings', handler: (e) => {
e.preventDefault();
e.stopPropagation();
this.navigateToSettings();
}}
];
actions.forEach(action => {
const element = document.getElementById(action.id);
if (element) {
// Entferne existierende Event-Listener
element.removeEventListener('click', action.handler);
// Füge neuen Event-Listener hinzu
element.addEventListener('click', action.handler);
}
});
}
openModal() {
if (this.modal && !this.isOpen) {
console.log('✅ Modal wird geöffnet');
this.modal.classList.remove('hidden');
this.isOpen = true;
document.body.style.overflow = 'hidden';
// Focus-Management für Barrierefreiheit
setTimeout(() => {
const firstFocusable = this.modal.querySelector('button:not(#close-maintenance-modal)');
if (firstFocusable) {
firstFocusable.focus();
}
}, 100);
}
}
closeModal() {
if (this.modal && this.isOpen) {
console.log('❌ Modal wird geschlossen');
this.modal.classList.add('hidden');
this.isOpen = false;
document.body.style.overflow = '';
// Focus zurück zum Trigger-Button
if (this.triggerBtn) {
this.triggerBtn.focus();
}
}
}
setLoadingState(loading) {
this.isLoading = loading;
const buttons = this.modal.querySelectorAll('button:not(#close-maintenance-modal)');
buttons.forEach(button => {
if (loading) {
button.disabled = true;
button.style.opacity = '0.6';
button.style.cursor = 'not-allowed';
// Spinner hinzufügen
if (!button.querySelector('.loading-spinner')) {
const spinner = document.createElement('div');
spinner.className = 'loading-spinner inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2';
button.insertBefore(spinner, button.firstChild);
}
} else {
button.disabled = false;
button.style.opacity = '1';
button.style.cursor = 'pointer';
// Spinner entfernen
const spinner = button.querySelector('.loading-spinner');
if (spinner) {
spinner.remove();
}
}
});
// Loading-Overlay anzeigen/verstecken
const loadingOverlay = document.getElementById('loading-overlay');
if (loadingOverlay) {
if (loading) {
loadingOverlay.classList.remove('hidden');
} else {
loadingOverlay.classList.add('hidden');
}
}
}
async executeAction(actionType) {
if (this.isLoading) return;
try {
this.setLoadingState(true);
let endpoint = '';
let confirmMessage = '';
let successMessage = '';
switch (actionType) {
case 'clearCache':
endpoint = '/api/admin/maintenance/clear-cache';
confirmMessage = 'Möchten Sie den Cache wirklich leeren?';
successMessage = 'Cache erfolgreich geleert';
break;
case 'optimizeDatabase':
endpoint = '/api/admin/maintenance/optimize-database';
confirmMessage = 'Möchten Sie die Datenbank optimieren? Dies kann einige Minuten dauern.';
successMessage = 'Datenbank erfolgreich optimiert';
break;
case 'createBackup':
endpoint = '/api/admin/maintenance/create-backup';
confirmMessage = 'Möchten Sie ein Backup erstellen?';
successMessage = 'Backup erfolgreich erstellt';
break;
default:
throw new Error(`Unbekannte Aktion: ${actionType}`);
}
if (!confirm(confirmMessage)) {
this.setLoadingState(false);
return;
}
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(successMessage, 'success');
// Modal NICHT automatisch schließen - Benutzer soll entscheiden
} else {
showNotification(result.message || 'Fehler bei der Ausführung der Wartungsaktion', 'error');
}
} catch (error) {
console.error('Fehler bei Wartungsaktion:', error);
showNotification('Fehler bei der Ausführung der Wartungsaktion: ' + error.message, 'error');
} finally {
this.setLoadingState(false);
}
}
navigateToSettings() {
try {
// Navigation zu den erweiterten Admin-Einstellungen (HTML-Seite)
window.location.href = '/admin/advanced-settings';
} catch (error) {
console.error('Fehler beim Navigieren zu den Einstellungen:', error);
showNotification('Fehler beim Öffnen der Einstellungen', 'error');
}
}
}
// Globale Wartungs-Modal Instanz
let maintenanceModal = null;
// Initialisierung nach DOM-Laden - aber nur einmal
document.addEventListener('DOMContentLoaded', function() {
// Verhindere doppelte Initialisierung
if (!window.maintenanceModalInstance && !maintenanceModal) {
console.log('🔧 Wartungs-Modal wird initialisiert...');
maintenanceModal = new MaintenanceModal();
// Deaktiviere andere Event-Handler für den Wartungs-Button
const maintenanceBtn = document.getElementById('maintenance-btn');
if (maintenanceBtn) {
// Entferne alle anderen Event-Listener
const newBtn = maintenanceBtn.cloneNode(true);
maintenanceBtn.parentNode.replaceChild(newBtn, maintenanceBtn);
// Füge nur unseren Event-Listener hinzu
newBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (maintenanceModal) {
maintenanceModal.openModal();
}
}, { capture: true });
}
}
});
// Notification anzeigen
function showNotification(message, type = 'info') {
// Erstelle temporäre Notification
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-xl shadow-lg max-w-sm transition-all duration-300 ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' :
'bg-blue-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center space-x-3">
<div class="flex-shrink-0">
${type === 'success' ? '✅' : type === 'error' ? '❌' : ''}
</div>
<div class="text-sm font-medium">${message}</div>
<button onclick="this.parentElement.parentElement.remove()" class="ml-auto text-white hover:text-gray-200">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
`;
document.body.appendChild(notification);
// Animation einblenden
setTimeout(() => {
notification.style.transform = 'translateX(0)';
notification.style.opacity = '1';
}, 100);
// Automatisch entfernen nach 5 Sekunden
setTimeout(() => {
if (notification.parentNode) {
notification.style.transform = 'translateX(100%)';
notification.style.opacity = '0';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}
}, 5000);
}
// CSRF Token
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// ===== ERWEITERTE SYSTEM-CONTROL-FUNKTIONALITÄT =====
class SystemControlManager {
constructor() {
this.pendingOperations = new Map();
this.lastStatusUpdate = null;
this.isInitialized = false;
this.initializeEventListeners();
this.startStatusPolling();
this.isInitialized = true;
console.log('🔧 System-Control-Manager initialisiert');
}
initializeEventListeners() {
// Kiosk-Neustart
const kioskRestartBtn = document.getElementById('kiosk-restart-btn');
if (kioskRestartBtn) {
kioskRestartBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleKioskRestart();
});
}
// System-Neustart
const restartBtn = document.getElementById('restart-system-btn');
if (restartBtn) {
restartBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleSystemRestart();
});
}
// System-Shutdown
const shutdownBtn = document.getElementById('shutdown-system-btn');
if (shutdownBtn) {
shutdownBtn.addEventListener('click', (e) => {
e.preventDefault();
this.handleSystemShutdown();
});
}
// System-Status
const statusBtn = document.getElementById('system-status-btn');
if (statusBtn) {
statusBtn.addEventListener('click', (e) => {
e.preventDefault();
this.showSystemStatus();
});
}
// Error-Recovery Toggle
const errorRecoveryBtn = document.getElementById('error-recovery-toggle-btn');
if (errorRecoveryBtn) {
errorRecoveryBtn.addEventListener('click', (e) => {
e.preventDefault();
this.toggleErrorRecovery();
});
}
}
async handleKioskRestart() {
const confirmed = await this.confirmOperation(
'Kiosk-Neustart',
'Möchten Sie das Kiosk-Display neustarten? Dies dauert ca. 10-30 Sekunden.',
'Der Kiosk wird neugestartet...'
);
if (!confirmed) return;
try {
const response = await fetch('/api/admin/kiosk/restart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: 10,
reason: 'Manueller Kiosk-Neustart über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Kiosk-Neustart geplant', 'success');
this.trackOperation(result.operation_id, 'kiosk_restart');
} else {
showNotification(result.error || 'Fehler beim Kiosk-Neustart', 'error');
}
} catch (error) {
console.error('Kiosk-Neustart Fehler:', error);
showNotification('Fehler beim Kiosk-Neustart: ' + error.message, 'error');
}
}
async handleSystemRestart() {
const confirmed = await this.confirmOperation(
'System-Neustart',
'WARNUNG: Das gesamte System wird neu gestartet! Dies dauert ca. 2-5 Minuten.',
'Das System wird neu gestartet...',
true
);
if (!confirmed) return;
// Zusätzliche Verzögerungsabfrage
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '60');
if (!delayInput) return;
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 60));
try {
const response = await fetch('/api/admin/system/restart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: delay,
reason: 'Manueller System-Neustart über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(`System-Neustart in ${delay} Sekunden geplant`, 'success');
this.trackOperation(result.operation_id, 'system_restart');
this.showCountdown(delay, 'System-Neustart');
} else {
showNotification(result.error || 'Fehler beim System-Neustart', 'error');
}
} catch (error) {
console.error('System-Neustart Fehler:', error);
showNotification('Fehler beim System-Neustart: ' + error.message, 'error');
}
}
async handleSystemShutdown() {
const confirmed = await this.confirmOperation(
'System-Shutdown',
'KRITISCHE WARNUNG: Das System wird komplett heruntergefahren!',
'Das System wird heruntergefahren...',
true
);
if (!confirmed) return;
// Doppelte Bestätigung
const doubleConfirm = confirm('LETZTE WARNUNG: System wirklich herunterfahren?\nDas System muss danach manuell neu gestartet werden!');
if (!doubleConfirm) return;
// Verzögerungsabfrage
const delayInput = prompt('Verzögerung in Sekunden (10-3600):', '30');
if (!delayInput) return;
const delay = Math.max(10, Math.min(3600, parseInt(delayInput) || 30));
try {
const response = await fetch('/api/admin/system/shutdown', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
delay_seconds: delay,
reason: 'Manueller System-Shutdown über Admin-Panel'
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(`System-Shutdown in ${delay} Sekunden geplant`, 'error');
this.trackOperation(result.operation_id, 'system_shutdown');
this.showCountdown(delay, 'System-Shutdown');
} else {
showNotification(result.error || 'Fehler beim System-Shutdown', 'error');
}
} catch (error) {
console.error('System-Shutdown Fehler:', error);
showNotification('Fehler beim System-Shutdown: ' + error.message, 'error');
}
}
async showSystemStatus() {
try {
showNotification('System-Status wird geladen...', 'info');
const response = await fetch('/api/admin/system/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
const status = await response.json();
if (response.ok && status.success) {
this.displaySystemStatusModal(status);
} else {
showNotification('Fehler beim Laden des System-Status', 'error');
}
} catch (error) {
console.error('System-Status Fehler:', error);
showNotification('Fehler beim System-Status: ' + error.message, 'error');
}
}
async toggleErrorRecovery() {
try {
// Aktuellen Status abrufen
const statusResponse = await fetch('/api/admin/error-recovery/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
const statusData = await statusResponse.json();
const currentlyActive = statusData?.statistics?.monitoring_active || false;
// Toggle-Operation
const response = await fetch('/api/admin/error-recovery/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
enable: !currentlyActive
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification(result.message, 'success');
this.updateErrorRecoveryButton(result.monitoring_active);
} else {
showNotification(result.error || 'Fehler beim Error-Recovery Toggle', 'error');
}
} catch (error) {
console.error('Error-Recovery Toggle Fehler:', error);
showNotification('Fehler beim Error-Recovery Toggle: ' + error.message, 'error');
}
}
updateErrorRecoveryButton(isActive) {
const btn = document.getElementById('error-recovery-toggle-btn');
if (btn) {
if (isActive) {
btn.classList.remove('bg-green-500', 'hover:bg-green-600');
btn.classList.add('bg-yellow-500', 'hover:bg-yellow-600');
btn.querySelector('span').textContent = 'Fehlerresilienz AN';
} else {
btn.classList.remove('bg-yellow-500', 'hover:bg-yellow-600');
btn.classList.add('bg-green-500', 'hover:bg-green-600');
btn.querySelector('span').textContent = 'Fehlerresilienz AUS';
}
}
}
async confirmOperation(title, message, processingMessage, isDestructive = false) {
const bgColor = isDestructive ? 'bg-red-100 dark:bg-red-900' : 'bg-blue-100 dark:bg-blue-900';
const iconColor = isDestructive ? 'text-red-600 dark:text-red-400' : 'text-blue-600 dark:text-blue-400';
return new Promise((resolve) => {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-md w-full">
<div class="text-center mb-6">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full ${bgColor} mb-4">
<svg class="h-6 w-6 ${iconColor}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
</div>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">${title}</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">${message}</p>
</div>
<div class="flex space-x-3">
<button id="modal-cancel" class="flex-1 px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors">
Abbrechen
</button>
<button id="modal-confirm" class="flex-1 px-4 py-2 ${isDestructive ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600'} text-white rounded-xl transition-colors">
Bestätigen
</button>
</div>
</div>
`;
document.body.appendChild(modal);
const cancelBtn = modal.querySelector('#modal-cancel');
const confirmBtn = modal.querySelector('#modal-confirm');
cancelBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(false);
});
confirmBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve(true);
});
// ESC-Key
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
resolve(false);
}
};
document.addEventListener('keydown', handleEsc);
});
}
showCountdown(seconds, operationType) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/75 backdrop-blur-sm z-50 flex items-center justify-center p-4';
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl max-w-sm w-full text-center">
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-orange-100 dark:bg-orange-900 mb-6">
<svg class="h-8 w-8 text-orange-600 dark:text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">${operationType}</h3>
<p class="text-slate-600 dark:text-slate-400 mb-6">Wird ausgeführt in:</p>
<div id="countdown-timer" class="text-4xl font-bold text-orange-600 dark:text-orange-400 mb-6">${seconds}</div>
<button id="countdown-cancel" class="px-6 py-2 bg-red-500 text-white rounded-xl hover:bg-red-600 transition-colors">
Abbrechen
</button>
</div>
`;
document.body.appendChild(modal);
const timerEl = modal.querySelector('#countdown-timer');
const cancelBtn = modal.querySelector('#countdown-cancel');
let remainingSeconds = seconds;
const interval = setInterval(() => {
remainingSeconds--;
timerEl.textContent = remainingSeconds;
if (remainingSeconds <= 0) {
clearInterval(interval);
document.body.removeChild(modal);
}
}, 1000);
cancelBtn.addEventListener('click', async () => {
clearInterval(interval);
document.body.removeChild(modal);
// Versuche Operation zu stornieren
try {
// Hier könnte der API-Call zum Stornieren implementiert werden
showNotification('Operation wird storniert...', 'info');
} catch (error) {
console.error('Fehler beim Stornieren:', error);
}
});
}
displaySystemStatusModal(status) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4';
const servicesHtml = Object.entries(status.services || {}).map(([name, state]) => {
const isActive = state === 'active';
return `
<div class="flex items-center justify-between py-2 px-3 rounded-lg ${isActive ? 'bg-green-50 dark:bg-green-900/20' : 'bg-red-50 dark:bg-red-900/20'}">
<span class="font-medium">${name}</span>
<span class="px-2 py-1 rounded-full text-xs ${isActive ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100'}">${state}</span>
</div>
`;
}).join('');
const metrics = status.system_metrics || {};
const errorRecovery = status.error_recovery || {};
modal.innerHTML = `
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 shadow-2xl max-w-2xl w-full max-h-90vh overflow-y-auto">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-slate-900 dark:text-white">System-Status</h3>
<button id="close-status-modal" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-300">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Services -->
<div>
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Services</h4>
<div class="space-y-2">
${servicesHtml}
</div>
</div>
<!-- System-Metriken -->
<div>
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">System-Metriken</h4>
<div class="space-y-2">
<div class="flex justify-between">
<span>Speicher:</span>
<span class="font-medium ${metrics.memory_percent > 80 ? 'text-red-600' : 'text-green-600'}">${metrics.memory_percent?.toFixed(1) || 0}%</span>
</div>
<div class="flex justify-between">
<span>Festplatte:</span>
<span class="font-medium ${metrics.disk_percent > 90 ? 'text-red-600' : 'text-green-600'}">${metrics.disk_percent?.toFixed(1) || 0}%</span>
</div>
<div class="flex justify-between">
<span>System-Last:</span>
<span class="font-medium">${metrics.load_average?.toFixed(2) || 0}</span>
</div>
</div>
</div>
<!-- Fehlerresilienz -->
<div class="md:col-span-2">
<h4 class="font-semibold text-slate-900 dark:text-white mb-3">Fehlerresilienz</h4>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-blue-600">${errorRecovery.total_errors || 0}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Gesamt-Fehler</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-orange-600">${errorRecovery.errors_last_24h || 0}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Letzten 24h</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold text-green-600">${errorRecovery.recovery_success_rate || 0}%</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Erfolgsrate</div>
</div>
<div class="text-center p-3 bg-slate-50 dark:bg-slate-700 rounded-lg">
<div class="text-2xl font-bold ${status.is_safe ? 'text-green-600' : 'text-red-600'}">${status.is_safe ? 'SICHER' : 'UNSICHER'}</div>
<div class="text-xs text-slate-600 dark:text-slate-400">Status</div>
</div>
</div>
</div>
</div>
<div class="mt-6 pt-4 border-t border-slate-200 dark:border-slate-600">
<div class="text-sm text-slate-500 dark:text-slate-400">
Letztes Update: ${new Date().toLocaleString('de-DE')}
</div>
</div>
</div>
`;
document.body.appendChild(modal);
const closeBtn = modal.querySelector('#close-status-modal');
closeBtn.addEventListener('click', () => {
document.body.removeChild(modal);
});
// ESC zum Schließen
const handleEsc = (e) => {
if (e.key === 'Escape') {
document.body.removeChild(modal);
document.removeEventListener('keydown', handleEsc);
}
};
document.addEventListener('keydown', handleEsc);
}
trackOperation(operationId, type) {
this.pendingOperations.set(operationId, {
id: operationId,
type: type,
timestamp: Date.now()
});
}
async startStatusPolling() {
// Überwache Error-Recovery-Status
setInterval(async () => {
try {
const response = await fetch('/api/admin/error-recovery/status', {
method: 'GET',
headers: {
'X-CSRFToken': getCsrfToken()
}
});
if (response.ok) {
const data = await response.json();
this.updateErrorRecoveryButton(data?.statistics?.monitoring_active || false);
}
} catch (error) {
console.debug('Status-Polling Fehler:', error);
}
}, 30000); // Alle 30 Sekunden
}
}
// Globaler System-Control-Manager
let systemControlManager = null;
// Initialisierung beim DOM-Laden
document.addEventListener('DOMContentLoaded', function() {
if (!systemControlManager) {
systemControlManager = new SystemControlManager();
}
});
</script>
{% endblock %}