feat: Implement frontend production deployment and enhance admin dashboard functionality

This commit is contained in:
2025-05-26 21:54:13 +02:00
parent c2ea6c34ea
commit 7aa70cf976
59 changed files with 9161 additions and 10894 deletions

View File

@@ -8,17 +8,29 @@
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="{{ url_for('static', filename='js/admin.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/admin-system.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/admin-live.js') }}" defer></script>
{% endblock %}
{% block content %}
<!-- Modernes Admin Panel mit Tailwind CSS -->
<!-- Modernes Admin Panel mit Real-Time Updates -->
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<!-- Hero Header -->
<!-- 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>
@@ -40,26 +52,33 @@
<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">
Admin Panel
Admin Control Center
</span>
</h1>
<p class="text-xl md:text-2xl text-blue-100 max-w-3xl mx-auto leading-relaxed">
Verwalten Sie Ihr MYP-System mit modernster Technologie und Mercedes-Benz Qualität
Echtzeit-Verwaltung Ihres MYP-Systems mit modernster Technologie und Mercedes-Benz Qualität
</p>
<!-- Quick Actions -->
<!-- Real-Time Quick Actions -->
<div class="flex flex-wrap justify-center gap-4 mt-8">
<button 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">
<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 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">
<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>
Analytics
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>
@@ -68,9 +87,9 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10">
<!-- Stats Dashboard -->
<!-- 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">
<!-- Benutzer Stats -->
<!-- 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">
@@ -81,17 +100,21 @@
</svg>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_users }}</div>
<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 class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full" style="width: 75%"></div>
<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>
<!-- Drucker Stats -->
<!-- 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">
@@ -102,18 +125,22 @@
</svg>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_printers }}</div>
<div class="text-sm text-green-500">{{ stats.online_printers }} online</div>
<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="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Verbundene Drucker</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 class="bg-gradient-to-r from-green-500 to-green-600 h-2 rounded-full" style="width: 90%"></div>
<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>
<!-- Aktive Jobs Stats -->
<!-- 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">
@@ -124,18 +151,22 @@
</svg>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.active_jobs }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">{{ stats.queued_jobs }} in Warteschlange</div>
<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="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Laufende Druckaufträge</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 class="bg-gradient-to-r from-purple-500 to-purple-600 h-2 rounded-full" style="width: 60%"></div>
<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>
<!-- Erfolgsrate Stats -->
<!-- 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">
@@ -146,13 +177,24 @@
</svg>
</div>
<div class="text-right">
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.success_rate }}%</div>
<div class="text-sm text-green-500">+5% Verbesserung</div>
<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 class="bg-gradient-to-r from-orange-500 to-orange-600 h-2 rounded-full" style="width: {{ stats.success_rate }}%"></div>
<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>
@@ -172,7 +214,7 @@
<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="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 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"/>
<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>
Drucker
</a>
@@ -358,188 +400,6 @@
{% endfor %}
</div>
</div>
{% elif active_tab == 'jobs' %}
<!-- Jobs 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">Druckaufträge</h2>
<div class="flex space-x-3">
<select 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>Alle Status</option>
<option>Warteschlange</option>
<option>Druckt</option>
<option>Abgeschlossen</option>
<option>Fehler</option>
</select>
<button 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>
<!-- Jobs 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">Datei</th>
<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">Drucker</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">Fortschritt</th>
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Erstellt</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 job in jobs %}
<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-lg bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center">
<svg class="w-5 h-5 text-white" 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>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-slate-900 dark:text-white">{{ job.filename }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">{{ job.file_size_mb }} MB</div>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ job.user.username }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ job.printer.name if job.printer else 'Nicht zugewiesen' }}</td>
<td class="px-6 py-4 whitespace-nowrap">
{% set status_colors = {
'queued': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
'printing': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
'completed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
'failed': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
'cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'
} %}
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full {{ status_colors.get(job.status, 'bg-gray-100 text-gray-800') }}">
{{ job.status.title() }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="w-16 bg-slate-200 dark:bg-slate-600 rounded-full h-2 mr-2">
<div class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full" style="width: {{ job.progress }}%"></div>
</div>
<span class="text-sm text-slate-900 dark:text-white">{{ job.progress }}%</span>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
{{ job.created_at.strftime('%d.%m.%Y %H:%M') }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div class="flex space-x-2">
{% if job.status == 'queued' %}
<button class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors">
<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="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2-10h.01M5 20h14a2 2 0 002-2V7a2 2 0 00-2-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2zM9 9h6v6H9V9z"/>
</svg>
</button>
{% endif %}
{% if job.status in ['queued', 'printing'] %}
<button class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
{% endif %}
<button class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elif active_tab == 'system' %}
<!-- System Tab -->
<div class="p-8">
<h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-6">Systemverwaltung</h2>
<!-- System Status Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
<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">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Server Status</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 server-status">
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
Online
</span>
</div>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Uptime:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.uptime if system_info.uptime else 'Unbekannt' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">CPU:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu_usage if system_info.cpu_usage else 0 }}%</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">RAM:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory_usage if system_info.memory_usage else 0 }}%</span>
</div>
</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">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Datenbank</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 database-status">
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
Verbunden
</span>
</div>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Größe:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_size if system_info.db_size else 'Unbekannt' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Verbindungen:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_connections if system_info.db_connections else 'Unbekannt' }}</span>
</div>
</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">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Scheduler</h3>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 scheduler-status">
<span class="w-2 h-2 mr-1 rounded-full bg-blue-400 animate-pulse"></span>
{{ 'Läuft' if system_info.scheduler_running else 'Gestoppt' }}
</span>
</div>
<div class="space-y-2">
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Jobs:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.scheduler_jobs if system_info.scheduler_jobs else 0 }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-slate-600 dark:text-slate-400">Nächster Job:</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.next_job if system_info.next_job else 'Keine geplanten Jobs' }}</span>
</div>
</div>
</div>
</div>
<!-- System Actions -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@@ -574,7 +434,7 @@
</div>
</div>
</div>
{% elif active_tab == 'logs' %}
<!-- Logs Tab -->
<div class="p-8">
@@ -760,4 +620,4 @@ async function deleteUser(userId, userName) {
// Alle Funktionen sind bereits in admin-system.js definiert
</script>
{% endblock %}
{% endblock %}

View File

@@ -9,7 +9,7 @@
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<div class="mb-8">
@@ -22,135 +22,88 @@
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zur Druckerverwaltung
</a>
</div>
</div>
<!-- Formular -->
<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 p-8">
<form id="add-printer-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Druckername -->
<div>
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Druckername *
</label>
<input type="text" id="name" name="name" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. Prusa i3 MK3S+">
</div>
<!-- Modell -->
<div>
<label for="model" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Modell *
</label>
<input type="text" id="model" name="model" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. Prusa i3 MK3S+">
</div>
<!-- Standort -->
<div>
<label for="location" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Standort *
</label>
<input type="text" id="location" name="location" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. Labor A, Raum 101">
</div>
<!-- MAC-Adresse -->
<div>
<label for="mac_address" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
MAC-Adresse *
</label>
<input type="text" id="mac_address" name="mac_address" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. 00:11:22:33:44:55"
pattern="^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$">
</div>
<!-- Plug IP-Adresse -->
<div>
<label for="plug_ip" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Smart Plug IP-Adresse *
</label>
<input type="text" id="plug_ip" name="plug_ip" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. 192.168.1.100"
pattern="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</div>
<!-- Drucker IP-Adresse -->
<div>
<label for="printer_ip" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker IP-Adresse
</label>
<input type="text" id="printer_ip" name="printer_ip"
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="z.B. 192.168.1.101"
pattern="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</div>
<!-- Form -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
<form method="POST" action="{{ url_for('admin_create_printer_form') }}" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- Name -->
<div>
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker-Name
</label>
<input type="text" name="name" id="name" required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Prusa i3 MK3S+">
</div>
<!-- Erweiterte Einstellungen -->
<div class="border-t border-slate-200 dark:border-slate-700 pt-6">
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-4">Erweiterte Einstellungen</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Plug Benutzername -->
<div>
<label for="plug_username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Smart Plug Benutzername
</label>
<input type="text" id="plug_username" name="plug_username" value="admin"
class="w-full px-4 py-3 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 transition-all duration-300">
</div>
<!-- IP-Adresse -->
<div>
<label for="ip_address" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
IP-Adresse
</label>
<input type="text" name="ip_address" id="ip_address" required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="192.168.1.100"
pattern="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</div>
<!-- Plug Passwort -->
<div>
<label for="plug_password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Smart Plug Passwort
</label>
<input type="password" id="plug_password" name="plug_password" value="vT6Vsd^p"
class="w-full px-4 py-3 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 transition-all duration-300">
</div>
<!-- Modell -->
<div>
<label for="model" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker-Modell
</label>
<input type="text" name="model" id="model"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Prusa i3 MK3S+">
</div>
<!-- Status -->
<div>
<label for="status" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Anfangsstatus
</label>
<select id="status" name="status"
class="w-full px-4 py-3 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 transition-all duration-300">
<option value="available">Verfügbar</option>
<option value="offline">Offline</option>
<option value="maintenance">Wartung</option>
</select>
</div>
<!-- Standort -->
<div>
<label for="location" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Standort
</label>
<input type="text" name="location" id="location"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Werkstatt A, Regal 3">
</div>
<!-- Beschreibung -->
<div>
<label for="description" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Beschreibung
</label>
<textarea id="description" name="description" rows="3"
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Zusätzliche Informationen zum Drucker..."></textarea>
</div>
</div>
<!-- Beschreibung -->
<div>
<label for="description" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Beschreibung
</label>
<textarea name="description" id="description" rows="3"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Zusätzliche Informationen zum Drucker..."></textarea>
</div>
<!-- Status -->
<div>
<label for="status" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Status
</label>
<select name="status" id="status"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
<option value="available">Verfügbar</option>
<option value="maintenance">Wartung</option>
<option value="offline">Offline</option>
</select>
</div>
<!-- Buttons -->
<div class="flex justify-end space-x-4 pt-6">
<button type="button" onclick="window.history.back()"
class="px-6 py-3 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-all duration-300">
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ url_for('admin_page', tab='printers') }}"
class="px-6 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300">
Abbrechen
</button>
<button type="submit"
class="px-6 py-3 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">
</a>
<button type="submit"
class="px-6 py-3 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">
Drucker hinzufügen
</button>
</div>
@@ -158,92 +111,4 @@
</div>
</div>
</div>
<!-- Notification -->
<div id="notification" class="fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full hidden">
<span id="notification-message"></span>
</div>
<script>
// CSRF Token
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// Notification anzeigen
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
const messageEl = document.getElementById('notification-message');
const colors = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
messageEl.textContent = message;
notification.classList.remove('hidden');
notification.style.transform = 'translateX(0)';
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.classList.add('hidden'), 300);
}, 5000);
}
// Formular absenden
document.getElementById('add-printer-form').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
// API-Aufruf
try {
const response = await fetch('/api/printers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
name: data.name,
model: data.model,
location: data.location,
mac_address: data.mac_address,
plug_ip: data.plug_ip,
printer_ip: data.printer_ip,
plug_username: data.plug_username || 'admin',
plug_password: data.plug_password || 'vT6Vsd^p',
status: data.status || 'available',
description: data.description
})
});
const result = await response.json();
if (response.ok) {
showNotification('Drucker erfolgreich hinzugefügt', 'success');
setTimeout(() => {
window.location.href = '/admin-dashboard?tab=printers';
}, 2000);
} else {
showNotification(result.error || 'Fehler beim Hinzufügen des Druckers', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
});
// MAC-Adresse formatieren
document.getElementById('mac_address').addEventListener('input', function(e) {
let value = e.target.value.replace(/[^0-9A-Fa-f]/g, '');
let formatted = value.match(/.{1,2}/g)?.join(':') || value;
if (formatted.length > 17) formatted = formatted.substring(0, 17);
e.target.value = formatted;
});
</script>
{% endblock %}

