feat: Implement frontend production deployment and enhance admin dashboard functionality
This commit is contained in:
@@ -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 %}
|
||||
|
@@ -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 %}
|
@@ -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 %}
|
@@ -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>
|
||||
|
@@ -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 %}
|
@@ -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>
|
||||
|
@@ -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 %}
|
@@ -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>
|
||||
|
35
backend/app/templates/errors/403.html
Normal file
35
backend/app/templates/errors/403.html
Normal 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 %}
|
47
backend/app/templates/errors/404.html
Normal file
47
backend/app/templates/errors/404.html
Normal 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 %}
|
66
backend/app/templates/errors/500.html
Normal file
66
backend/app/templates/errors/500.html
Normal 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 %}
|
@@ -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 %}
|
Reference in New Issue
Block a user