View File

@@ -9,7 +9,7 @@
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<div class="mb-8">
@@ -22,111 +22,66 @@
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zur Benutzerverwaltung
</a>
</div>
</div>
<!-- Formular -->
<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 p-8">
<form id="add-user-form" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Benutzername -->
<div>
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Benutzername *
</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Benutzername eingeben">
</div>
<!-- E-Mail -->
<div>
<label for="email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
E-Mail-Adresse *
</label>
<input type="email" id="email" name="email" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="E-Mail-Adresse eingeben">
</div>
<!-- Vorname -->
<div>
<label for="first_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Vorname
</label>
<input type="text" id="first_name" name="first_name"
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Vorname eingeben">
</div>
<!-- Nachname -->
<div>
<label for="last_name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Nachname
</label>
<input type="text" id="last_name" name="last_name"
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Nachname eingeben">
</div>
<!-- Passwort -->
<div>
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Passwort *
</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Passwort eingeben">
</div>
<!-- Passwort bestätigen -->
<div>
<label for="password_confirm" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Passwort bestätigen *
</label>
<input type="password" id="password_confirm" name="password_confirm" required
class="w-full px-4 py-3 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 transition-all duration-300"
placeholder="Passwort bestätigen">
</div>
<!-- Form -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
<form method="POST" action="{{ url_for('admin_create_user_form') }}" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- E-Mail -->
<div>
<label for="email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
E-Mail-Adresse
</label>
<input type="email" name="email" id="email" required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="benutzer@mercedes-benz.com">
</div>
<!-- Rolle und Status -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Rolle -->
<div>
<label for="role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Rolle
</label>
<select id="role" name="role"
class="w-full px-4 py-3 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 transition-all duration-300">
<option value="user">Benutzer</option>
<option value="admin">Administrator</option>
</select>
</div>
<!-- Name -->
<div>
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Vollständiger Name
</label>
<input type="text" name="name" id="name"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Max Mustermann">
</div>
<!-- Status -->
<div>
<label for="status" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Status
</label>
<select id="status" name="status"
class="w-full px-4 py-3 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 transition-all duration-300">
<option value="active">Aktiv</option>
<option value="inactive">Inaktiv</option>
</select>
</div>
<!-- Passwort -->
<div>
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Passwort
</label>
<input type="password" name="password" id="password" required
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Sicheres Passwort">
</div>
<!-- Rolle -->
<div>
<label for="role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Benutzerrolle
</label>
<select name="role" id="role"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
<option value="user">Benutzer</option>
<option value="admin">Administrator</option>
</select>
</div>
<!-- Buttons -->
<div class="flex justify-end space-x-4 pt-6">
<button type="button" onclick="window.history.back()"
class="px-6 py-3 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-all duration-300">
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ url_for('admin_page', tab='users') }}"
class="px-6 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300">
Abbrechen
</button>
<button type="submit"
class="px-6 py-3 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">
</a>
<button type="submit"
class="px-6 py-3 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">
Benutzer erstellen
</button>
</div>
@@ -134,87 +89,4 @@
</div>
</div>
</div>
<!-- Notification -->
<div id="notification" class="fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full hidden">
<span id="notification-message"></span>
</div>
<script>
// CSRF Token
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// Notification anzeigen
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
const messageEl = document.getElementById('notification-message');
const colors = {
success: 'bg-green-500 text-white',
error: 'bg-red-500 text-white',
warning: 'bg-yellow-500 text-white',
info: 'bg-blue-500 text-white'
};
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
messageEl.textContent = message;
notification.classList.remove('hidden');
notification.style.transform = 'translateX(0)';
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.classList.add('hidden'), 300);
}, 5000);
}
// Formular absenden
document.getElementById('add-user-form').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
// Passwort-Validierung
if (data.password !== data.password_confirm) {
showNotification('Die Passwörter stimmen nicht überein', 'error');
return;
}
// API-Aufruf
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
name: data.username,
email: data.email,
first_name: data.first_name,
last_name: data.last_name,
password: data.password,
role: data.role,
status: data.status
})
});
const result = await response.json();
if (response.ok) {
showNotification('Benutzer erfolgreich erstellt', 'success');
setTimeout(() => {
window.location.href = '/admin-dashboard?tab=users';
}, 2000);
} else {
showNotification(result.error || 'Fehler beim Erstellen des Benutzers', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
});
</script>
{% endblock %}

View File

@@ -2,42 +2,105 @@
{% block title %}Benutzer bearbeiten - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Benutzer bearbeiten</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Bearbeiten Sie die Daten von {{ user.username }}</p>
<p class="text-slate-600 dark:text-slate-400 mt-2">Bearbeiten Sie die Daten von {{ user.name or user.email }}</p>
</div>
<a href="{{ url_for('admin_page', tab='users') }}" class="inline-flex items-center 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-all duration-300">
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zur Benutzerverwaltung
</a>
</div>
</div>
<!-- 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 p-8">
<div class="text-center py-12">
<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="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>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Benutzerbearbeitung</h3>
<p class="text-slate-500 dark:text-slate-400 mb-6">Diese Funktion wird in einer zukünftigen Version implementiert.</p>
<div class="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<p><strong>Benutzer-ID:</strong> {{ user.id }}</p>
<p><strong>Benutzername:</strong> {{ user.username }}</p>
<p><strong>E-Mail:</strong> {{ user.email }}</p>
<p><strong>Status:</strong> {{ 'Aktiv' if user.is_active else 'Inaktiv' }}</p>
<p><strong>Rolle:</strong> {{ 'Administrator' if user.is_admin else 'Benutzer' }}</p>
<!-- Form -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
<form method="POST" action="{{ url_for('admin_update_user_form', user_id=user.id) }}" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="_method" value="PUT"/>
<!-- E-Mail -->
<div>
<label for="email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
E-Mail-Adresse
</label>
<input type="email" name="email" id="email" required
value="{{ user.email }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="benutzer@mercedes-benz.com">
</div>
</div>
<!-- Name -->
<div>
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Vollständiger Name
</label>
<input type="text" name="name" id="name"
value="{{ user.name }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Max Mustermann">
</div>
<!-- Neues Passwort (optional) -->
<div>
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Neues Passwort (leer lassen, um beizubehalten)
</label>
<input type="password" name="password" id="password"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Neues Passwort">
</div>
<!-- Rolle -->
<div>
<label for="role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Benutzerrolle
</label>
<select name="role" id="role"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
<option value="user" {% if not user.is_admin %}selected{% endif %}>Benutzer</option>
<option value="admin" {% if user.is_admin %}selected{% endif %}>Administrator</option>
</select>
</div>
<!-- Status -->
<div>
<label for="is_active" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Benutzerstatus
</label>
<select name="is_active" id="is_active"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
<option value="true" {% if user.active %}selected{% endif %}>Aktiv</option>
<option value="false" {% if not user.active %}selected{% endif %}>Deaktiviert</option>
</select>
</div>
<!-- Buttons -->
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ url_for('admin_page', tab='users') }}"
class="px-6 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300">
Abbrechen
</a>
<button type="submit"
class="px-6 py-3 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">
Änderungen speichern
</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -2,6 +2,11 @@
{% block title %}Drucker verwalten - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@@ -10,37 +15,224 @@
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Drucker verwalten</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Verwaltung von {{ printer.name }}</p>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">{{ printer.name }} verwalten</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Verwaltung und Überwachung des Druckers</p>
</div>
<a href="{{ url_for('admin_page', tab='printers') }}" class="inline-flex items-center 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-all duration-300">
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zur Druckerverwaltung
</a>
</div>
</div>
<!-- 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 p-8">
<div class="text-center py-12">
<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="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 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>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Druckerverwaltung</h3>
<p class="text-slate-500 dark:text-slate-400 mb-6">Diese Funktion wird in einer zukünftigen Version implementiert.</p>
<div class="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<p><strong>Drucker-ID:</strong> {{ printer.id }}</p>
<p><strong>Name:</strong> {{ printer.name }}</p>
<p><strong>Modell:</strong> {{ printer.model }}</p>
<p><strong>Standort:</strong> {{ printer.location }}</p>
<p><strong>Status:</strong> {{ printer.status.title() }}</p>
<p><strong>MAC-Adresse:</strong> {{ printer.mac_address }}</p>
<p><strong>Plug IP:</strong> {{ printer.plug_ip }}</p>
<!-- Drucker-Info -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Status Card -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Status</h3>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Aktueller Status:</span>
<span class="px-3 py-1 rounded-full text-sm font-medium
{% if printer.status == 'available' %}bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200{% elif printer.status == 'busy' %}bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200{% elif printer.status == 'maintenance' %}bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200{% else %}bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200{% endif %}">
{{ printer.status|title }}
</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">IP-Adresse:</span>
<span class="text-slate-900 dark:text-white font-mono">{{ printer.ip_address }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Standort:</span>
<span class="text-slate-900 dark:text-white">{{ printer.location or 'Nicht angegeben' }}</span>
</div>
</div>
</div>
<!-- Aktionen Card -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Aktionen</h3>
<div class="space-y-3">
<button onclick="togglePrinter({{ printer.id }})"
class="w-full px-4 py-2 bg-blue-500 text-white rounded-xl hover:bg-blue-600 transition-all duration-300">
{% if printer.status == 'available' %}Deaktivieren{% else %}Aktivieren{% endif %}
</button>
<button onclick="testConnection({{ printer.id }})"
class="w-full px-4 py-2 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-all duration-300">
Verbindung testen
</button>
<a href="{{ url_for('admin_printer_settings_page', printer_id=printer.id) }}"
class="block w-full px-4 py-2 bg-slate-500 text-white rounded-xl hover:bg-slate-600 transition-all duration-300 text-center">
Einstellungen
</a>
</div>
</div>
<!-- Statistiken Card -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Statistiken</h3>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Gesamte Jobs:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="total-jobs">-</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Aktive Jobs:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="active-jobs">-</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Erfolgsrate:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="success-rate">-</span>
</div>
</div>
</div>
</div>
<!-- Aktuelle Jobs -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Aktuelle Jobs</h3>
<div id="current-jobs" class="space-y-4">
<div class="text-center text-slate-500 dark:text-slate-400 py-8">
Lade Jobs...
</div>
</div>
</div>
</div>
</div>
<script>
// CSRF Token
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// Notification anzeigen
function showNotification(message, type = 'info') {
if (type === 'success') {
alert('✓ ' + message);
} else if (type === 'error') {
alert('✗ ' + message);
} else {
alert(' ' + message);
}
}
// Drucker aktivieren/deaktivieren
async function togglePrinter(printerId) {
try {
const response = await fetch(`/api/admin/printers/${printerId}/toggle`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Drucker-Status erfolgreich geändert', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showNotification(result.error || 'Fehler beim Ändern des Drucker-Status', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Verbindung testen
async function testConnection(printerId) {
try {
const response = await fetch(`/api/printers/${printerId}/test`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok) {
if (result.connected) {
showNotification('Verbindung erfolgreich', 'success');
} else {
showNotification('Verbindung fehlgeschlagen', 'error');
}
} else {
showNotification(result.error || 'Fehler beim Testen der Verbindung', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Statistiken laden
async function loadStats() {
try {
const response = await fetch(`/api/printers/{{ printer.id }}/stats`);
const stats = await response.json();
if (response.ok) {
document.getElementById('total-jobs').textContent = stats.total_jobs || 0;
document.getElementById('active-jobs').textContent = stats.active_jobs || 0;
document.getElementById('success-rate').textContent = (stats.success_rate || 0) + '%';
}
} catch (error) {
console.error('Fehler beim Laden der Statistiken:', error);
}
}
// Jobs laden
async function loadJobs() {
try {
const response = await fetch(`/api/printers/{{ printer.id }}/jobs`);
const jobs = await response.json();
const container = document.getElementById('current-jobs');
if (response.ok && jobs.length > 0) {
container.innerHTML = jobs.map(job => `
<div class="border border-slate-200 dark:border-slate-700 rounded-xl p-4">
<div class="flex items-center justify-between">
<div>
<h4 class="font-medium text-slate-900 dark:text-white">${job.name}</h4>
<p class="text-sm text-slate-600 dark:text-slate-400">von ${job.user_name}</p>
</div>
<div class="text-right">
<span class="px-3 py-1 rounded-full text-sm font-medium
${job.status === 'running' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' :
job.status === 'pending' ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' :
'bg-slate-100 text-slate-800 dark:bg-slate-900 dark:text-slate-200'}">
${job.status}
</span>
</div>
</div>
</div>
`).join('');
} else {
container.innerHTML = '<div class="text-center text-slate-500 dark:text-slate-400 py-8">Keine aktiven Jobs</div>';
}
} catch (error) {
console.error('Fehler beim Laden der Jobs:', error);
document.getElementById('current-jobs').innerHTML = '<div class="text-center text-red-500 py-8">Fehler beim Laden der Jobs</div>';
}
}
// Beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
loadStats();
loadJobs();
// Alle 30 Sekunden aktualisieren
setInterval(() => {
loadStats();
loadJobs();
}, 30000);
});
</script>
{% endblock %}

View File

@@ -2,42 +2,117 @@
{% block title %}Drucker-Einstellungen - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Drucker-Einstellungen</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Einstellungen für {{ printer.name }}</p>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">{{ printer.name }} - Einstellungen</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Konfiguration und Einstellungen des Druckers</p>
</div>
<a href="{{ url_for('admin_page', tab='printers') }}" class="inline-flex items-center 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-all duration-300">
<a href="{{ url_for('admin_manage_printer_page', printer_id=printer.id) }}" class="inline-flex items-center 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-all duration-300">
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zur Verwaltung
</a>
</div>
</div>
<!-- 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 p-8">
<div class="text-center py-12">
<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="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>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Drucker-Einstellungen</h3>
<p class="text-slate-500 dark:text-slate-400 mb-6">Diese Funktion wird in einer zukünftigen Version implementiert.</p>
<div class="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<p><strong>Drucker:</strong> {{ printer.name }}</p>
<p><strong>Modell:</strong> {{ printer.model }}</p>
<p><strong>Standort:</strong> {{ printer.location }}</p>
<p><strong>Aktueller Status:</strong> {{ printer.status.title() }}</p>
<!-- Form -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
<form method="POST" action="{{ url_for('admin_update_printer_form', printer_id=printer.id) }}" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="_method" value="PUT"/>
<!-- Name -->
<div>
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker-Name
</label>
<input type="text" name="name" id="name" required
value="{{ printer.name }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Prusa i3 MK3S+">
</div>
</div>
<!-- IP-Adresse -->
<div>
<label for="ip_address" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
IP-Adresse
</label>
<input type="text" name="ip_address" id="ip_address" required
value="{{ printer.ip_address }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="192.168.1.100"
pattern="^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$">
</div>
<!-- Modell -->
<div>
<label for="model" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Drucker-Modell
</label>
<input type="text" name="model" id="model"
value="{{ printer.model or '' }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Prusa i3 MK3S+">
</div>
<!-- Standort -->
<div>
<label for="location" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Standort
</label>
<input type="text" name="location" id="location"
value="{{ printer.location or '' }}"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Werkstatt A, Regal 3">
</div>
<!-- Beschreibung -->
<div>
<label for="description" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Beschreibung
</label>
<textarea name="description" id="description" rows="3"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
placeholder="Zusätzliche Informationen zum Drucker...">{{ printer.description or '' }}</textarea>
</div>
<!-- Status -->
<div>
<label for="status" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Status
</label>
<select name="status" id="status"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
<option value="available" {% if printer.status == 'available' %}selected{% endif %}>Verfügbar</option>
<option value="maintenance" {% if printer.status == 'maintenance' %}selected{% endif %}>Wartung</option>
<option value="offline" {% if printer.status == 'offline' %}selected{% endif %}>Offline</option>
</select>
</div>
<!-- Buttons -->
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="{{ url_for('admin_manage_printer_page', printer_id=printer.id) }}"
class="px-6 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300">
Abbrechen
</a>
<button type="submit"
class="px-6 py-3 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">
Einstellungen speichern
</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -2,6 +2,11 @@
{% block title %}Admin-Einstellungen - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<meta name="csrf-token" content="{{ csrf_token() }}">
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@@ -10,39 +15,362 @@
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">System-Einstellungen</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Konfiguration des MYP-Systems</p>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Admin-Einstellungen</h1>
<p class="text-slate-600 dark:text-slate-400 mt-2">Systemkonfiguration und Verwaltungsoptionen</p>
</div>
<a href="{{ url_for('admin_page', tab='system') }}" class="inline-flex items-center 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-all duration-300">
<a href="{{ url_for('admin_page') }}" class="inline-flex items-center 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-all duration-300">
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Zurück
Zurück zum Dashboard
</a>
</div>
</div>
<!-- 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 p-8">
<div class="text-center py-12">
<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="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>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">System-Einstellungen</h3>
<p class="text-slate-500 dark:text-slate-400 mb-6">Diese Funktion wird in einer zukünftigen Version implementiert.</p>
<div class="space-y-2 text-sm text-slate-600 dark:text-slate-400">
<p>Hier können Sie verschiedene Systemeinstellungen konfigurieren:</p>
<ul class="list-disc list-inside space-y-1 mt-4">
<li>Allgemeine Systemkonfiguration</li>
<li>Sicherheitseinstellungen</li>
<li>Netzwerkeinstellungen</li>
<li>Backup-Konfiguration</li>
<li>Logging-Einstellungen</li>
</ul>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- System-Wartung -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">System-Wartung</h3>
<div class="space-y-4">
<button onclick="clearCache()"
class="w-full px-4 py-3 bg-blue-500 text-white rounded-xl hover:bg-blue-600 transition-all duration-300">
Cache leeren
</button>
<button onclick="optimizeDatabase()"
class="w-full px-4 py-3 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-all duration-300">
Datenbank optimieren
</button>
<button onclick="createBackup()"
class="w-full px-4 py-3 bg-purple-500 text-white rounded-xl hover:bg-purple-600 transition-all duration-300">
Backup erstellen
</button>
</div>
</div>
<!-- Drucker-Verwaltung -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Drucker-Verwaltung</h3>
<div class="space-y-4">
<button onclick="updatePrinters()"
class="w-full px-4 py-3 bg-orange-500 text-white rounded-xl hover:bg-orange-600 transition-all duration-300">
Drucker-Status aktualisieren
</button>
<button onclick="testAllPrinters()"
class="w-full px-4 py-3 bg-teal-500 text-white rounded-xl hover:bg-teal-600 transition-all duration-300">
Alle Drucker testen
</button>
</div>
</div>
<!-- System-Informationen -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">System-Informationen</h3>
<div class="space-y-3" id="system-info">
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Server-Status:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="server-status">Lade...</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Datenbank:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="db-status">Lade...</span>
</div>
<div class="flex items-center justify-between">
<span class="text-slate-600 dark:text-slate-400">Uptime:</span>
<span class="text-slate-900 dark:text-white font-semibold" id="uptime">Lade...</span>
</div>
</div>
</div>
<!-- Logs und Überwachung -->
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Logs und Überwachung</h3>
<div class="space-y-4">
<button onclick="downloadLogs()"
class="w-full px-4 py-3 bg-slate-500 text-white rounded-xl hover:bg-slate-600 transition-all duration-300">
Logs herunterladen
</button>
<button onclick="runMaintenance()"
class="w-full px-4 py-3 bg-indigo-500 text-white rounded-xl hover:bg-indigo-600 transition-all duration-300">
Wartung ausführen
</button>
</div>
</div>
</div>
<!-- Erweiterte Einstellungen -->
<div class="mt-8 bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-6">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Erweiterte Einstellungen</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Automatische Backup-Intervall (Stunden)
</label>
<input type="number" id="backup-interval" min="1" max="168" value="24"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Maximale Job-Laufzeit (Stunden)
</label>
<input type="number" id="max-job-time" min="1" max="72" value="12"
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
</div>
</div>
<div class="mt-6 flex justify-end">
<button onclick="saveSettings()"
class="px-6 py-3 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">
Einstellungen speichern
</button>
</div>
</div>
</div>
</div>
<script>
// CSRF Token
function getCsrfToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// Notification anzeigen
function showNotification(message, type = 'info') {
if (type === 'success') {
alert('✓ ' + message);
} else if (type === 'error') {
alert('✗ ' + message);
} else {
alert(' ' + message);
}
}
// Cache leeren
async function clearCache() {
if (!confirm('Möchten Sie den Cache wirklich leeren?')) return;
try {
const response = await fetch('/api/admin/cache/clear', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Cache erfolgreich geleert', 'success');
} else {
showNotification(result.error || 'Fehler beim Leeren des Cache', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Datenbank optimieren
async function optimizeDatabase() {
if (!confirm('Möchten Sie die Datenbank optimieren? Dies kann einige Minuten dauern.')) return;
try {
const response = await fetch('/api/admin/database/optimize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Datenbank erfolgreich optimiert', 'success');
} else {
showNotification(result.error || 'Fehler bei der Datenbankoptimierung', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Backup erstellen
async function createBackup() {
if (!confirm('Möchten Sie ein Backup erstellen?')) return;
try {
const response = await fetch('/api/admin/backup/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Backup erfolgreich erstellt', 'success');
} else {
showNotification(result.error || 'Fehler beim Erstellen des Backups', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Drucker aktualisieren
async function updatePrinters() {
try {
const response = await fetch('/api/admin/printers/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Drucker-Status erfolgreich aktualisiert', 'success');
} else {
showNotification(result.error || 'Fehler beim Aktualisieren der Drucker', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Alle Drucker testen
async function testAllPrinters() {
try {
const response = await fetch('/api/printers/status');
const printers = await response.json();
if (response.ok) {
const onlineCount = printers.filter(p => p.status === 'available').length;
showNotification(`${onlineCount} von ${printers.length} Druckern sind online`, 'info');
} else {
showNotification('Fehler beim Testen der Drucker', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Logs herunterladen
async function downloadLogs() {
try {
const response = await fetch('/api/admin/logs/download');
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'myp-logs.zip';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
showNotification('Logs werden heruntergeladen', 'success');
} else {
showNotification('Fehler beim Herunterladen der Logs', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Wartung ausführen
async function runMaintenance() {
if (!confirm('Möchten Sie die Systemwartung ausführen?')) return;
try {
const response = await fetch('/api/admin/maintenance/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
}
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Wartung erfolgreich ausgeführt', 'success');
} else {
showNotification(result.error || 'Fehler bei der Wartung', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// Einstellungen speichern
async function saveSettings() {
const backupInterval = document.getElementById('backup-interval').value;
const maxJobTime = document.getElementById('max-job-time').value;
try {
const response = await fetch('/api/admin/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken()
},
body: JSON.stringify({
backup_interval: parseInt(backupInterval),
max_job_time: parseInt(maxJobTime)
})
});
const result = await response.json();
if (response.ok && result.success) {
showNotification('Einstellungen erfolgreich gespeichert', 'success');
} else {
showNotification(result.error || 'Fehler beim Speichern der Einstellungen', 'error');
}
} catch (error) {
showNotification('Netzwerkfehler: ' + error.message, 'error');
}
}
// System-Informationen laden
async function loadSystemInfo() {
try {
const [systemResponse, dbResponse] = await Promise.all([
fetch('/api/admin/system/status'),
fetch('/api/admin/database/status')
]);
const systemData = await systemResponse.json();
const dbData = await dbResponse.json();
if (systemResponse.ok) {
document.getElementById('server-status').textContent = systemData.status || 'Online';
document.getElementById('uptime').textContent = systemData.uptime || 'Unbekannt';
}
if (dbResponse.ok) {
document.getElementById('db-status').textContent = dbData.connected ? 'Verbunden' : 'Getrennt';
}
} catch (error) {
console.error('Fehler beim Laden der System-Informationen:', error);
document.getElementById('server-status').textContent = 'Fehler';
document.getElementById('db-status').textContent = 'Fehler';
document.getElementById('uptime').textContent = 'Fehler';
}
}
// Beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
loadSystemInfo();
// Alle 30 Sekunden aktualisieren
setInterval(loadSystemInfo, 30000);
});
</script>
{% endblock %}

View File

@@ -157,13 +157,13 @@
<!-- Dark Mode Toggle -->
<button
id="darkModeToggle"
class="dark-mode-toggle"
class="dark-mode-toggle p-2 sm:p-3"
aria-label="Dark Mode umschalten"
aria-pressed="false"
data-action="toggle-dark-mode"
title="Dark Mode aktivieren"
>
<svg class="w-4 h-4 sm:w-5 sm:h-5 transition-all duration-300 transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<svg class="w-6 h-6 sm:w-7 sm:h-7 transition-all duration-300 transform" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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" />
</svg>
</button>

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}403 - Zugriff verweigert{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-8 text-center">
<div class="mb-6">
<div class="text-6xl text-red-500 mb-4">🚫</div>
<h1 class="text-3xl font-bold text-gray-800 mb-2">403</h1>
<h2 class="text-xl text-gray-600 mb-4">Zugriff verweigert</h2>
</div>
<div class="mb-6">
<p class="text-gray-600 mb-4">
Sie haben keine Berechtigung, auf diese Seite zuzugreifen.
</p>
<p class="text-sm text-gray-500">
Falls Sie glauben, dass dies ein Fehler ist, wenden Sie sich an einen Administrator.
</p>
</div>
<div class="space-y-3">
<a href="{{ url_for('index') }}"
class="block w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Zur Startseite
</a>
<button onclick="history.back()"
class="block w-full bg-gray-500 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors">
Zurück
</button>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}404 - Seite nicht gefunden - Mercedes-Benz MYP Platform{% endblock %}
{% block content %}
<div class="min-h-[80vh] flex flex-col items-center justify-center p-4">
<!-- 404 Error Container -->
<div class="w-full max-w-md">
<div class="bg-white dark:bg-gray-800 backdrop-blur-xl bg-opacity-95 dark:bg-opacity-95 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-700 p-8 text-center transition-all duration-300">
<!-- Mercedes-Benz Logo -->
<div class="flex justify-center mb-6">
<div class="w-16 h-16 text-gray-300 dark:text-gray-600 transition-transform duration-500 hover:scale-110">
<svg class="w-full h-full" fill="currentColor" viewBox="0 0 80 80">
<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>
</div>
<!-- Error Message -->
<h1 class="text-6xl font-bold text-gray-300 dark:text-gray-600 mb-4">404</h1>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Seite nicht gefunden</h2>
<p class="text-gray-600 dark:text-gray-400 mb-6">Die von Ihnen gesuchte Seite existiert nicht oder wurde verschoben.</p>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row justify-center gap-4 mt-8">
<a href="{{ url_for('dashboard') }}" class="inline-flex items-center justify-center px-5 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium rounded-lg transition-all duration-300 transform hover:-translate-y-0.5">
<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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
<span>Zum Dashboard</span>
</a>
<button onclick="window.history.back()" class="inline-flex items-center justify-center px-5 py-3 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 font-medium rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-300 transform hover:-translate-y-0.5">
<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 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
<span>Zurück</span>
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block title %}Interner Serverfehler - Mercedes-Benz MYP Platform{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-red-50 to-orange-50 dark:from-slate-900 dark:via-red-900/20 dark:to-orange-900/20 flex items-center justify-center px-4">
<div class="max-w-2xl w-full text-center">
<!-- Error Icon -->
<div class="mb-8">
<div class="inline-flex items-center justify-center w-24 h-24 bg-red-100 dark:bg-red-900/30 rounded-full mb-6">
<svg class="w-12 h-12 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
</div>
<!-- Error Message -->
<h1 class="text-6xl font-bold text-slate-900 dark:text-white mb-4">500</h1>
<h2 class="text-2xl font-semibold text-slate-700 dark:text-slate-300 mb-6">Interner Serverfehler</h2>
<p class="text-lg text-slate-600 dark:text-slate-400 mb-8 max-w-lg mx-auto">
Es ist ein unerwarteter Fehler aufgetreten. Unser Team wurde automatisch benachrichtigt und arbeitet an einer Lösung.
</p>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('dashboard') }}" class="inline-flex items-center px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-xl transition-colors duration-200 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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
Zurück zum Dashboard
</a>
<button onclick="window.location.reload()" class="inline-flex items-center px-6 py-3 bg-slate-200 hover:bg-slate-300 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium rounded-xl transition-colors duration-200">
<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>
Seite neu laden
</button>
</div>
<!-- Additional Info -->
<div class="mt-12 p-6 bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl border border-slate-200 dark:border-slate-700">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-3">Was können Sie tun?</h3>
<ul class="text-left text-slate-600 dark:text-slate-400 space-y-2">
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Versuchen Sie, die Seite neu zu laden
</li>
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Kehren Sie zum Dashboard zurück
</li>
<li class="flex items-start">
<svg class="w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0" 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>
Kontaktieren Sie den Administrator, falls das Problem weiterhin besteht
</li>
</ul>
</div>
</div>
</div>
{% endblock %}

View File

@@ -36,6 +36,7 @@
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Lade Drucker...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies sollte nur wenige Sekunden dauern</p>
</div>
</div>
</div>
@@ -138,11 +139,56 @@
</div>
{% endblock %}
{% block extra_js %}
{% block scripts %}
<script>
// Globale Variablen für die Drucker-Verwaltung
let printers = [];
// Refresh printers mit Status-Check - Make it globally available
function refreshPrinters() {
console.log('refreshPrinters function called');
const grid = document.getElementById('printers-grid');
const refreshBtn = document.querySelector('button[onclick="refreshPrinters()"]');
// Button deaktivieren und Loading-State anzeigen
if (refreshBtn) {
refreshBtn.disabled = true;
refreshBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Überprüfe Status...
`;
}
// Loading-State im Grid anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Überprüfe Drucker-Status...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies kann bis zu 7 Sekunden dauern</p>
</div>
`;
// Drucker laden mit Status-Check
loadPrintersWithStatusCheck().finally(() => {
// Button wieder aktivieren
if (refreshBtn) {
refreshBtn.disabled = false;
refreshBtn.innerHTML = `
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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
`;
}
});
}
// Make function globally available
window.refreshPrinters = refreshPrinters;
// Modal-Funktionen
function showAddPrinterModal() {
document.getElementById('addPrinterModal').classList.remove('hidden');
@@ -153,6 +199,10 @@
document.getElementById('addPrinterForm').reset();
}
// Make modal functions globally available
window.showAddPrinterModal = showAddPrinterModal;
window.hideAddPrinterModal = hideAddPrinterModal;
function showPrinterDetail(printerId) {
const printer = printers.find(p => p.id === printerId);
if (!printer) return;
@@ -223,17 +273,68 @@
// Load printers (schnelles Laden ohne Status-Check)
async function loadPrinters() {
const grid = document.getElementById('printers-grid');
// Loading-State anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Lade Drucker...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies sollte nur wenige Sekunden dauern</p>
</div>
`;
try {
const response = await fetch('/api/printers');
// Erstelle einen AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 Sekunden Timeout
const response = await fetch('/api/printers', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error('Fehler beim Laden der Drucker');
if (response.status === 408) {
throw new Error('Timeout beim Laden der Drucker. Bitte versuchen Sie es erneut.');
}
throw new Error(`Server-Fehler: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Prüfe auf Server-seitige Fehler
if (data.error) {
throw new Error(data.error);
}
// Verwende die korrekten Daten aus der neuen API-Antwort
printers = data.printers || [];
console.log(`Successfully loaded ${printers.length} printers (ohne Status-Check)`);
renderPrinters();
// Zeige Erfolgsmeldung nur wenn Drucker vorhanden sind
if (printers.length > 0) {
showStatusMessage(`${printers.length} Drucker erfolgreich geladen (ohne aktuellen Status)`, 'success');
}
} catch (error) {
console.error('Error loading printers:', error);
showError('Fehler beim Laden der Drucker');
// Spezielle Behandlung für verschiedene Fehlertypen
let errorMessage = 'Fehler beim Laden der Drucker';
if (error.name === 'AbortError') {
errorMessage = 'Timeout beim Laden der Drucker. Die Anfrage dauerte zu lange.';
} else if (error.message) {
errorMessage = error.message;
}
showError(errorMessage);
}
}
@@ -394,10 +495,25 @@
<svg class="h-12 w-12 sm:h-16 sm:w-16 text-red-500 dark:text-red-400 mx-auto mb-3 sm:mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="text-slate-700 dark:text-slate-300 text-base sm:text-lg">${message}</p>
<button onclick="refreshPrinters()" class="mt-3 sm:mt-4 bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white px-4 sm:px-6 py-1.5 sm:py-2 rounded-lg transition-all duration-200 text-sm sm:text-base">
Erneut versuchen
</button>
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Drucker konnten nicht geladen werden</h3>
<p class="text-slate-700 dark:text-slate-300 text-sm sm:text-base mb-4 max-w-md mx-auto">${message}</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center items-center">
<button onclick="loadPrinters()" class="bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-600 dark:hover:bg-indigo-500 text-white px-4 sm:px-6 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
Erneut versuchen
</button>
<button onclick="refreshPrinters()" class="bg-green-600 hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-500 text-white px-4 sm:px-6 py-2 rounded-lg transition-all duration-200 text-sm sm:text-base flex items-center">
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
Mit Status-Check
</button>
</div>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-3">
Tipp: "Mit Status-Check" dauert länger, überprüft aber die Verfügbarkeit aller Drucker
</p>
</div>
`;
}
@@ -444,8 +560,8 @@
</svg>
<span class="text-sm font-medium">${message}</span>
<button onclick="this.parentElement.remove()" class="ml-2 text-current hover:opacity-75">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
<svg class="h-4 w-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"></path>
</svg>
</button>
`;
@@ -466,38 +582,112 @@
event.preventDefault();
const form = document.getElementById('addPrinterForm');
const submitBtn = form.querySelector('button[type="submit"]');
const formData = new FormData(form);
// Deaktiviere Submit Button während der Verarbeitung
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Wird hinzugefügt...
`;
// Sammle und validiere Formulardaten
const printerData = {
name: formData.get('name'),
model: formData.get('model'),
location: formData.get('location'),
mac_address: formData.get('mac_address'),
plug_ip: formData.get('plug_ip'),
plug_username: formData.get('plug_username'),
plug_password: formData.get('plug_password')
name: formData.get('name')?.trim(),
model: formData.get('model')?.trim(),
location: formData.get('location')?.trim(),
mac_address: formData.get('mac_address')?.trim().toUpperCase(),
plug_ip: formData.get('plug_ip')?.trim(),
plug_username: formData.get('plug_username')?.trim(),
plug_password: formData.get('plug_password') // Passwort nicht trimmen
};
// Client-seitige Validierung
const errors = [];
if (!printerData.name) errors.push('Name ist erforderlich');
if (!printerData.model) errors.push('Modell ist erforderlich');
if (!printerData.location) errors.push('Standort ist erforderlich');
if (!printerData.mac_address) errors.push('MAC-Adresse ist erforderlich');
if (!printerData.plug_ip) errors.push('Plug IP ist erforderlich');
if (!printerData.plug_username) errors.push('Plug Benutzername ist erforderlich');
if (!printerData.plug_password) errors.push('Plug Passwort ist erforderlich');
// MAC-Adresse Format validieren
if (printerData.mac_address && !/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/.test(printerData.mac_address)) {
// Versuche automatische Formatierung
const cleanMac = printerData.mac_address.replace(/[^0-9A-F]/g, '');
if (cleanMac.length === 12) {
printerData.mac_address = cleanMac.match(/.{2}/g).join(':');
} else {
errors.push('MAC-Adresse muss im Format AA:BB:CC:DD:EE:FF sein');
}
}
// IP-Adresse Format validieren
if (printerData.plug_ip && !/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(printerData.plug_ip)) {
errors.push('IP-Adresse muss im Format 192.168.1.100 sein');
}
if (errors.length > 0) {
showStatusMessage(`Bitte korrigieren Sie folgende Fehler:\n${errors.join('\n')}`, 'error');
// Button wieder aktivieren
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
return;
}
try {
// Erstelle AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 Sekunden Timeout
const response = await fetch('/api/printers/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(printerData)
body: JSON.stringify(printerData),
signal: controller.signal
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Fehler beim Hinzufügen des Druckers');
}
clearTimeout(timeoutId);
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || `Server-Fehler: ${response.status}`);
}
// Erfolg!
hideAddPrinterModal();
showStatusMessage(result.message || 'Drucker erfolgreich hinzugefügt', 'success');
loadPrinters();
// Drucker-Liste neu laden
await loadPrinters();
console.log('Printer added successfully:', result.printer);
} catch (error) {
console.error('Error adding printer:', error);
showStatusMessage('Fehler beim Hinzufügen des Druckers: ' + error.message, 'error');
let errorMessage = 'Fehler beim Hinzufügen des Druckers';
if (error.name === 'AbortError') {
errorMessage = 'Timeout beim Hinzufügen des Druckers. Bitte versuchen Sie es erneut.';
} else if (error.message) {
errorMessage = error.message;
}
showStatusMessage(errorMessage, 'error');
} finally {
// Button wieder aktivieren
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
}
}
@@ -530,66 +720,44 @@
}
}
// Refresh printers mit Status-Check
function refreshPrinters() {
const grid = document.getElementById('printers-grid');
const refreshBtn = document.querySelector('button[onclick="refreshPrinters()"]');
// Button deaktivieren und Loading-State anzeigen
if (refreshBtn) {
refreshBtn.disabled = true;
refreshBtn.innerHTML = `
<svg class="animate-spin h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" class="opacity-75"></path>
</svg>
Überprüfe Status...
`;
}
// Loading-State im Grid anzeigen
grid.innerHTML = `
<div class="col-span-full text-center py-6 sm:py-12">
<div class="animate-spin rounded-full h-8 w-8 sm:h-12 sm:w-12 border-b-2 border-indigo-600 dark:border-indigo-400 mx-auto"></div>
<p class="mt-3 sm:mt-4 text-sm sm:text-base text-slate-600 dark:text-slate-400">Überprüfe Drucker-Status...</p>
<p class="mt-1 text-xs text-slate-500 dark:text-slate-500">Dies kann bis zu 7 Sekunden dauern</p>
</div>
`;
// Drucker laden mit Status-Check
loadPrintersWithStatusCheck().finally(() => {
// Button wieder aktivieren
if (refreshBtn) {
refreshBtn.disabled = false;
refreshBtn.innerHTML = `
<svg class="h-4 w-4 sm:h-5 sm:w-5 mr-1 sm:mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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
`;
}
});
}
// Erweiterte Funktion zum Laden der Drucker mit Status-Check
async function loadPrintersWithStatusCheck() {
try {
const response = await fetch('/api/printers/status');
// Erstelle einen AbortController für Timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 Sekunden Timeout für Status-Check
const response = await fetch('/api/printers/status', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error('Fehler beim Laden der Drucker-Status');
if (response.status === 408) {
throw new Error('Timeout beim Status-Check der Drucker. Versuchen Sie es später erneut.');
}
throw new Error(`Fehler beim Laden der Drucker-Status: ${response.status} ${response.statusText}`);
}
const statusData = await response.json();
// Prüfe ob statusData ein Array ist
if (!Array.isArray(statusData)) {
throw new Error('Ungültige Antwort vom Server');
console.error('Invalid response from /api/printers/status:', statusData);
throw new Error('Ungültige Antwort vom Server (erwartet Array, erhalten: ' + typeof statusData + ')');
}
// Drucker-Daten mit Status-Informationen anreichern
printers = statusData.map(printer => ({
...printer,
// Status ist bereits korrekt gemappt vom Backend
status: printer.status || 'offline'
status: printer.status || 'offline',
last_checked: printer.last_checked || new Date().toISOString()
}));
renderPrinters();
@@ -670,5 +838,23 @@
}
});
});
// Make all functions globally available for onclick handlers
window.showPrinterDetail = showPrinterDetail;
window.hidePrinterDetailModal = hidePrinterDetailModal;
window.deletePrinter = deletePrinter;
window.loadPrinters = loadPrinters;
window.handleAddPrinter = handleAddPrinter;
// Debug: Log that functions are available
console.log('All printer functions loaded and available globally:', {
refreshPrinters: typeof window.refreshPrinters,
showAddPrinterModal: typeof window.showAddPrinterModal,
hideAddPrinterModal: typeof window.hideAddPrinterModal,
showPrinterDetail: typeof window.showPrinterDetail,
hidePrinterDetailModal: typeof window.hidePrinterDetailModal,
deletePrinter: typeof window.deletePrinter,
loadPrinters: typeof window.loadPrinters
});
</script>
{% endblock %}