📝 🚀 "Refactor backend templates
This commit is contained in:
parent
20e81b11d3
commit
dfad0937d1
@ -692,7 +692,7 @@ def api_callback():
|
|||||||
username=user_data['username'],
|
username=user_data['username'],
|
||||||
email=user_data['email'],
|
email=user_data['email'],
|
||||||
name=user_data['name'],
|
name=user_data['name'],
|
||||||
is_admin=False,
|
role="user",
|
||||||
oauth_provider=provider,
|
oauth_provider=provider,
|
||||||
oauth_id=str(user_data['id'])
|
oauth_id=str(user_data['id'])
|
||||||
)
|
)
|
||||||
@ -777,7 +777,7 @@ def api_callback():
|
|||||||
username=user_data['username'],
|
username=user_data['username'],
|
||||||
email=user_data['email'],
|
email=user_data['email'],
|
||||||
name=user_data['name'],
|
name=user_data['name'],
|
||||||
is_admin=False,
|
role="user",
|
||||||
oauth_provider=provider,
|
oauth_provider=provider,
|
||||||
oauth_id=str(user_data['id'])
|
oauth_id=str(user_data['id'])
|
||||||
)
|
)
|
||||||
@ -2570,6 +2570,45 @@ def admin_update_printer_form(printer_id):
|
|||||||
flash("Fehler beim Aktualisieren des Druckers.", "error")
|
flash("Fehler beim Aktualisieren des Druckers.", "error")
|
||||||
return redirect(url_for("admin_edit_printer_page", printer_id=printer_id))
|
return redirect(url_for("admin_edit_printer_page", printer_id=printer_id))
|
||||||
|
|
||||||
|
@app.route("/api/admin/users/<int:user_id>", methods=["DELETE"])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def delete_user(user_id):
|
||||||
|
"""Löscht einen Benutzer (nur für Admins)."""
|
||||||
|
# Verhindern, dass sich der Admin selbst löscht
|
||||||
|
if user_id == current_user.id:
|
||||||
|
return jsonify({"error": "Sie können sich nicht selbst löschen"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
db_session = get_db_session()
|
||||||
|
|
||||||
|
user = db_session.get(User, user_id)
|
||||||
|
if not user:
|
||||||
|
db_session.close()
|
||||||
|
return jsonify({"error": "Benutzer nicht gefunden"}), 404
|
||||||
|
|
||||||
|
# Prüfen, ob noch aktive Jobs für diesen Benutzer existieren
|
||||||
|
active_jobs = db_session.query(Job).filter(
|
||||||
|
Job.user_id == user_id,
|
||||||
|
Job.status.in_(["scheduled", "running"])
|
||||||
|
).count()
|
||||||
|
|
||||||
|
if active_jobs > 0:
|
||||||
|
db_session.close()
|
||||||
|
return jsonify({"error": f"Benutzer kann nicht gelöscht werden: {active_jobs} aktive Jobs vorhanden"}), 400
|
||||||
|
|
||||||
|
username = user.username or user.email
|
||||||
|
db_session.delete(user)
|
||||||
|
db_session.commit()
|
||||||
|
db_session.close()
|
||||||
|
|
||||||
|
user_logger.info(f"Benutzer '{username}' (ID: {user_id}) gelöscht von Admin {current_user.id}")
|
||||||
|
return jsonify({"success": True, "message": "Benutzer erfolgreich gelöscht"})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
user_logger.error(f"Fehler beim Löschen des Benutzers {user_id}: {str(e)}")
|
||||||
|
return jsonify({"error": "Interner Serverfehler"}), 500
|
||||||
|
|
||||||
|
|
||||||
# ===== FILE-UPLOAD-ROUTEN =====
|
# ===== FILE-UPLOAD-ROUTEN =====
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
1
backend/docs/FEHLER_BEHOBEN_USER_DELETE.md
Normal file
1
backend/docs/FEHLER_BEHOBEN_USER_DELETE.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -21,3 +21,4 @@
|
|||||||
2025-06-01 01:16:18 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
2025-06-01 01:16:18 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||||
2025-06-01 01:19:40 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
2025-06-01 01:19:40 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||||
2025-06-01 01:23:29 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
2025-06-01 01:23:29 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||||
|
2025-06-01 01:31:25 - myp.jobs - INFO - Jobs abgerufen: 0 von 0 (Seite 1)
|
||||||
|
@ -2747,3 +2747,60 @@
|
|||||||
2025-06-01 01:30:30 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
2025-06-01 01:30:30 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
2025-06-01 01:30:31 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
2025-06-01 01:30:31 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
2025-06-01 01:30:31 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
2025-06-01 01:30:31 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:01 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:01 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:02 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:02 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:15 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||||
|
2025-06-01 01:31:15 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:15 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:15 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||||
|
2025-06-01 01:31:25 - myp.printers - INFO - Schnelles Laden abgeschlossen: 6 Drucker geladen (ohne Status-Check)
|
||||||
|
2025-06-01 01:31:39 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:39 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:46 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:46 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:50 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:50 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:52 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:52 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:57 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:57 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:31:59 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:31:59 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:32:29 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:32:29 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:32:59 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:32:59 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:33:20 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:33:20 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:33:21 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:33:21 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:33:24 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:33:24 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:33:50 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:33:50 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:34:20 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:34:20 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:34:50 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:34:50 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:34:55 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:34:55 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:35:44 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:35:44 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:35:46 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:35:46 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:35:55 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:35:55 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:36:14 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:36:14 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:36:39 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:36:39 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:37:02 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:37:02 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:37:32 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:37:32 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:38:02 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:38:02 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
2025-06-01 01:38:08 - myp.printers - INFO - 🔄 Live-Status-Abfrage von Benutzer Administrator (ID: 1)
|
||||||
|
2025-06-01 01:38:08 - myp.printers - INFO - ✅ Live-Status-Abfrage erfolgreich: 0 Drucker
|
||||||
|
@ -2759,3 +2759,10 @@
|
|||||||
2025-06-01 01:22:56 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
2025-06-01 01:22:56 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
2025-06-01 01:22:57 - myp.scheduler - INFO - Scheduler-Thread gestartet
|
2025-06-01 01:22:57 - myp.scheduler - INFO - Scheduler-Thread gestartet
|
||||||
2025-06-01 01:22:57 - myp.scheduler - INFO - Scheduler gestartet
|
2025-06-01 01:22:57 - myp.scheduler - INFO - Scheduler gestartet
|
||||||
|
2025-06-01 01:35:18 - myp.scheduler - INFO - Scheduler-Thread beendet
|
||||||
|
2025-06-01 01:35:18 - myp.scheduler - INFO - Scheduler gestoppt
|
||||||
|
2025-06-01 01:35:37 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
|
2025-06-01 01:35:39 - myp.scheduler - INFO - Scheduler-Thread gestartet
|
||||||
|
2025-06-01 01:35:39 - myp.scheduler - INFO - Scheduler gestartet
|
||||||
|
2025-06-01 01:38:12 - myp.scheduler - INFO - Scheduler-Thread beendet
|
||||||
|
2025-06-01 01:38:12 - myp.scheduler - INFO - Scheduler gestoppt
|
||||||
|
@ -238,14 +238,6 @@
|
|||||||
Reservierungen
|
Reservierungen
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ url_for('admin_guest_requests') }}"
|
|
||||||
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'guest_requests' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
||||||
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'guest_requests' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
||||||
</svg>
|
|
||||||
TBA-Anträge
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="{{ url_for('admin_page', tab='system') }}"
|
<a href="{{ url_for('admin_page', tab='system') }}"
|
||||||
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'system' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'system' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
||||||
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'system' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'system' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@ -261,6 +253,14 @@
|
|||||||
<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"/>
|
<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>
|
</svg>
|
||||||
Logs
|
Logs
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ url_for('admin_guest_requests') }}"
|
||||||
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'guest_requests' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
||||||
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'guest_requests' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||||
|
</svg>
|
||||||
|
TBA-Anträge
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,203 +1,208 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
{% block title %}Drucker hinzufügen - MYP Admin{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% block extra_css %}
|
||||||
<title>Drucker hinzufügen - MYP Admin</title>
|
<!-- Zusätzliche Styles für diese Seite -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<style>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
.form-container {
|
||||||
</head>
|
background: rgba(255, 255, 255, 0.95);
|
||||||
<body class="bg-gray-100">
|
backdrop-filter: blur(10px);
|
||||||
<div class="min-h-screen py-8">
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
<div class="max-w-2xl mx-auto">
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
<!-- Header -->
|
}
|
||||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
||||||
<div class="flex items-center justify-between">
|
.dark .form-container {
|
||||||
<div class="flex items-center space-x-3">
|
background: rgba(30, 41, 59, 0.95);
|
||||||
<i class="fas fa-print text-blue-600 text-2xl"></i>
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
<h1 class="text-2xl font-bold text-gray-800">Neuen Drucker hinzufügen</h1>
|
}
|
||||||
</div>
|
</style>
|
||||||
<a href="{{ url_for('admin_page', tab='printers') }}"
|
{% endblock %}
|
||||||
class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
|
|
||||||
<i class="fas fa-arrow-left mr-2"></i>Zurück
|
{% block content %}
|
||||||
</a>
|
<div class="min-h-screen py-8">
|
||||||
|
<div class="max-w-2xl mx-auto px-4">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="form-container rounded-lg p-6 mb-6">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<i class="fas fa-print text-blue-600 dark:text-blue-400 text-2xl"></i>
|
||||||
|
<h1 class="text-2xl font-bold text-slate-800 dark:text-white">Neuen Drucker hinzufügen</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<a href="{{ url_for('admin_page', tab='printers') }}"
|
||||||
|
class="bg-slate-500 hover:bg-slate-600 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
<!-- Formular -->
|
<i class="fas fa-arrow-left mr-2"></i>Zurück
|
||||||
<div class="bg-white rounded-lg shadow-md p-6">
|
</a>
|
||||||
<form action="{{ url_for('admin_create_printer_form') }}" method="POST" class="space-y-6">
|
|
||||||
<!-- CSRF Token -->
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
||||||
|
|
||||||
<!-- Name -->
|
|
||||||
<div>
|
|
||||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-tag mr-2"></i>Drucker-Name *
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
required
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="3D-Drucker Raum A001">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- IP-Adresse -->
|
|
||||||
<div>
|
|
||||||
<label for="ip_address" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-network-wired mr-2"></i>IP-Adresse *
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="ip_address"
|
|
||||||
name="ip_address"
|
|
||||||
required
|
|
||||||
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]?)$"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="192.168.1.100">
|
|
||||||
<p class="text-sm text-gray-500 mt-1">IP-Adresse der Tapo-Steckdose</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modell -->
|
|
||||||
<div>
|
|
||||||
<label for="model" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-cogs mr-2"></i>Drucker-Modell
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="model"
|
|
||||||
name="model"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Ender 3 V2">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Standort -->
|
|
||||||
<div>
|
|
||||||
<label for="location" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-map-marker-alt mr-2"></i>Standort
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="location"
|
|
||||||
name="location"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Raum A001, Erdgeschoss">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beschreibung -->
|
|
||||||
<div>
|
|
||||||
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-comment mr-2"></i>Beschreibung
|
|
||||||
</label>
|
|
||||||
<textarea id="description"
|
|
||||||
name="description"
|
|
||||||
rows="3"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Zusätzliche Informationen zum Drucker..."></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div>
|
|
||||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-circle mr-2"></i>Anfangsstatus
|
|
||||||
</label>
|
|
||||||
<select id="status"
|
|
||||||
name="status"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
||||||
<option value="available">Verfügbar</option>
|
|
||||||
<option value="offline">Offline</option>
|
|
||||||
<option value="maintenance">Wartung</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hinweise -->
|
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
||||||
<div class="flex">
|
|
||||||
<i class="fas fa-info-circle text-blue-500 mt-0.5 mr-3"></i>
|
|
||||||
<div class="text-sm text-blue-700">
|
|
||||||
<p class="font-semibold mb-1">Hinweise:</p>
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<li>Felder mit * sind Pflichtfelder</li>
|
|
||||||
<li>Die IP-Adresse sollte die Adresse der Tapo-Steckdose sein</li>
|
|
||||||
<li>Der Drucker wird automatisch mit Standard-Tapo-Einstellungen konfiguriert</li>
|
|
||||||
<li>Status "Verfügbar" bedeutet bereit für Druckaufträge</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Aktionen -->
|
|
||||||
<div class="flex space-x-3 pt-4">
|
|
||||||
<button type="submit"
|
|
||||||
class="flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
|
|
||||||
<i class="fas fa-save mr-2"></i>Drucker erstellen
|
|
||||||
</button>
|
|
||||||
<a href="{{ url_for('admin_page', tab='printers') }}"
|
|
||||||
class="flex-1 bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg text-center transition-colors">
|
|
||||||
<i class="fas fa-times mr-2"></i>Abbrechen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Flash Messages -->
|
<!-- Formular -->
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
<div class="form-container rounded-lg p-6">
|
||||||
{% if messages %}
|
<form action="{{ url_for('admin_create_printer_form') }}" method="POST" class="space-y-6">
|
||||||
<div class="fixed top-4 right-4 z-50 space-y-2">
|
<!-- CSRF Token -->
|
||||||
{% for category, message in messages %}
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} bg-{{ 'red' if category == 'error' else 'green' }}-100 border border-{{ 'red' if category == 'error' else 'green' }}-400 text-{{ 'red' if category == 'error' else 'green' }}-700 px-4 py-3 rounded-lg shadow-md">
|
|
||||||
<div class="flex items-center">
|
<!-- Name -->
|
||||||
<i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'check-circle' }} mr-2"></i>
|
<div>
|
||||||
{{ message }}
|
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-tag mr-2"></i>Drucker-Name *
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="3D-Drucker Raum A001">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP-Adresse -->
|
||||||
|
<div>
|
||||||
|
<label for="ip_address" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-network-wired mr-2"></i>IP-Adresse *
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="ip_address"
|
||||||
|
name="ip_address"
|
||||||
|
required
|
||||||
|
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]?)$"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="192.168.1.100">
|
||||||
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">IP-Adresse der Tapo-Steckdose</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modell -->
|
||||||
|
<div>
|
||||||
|
<label for="model" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-cogs mr-2"></i>Drucker-Modell
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="Ender 3 V2">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Standort -->
|
||||||
|
<div>
|
||||||
|
<label for="location" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-map-marker-alt mr-2"></i>Standort
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="location"
|
||||||
|
name="location"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="Raum A001, Erdgeschoss">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Beschreibung -->
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-comment mr-2"></i>Beschreibung
|
||||||
|
</label>
|
||||||
|
<textarea id="description"
|
||||||
|
name="description"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 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">
|
||||||
|
<i class="fas fa-circle mr-2"></i>Anfangsstatus
|
||||||
|
</label>
|
||||||
|
<select id="status"
|
||||||
|
name="status"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||||
|
<option value="available">Verfügbar</option>
|
||||||
|
<option value="offline">Offline</option>
|
||||||
|
<option value="maintenance">Wartung</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hinweise -->
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<i class="fas fa-info-circle text-blue-500 dark:text-blue-400 mt-0.5 mr-3"></i>
|
||||||
|
<div class="text-sm text-blue-700 dark:text-blue-300">
|
||||||
|
<p class="font-semibold mb-1">Hinweise:</p>
|
||||||
|
<ul class="list-disc list-inside space-y-1">
|
||||||
|
<li>Felder mit * sind Pflichtfelder</li>
|
||||||
|
<li>Die IP-Adresse sollte die Adresse der Tapo-Steckdose sein</li>
|
||||||
|
<li>Der Drucker wird automatisch mit Standard-Tapo-Einstellungen konfiguriert</li>
|
||||||
|
<li>Status "Verfügbar" bedeutet bereit für Druckaufträge</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<!-- JavaScript für Form-Validierung -->
|
<!-- Aktionen -->
|
||||||
<script>
|
<div class="flex space-x-3 pt-4">
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
<button type="submit"
|
||||||
const form = document.querySelector('form');
|
class="flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
const nameInput = document.getElementById('name');
|
<i class="fas fa-save mr-2"></i>Drucker erstellen
|
||||||
const ipInput = document.getElementById('ip_address');
|
</button>
|
||||||
|
<a href="{{ url_for('admin_page', tab='printers') }}"
|
||||||
|
class="flex-1 bg-slate-500 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-center transition-colors">
|
||||||
|
<i class="fas fa-times mr-2"></i>Abbrechen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
// IP-Adresse-Validierung
|
{% block extra_js %}
|
||||||
ipInput.addEventListener('blur', function() {
|
<!-- JavaScript für Form-Validierung -->
|
||||||
const ip = this.value;
|
<script>
|
||||||
const ipRegex = /^(?:(?: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]?)$/;
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const form = document.querySelector('form');
|
||||||
if (ip && !ipRegex.test(ip)) {
|
const nameInput = document.getElementById('name');
|
||||||
this.classList.add('border-red-500');
|
const ipInput = document.getElementById('ip_address');
|
||||||
this.classList.remove('border-gray-300');
|
|
||||||
} else {
|
|
||||||
this.classList.remove('border-red-500');
|
|
||||||
this.classList.add('border-gray-300');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form-Submit-Validierung
|
// IP-Adresse-Validierung
|
||||||
form.addEventListener('submit', function(e) {
|
ipInput.addEventListener('blur', function() {
|
||||||
const name = nameInput.value.trim();
|
const ip = this.value;
|
||||||
const ip = ipInput.value.trim();
|
const ipRegex = /^(?:(?: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]?)$/;
|
||||||
const ipRegex = /^(?:(?: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]?)$/;
|
|
||||||
|
if (ip && !ipRegex.test(ip)) {
|
||||||
if (!name) {
|
this.classList.add('border-red-500');
|
||||||
e.preventDefault();
|
this.classList.remove('border-slate-300', 'dark:border-slate-600');
|
||||||
alert('Bitte geben Sie einen Drucker-Namen ein.');
|
} else {
|
||||||
nameInput.focus();
|
this.classList.remove('border-red-500');
|
||||||
return;
|
this.classList.add('border-slate-300', 'dark:border-slate-600');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ip || !ipRegex.test(ip)) {
|
|
||||||
e.preventDefault();
|
|
||||||
alert('Bitte geben Sie eine gültige IP-Adresse ein.');
|
|
||||||
ipInput.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
</body>
|
// Form-Submit-Validierung
|
||||||
</html>
|
form.addEventListener('submit', function(e) {
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
const ip = ipInput.value.trim();
|
||||||
|
const ipRegex = /^(?:(?: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]?)$/;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Bitte geben Sie einen Drucker-Namen ein.', 'error');
|
||||||
|
} else {
|
||||||
|
alert('Bitte geben Sie einen Drucker-Namen ein.');
|
||||||
|
}
|
||||||
|
nameInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ip || !ipRegex.test(ip)) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Bitte geben Sie eine gültige IP-Adresse ein.', 'error');
|
||||||
|
} else {
|
||||||
|
alert('Bitte geben Sie eine gültige IP-Adresse ein.');
|
||||||
|
}
|
||||||
|
ipInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -1,347 +1,382 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
{% block title %}Drucker bearbeiten - MYP Admin{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% block extra_css %}
|
||||||
<title>Drucker bearbeiten - MYP Admin</title>
|
<!-- Zusätzliche Styles für diese Seite -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
<style>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
.form-container {
|
||||||
</head>
|
background: rgba(255, 255, 255, 0.95);
|
||||||
<body class="bg-gray-100">
|
backdrop-filter: blur(10px);
|
||||||
<div class="min-h-screen py-8">
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
<div class="max-w-2xl mx-auto">
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
<!-- Header -->
|
}
|
||||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
||||||
<div class="flex items-center justify-between">
|
.dark .form-container {
|
||||||
<div class="flex items-center space-x-3">
|
background: rgba(30, 41, 59, 0.95);
|
||||||
<i class="fas fa-edit text-blue-600 text-2xl"></i>
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
<h1 class="text-2xl font-bold text-gray-800">Drucker bearbeiten</h1>
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="min-h-screen py-8">
|
||||||
|
<div class="max-w-2xl mx-auto px-4">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="form-container rounded-lg p-6 mb-6">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<i class="fas fa-edit text-blue-600 dark:text-blue-400 text-2xl"></i>
|
||||||
|
<h1 class="text-2xl font-bold text-slate-800 dark:text-white">Drucker bearbeiten</h1>
|
||||||
|
</div>
|
||||||
|
<a href="{{ url_for('admin_page', tab='printers') }}"
|
||||||
|
class="bg-slate-500 hover:bg-slate-600 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
|
<i class="fas fa-arrow-left mr-2"></i>Zurück
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
|
<p class="text-sm text-blue-700 dark:text-blue-300">
|
||||||
|
<i class="fas fa-info-circle mr-2"></i>
|
||||||
|
<strong>Drucker-ID:</strong> {{ printer.id }} |
|
||||||
|
<strong>Erstellt am:</strong> {{ printer.created_at[:10] if printer.created_at else 'Unbekannt' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-container rounded-lg p-6">
|
||||||
|
<form action="{{ url_for('admin_update_printer_form', printer_id=printer.id) }}" method="POST" class="space-y-6">
|
||||||
|
<!-- CSRF Token -->
|
||||||
|
<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">
|
||||||
|
<i class="fas fa-tag mr-2"></i>Drucker-Name *
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
value="{{ printer.name }}"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="3D-Drucker Raum A001">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IP-Adresse -->
|
||||||
|
<div>
|
||||||
|
<label for="ip_address" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-network-wired mr-2"></i>IP-Adresse *
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="ip_address"
|
||||||
|
name="ip_address"
|
||||||
|
required
|
||||||
|
value="{{ printer.plug_ip }}"
|
||||||
|
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]?)$"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="192.168.1.100">
|
||||||
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">IP-Adresse der Tapo-Steckdose</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modell -->
|
||||||
|
<div>
|
||||||
|
<label for="model" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-cogs mr-2"></i>Drucker-Modell
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
value="{{ printer.model }}"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="Ender 3 V2">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Standort -->
|
||||||
|
<div>
|
||||||
|
<label for="location" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-map-marker-alt mr-2"></i>Standort
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="location"
|
||||||
|
name="location"
|
||||||
|
value="{{ printer.location }}"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white"
|
||||||
|
placeholder="Raum A001, Erdgeschoss">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Beschreibung -->
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
<i class="fas fa-comment mr-2"></i>Beschreibung
|
||||||
|
</label>
|
||||||
|
<textarea id="description"
|
||||||
|
name="description"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 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">
|
||||||
|
<i class="fas fa-circle mr-2"></i>Aktueller Status
|
||||||
|
</label>
|
||||||
|
<select id="status"
|
||||||
|
name="status"
|
||||||
|
class="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-slate-800 text-slate-900 dark:text-white">
|
||||||
|
<option value="available" {{ 'selected' if printer.status == 'available' else '' }}>Verfügbar</option>
|
||||||
|
<option value="offline" {{ 'selected' if printer.status == 'offline' else '' }}>Offline</option>
|
||||||
|
<option value="maintenance" {{ 'selected' if printer.status == 'maintenance' else '' }}>Wartung</option>
|
||||||
|
<option value="online" {{ 'selected' if printer.status == 'online' else '' }}>Online</option>
|
||||||
|
<option value="printing" {{ 'selected' if printer.status == 'printing' else '' }}>Druckt</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aktiv-Status -->
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center space-x-3">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="is_active"
|
||||||
|
{{ 'checked' if printer.active else '' }}
|
||||||
|
class="w-4 h-4 text-blue-600 bg-slate-100 dark:bg-slate-700 border-slate-300 dark:border-slate-600 rounded focus:ring-blue-500 focus:ring-2">
|
||||||
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
<i class="fas fa-power-off mr-2"></i>Drucker aktiv
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">Inaktive Drucker werden nicht für neue Aufträge verwendet</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Erweiterte Informationen -->
|
||||||
|
<div class="bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg p-4">
|
||||||
|
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
|
<i class="fas fa-info-circle mr-2"></i>Drucker-Informationen
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-slate-600 dark:text-slate-400">MAC-Adresse:</span>
|
||||||
|
<span class="text-slate-800 dark:text-slate-200">{{ printer.mac_address or 'Nicht verfügbar' }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-medium text-slate-600 dark:text-slate-400">Letzter Check:</span>
|
||||||
|
<span class="text-slate-800 dark:text-slate-200">{{ printer.last_checked or 'Nie' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Warnung -->
|
||||||
|
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<i class="fas fa-exclamation-triangle text-yellow-500 dark:text-yellow-400 mt-0.5 mr-3"></i>
|
||||||
|
<div class="text-sm text-yellow-700 dark:text-yellow-300">
|
||||||
|
<p class="font-semibold mb-1">Wichtige Hinweise:</p>
|
||||||
|
<ul class="list-disc list-inside space-y-1">
|
||||||
|
<li>Änderungen an der IP-Adresse können die Verbindung unterbrechen</li>
|
||||||
|
<li>Stellen Sie sicher, dass die Tapo-Steckdose unter der neuen IP erreichbar ist</li>
|
||||||
|
<li>Bei Status-Änderungen werden laufende Jobs möglicherweise beeinflusst</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aktionen -->
|
||||||
|
<div class="flex space-x-3 pt-4">
|
||||||
|
<button type="submit"
|
||||||
|
class="flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
|
||||||
|
<i class="fas fa-save mr-2"></i>Änderungen speichern
|
||||||
|
</button>
|
||||||
<a href="{{ url_for('admin_page', tab='printers') }}"
|
<a href="{{ url_for('admin_page', tab='printers') }}"
|
||||||
class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
|
class="flex-1 bg-slate-500 hover:bg-slate-600 text-white px-4 py-2 rounded-lg text-center transition-colors">
|
||||||
<i class="fas fa-arrow-left mr-2"></i>Zurück
|
<i class="fas fa-times mr-2"></i>Abbrechen
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 p-3 bg-blue-50 rounded-lg">
|
</form>
|
||||||
<p class="text-sm text-blue-700">
|
</div>
|
||||||
<i class="fas fa-info-circle mr-2"></i>
|
|
||||||
<strong>Drucker-ID:</strong> {{ printer.id }} |
|
|
||||||
<strong>Erstellt am:</strong> {{ printer.created_at[:10] if printer.created_at else 'Unbekannt' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Formular -->
|
<!-- Zusätzliche Aktionen -->
|
||||||
<div class="bg-white rounded-lg shadow-md p-6">
|
<div class="form-container rounded-lg p-6 mt-6">
|
||||||
<form action="{{ url_for('admin_update_printer_form', printer_id=printer.id) }}" method="POST" class="space-y-6">
|
<h3 class="text-lg font-semibold text-slate-800 dark:text-white mb-4">
|
||||||
<!-- CSRF Token -->
|
<i class="fas fa-tools mr-2"></i>Drucker-Aktionen
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<!-- Name -->
|
<button data-printer-id="{{ printer.id }}"
|
||||||
<div>
|
data-action="test"
|
||||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
class="printer-action-btn bg-green-600 hover:bg-green-700 text-white px-4 py-3 rounded-lg transition-colors">
|
||||||
<i class="fas fa-tag mr-2"></i>Drucker-Name *
|
<i class="fas fa-plug mr-2"></i>Verbindung testen
|
||||||
</label>
|
</button>
|
||||||
<input type="text"
|
<button data-printer-id="{{ printer.id }}"
|
||||||
id="name"
|
data-action="toggle"
|
||||||
name="name"
|
class="printer-action-btn bg-orange-600 hover:bg-orange-700 text-white px-4 py-3 rounded-lg transition-colors">
|
||||||
required
|
<i class="fas fa-power-off mr-2"></i>Ein/Ausschalten
|
||||||
value="{{ printer.name }}"
|
</button>
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="3D-Drucker Raum A001">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- IP-Adresse -->
|
|
||||||
<div>
|
|
||||||
<label for="ip_address" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-network-wired mr-2"></i>IP-Adresse *
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="ip_address"
|
|
||||||
name="ip_address"
|
|
||||||
required
|
|
||||||
value="{{ printer.plug_ip }}"
|
|
||||||
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]?)$"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="192.168.1.100">
|
|
||||||
<p class="text-sm text-gray-500 mt-1">IP-Adresse der Tapo-Steckdose</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modell -->
|
|
||||||
<div>
|
|
||||||
<label for="model" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-cogs mr-2"></i>Drucker-Modell
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="model"
|
|
||||||
name="model"
|
|
||||||
value="{{ printer.model }}"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Ender 3 V2">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Standort -->
|
|
||||||
<div>
|
|
||||||
<label for="location" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-map-marker-alt mr-2"></i>Standort
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="location"
|
|
||||||
name="location"
|
|
||||||
value="{{ printer.location }}"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Raum A001, Erdgeschoss">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Beschreibung -->
|
|
||||||
<div>
|
|
||||||
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-comment mr-2"></i>Beschreibung
|
|
||||||
</label>
|
|
||||||
<textarea id="description"
|
|
||||||
name="description"
|
|
||||||
rows="3"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
||||||
placeholder="Zusätzliche Informationen zum Drucker...">{{ printer.description or '' }}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<div>
|
|
||||||
<label for="status" class="block text-sm font-medium text-gray-700 mb-2">
|
|
||||||
<i class="fas fa-circle mr-2"></i>Aktueller Status
|
|
||||||
</label>
|
|
||||||
<select id="status"
|
|
||||||
name="status"
|
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
||||||
<option value="available" {{ 'selected' if printer.status == 'available' else '' }}>Verfügbar</option>
|
|
||||||
<option value="offline" {{ 'selected' if printer.status == 'offline' else '' }}>Offline</option>
|
|
||||||
<option value="maintenance" {{ 'selected' if printer.status == 'maintenance' else '' }}>Wartung</option>
|
|
||||||
<option value="online" {{ 'selected' if printer.status == 'online' else '' }}>Online</option>
|
|
||||||
<option value="printing" {{ 'selected' if printer.status == 'printing' else '' }}>Druckt</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Aktiv-Status -->
|
|
||||||
<div>
|
|
||||||
<label class="flex items-center space-x-3">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="is_active"
|
|
||||||
{{ 'checked' if printer.active else '' }}
|
|
||||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2">
|
|
||||||
<span class="text-sm font-medium text-gray-700">
|
|
||||||
<i class="fas fa-power-off mr-2"></i>Drucker aktiv
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<p class="text-sm text-gray-500 mt-1">Inaktive Drucker werden nicht für neue Aufträge verwendet</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Erweiterte Informationen -->
|
|
||||||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
|
||||||
<h3 class="text-sm font-semibold text-gray-700 mb-3">
|
|
||||||
<i class="fas fa-info-circle mr-2"></i>Drucker-Informationen
|
|
||||||
</h3>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
|
||||||
<div>
|
|
||||||
<span class="font-medium text-gray-600">MAC-Adresse:</span>
|
|
||||||
<span class="text-gray-800">{{ printer.mac_address or 'Nicht verfügbar' }}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="font-medium text-gray-600">Letzter Check:</span>
|
|
||||||
<span class="text-gray-800">{{ printer.last_checked or 'Nie' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Warnung -->
|
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
||||||
<div class="flex">
|
|
||||||
<i class="fas fa-exclamation-triangle text-yellow-500 mt-0.5 mr-3"></i>
|
|
||||||
<div class="text-sm text-yellow-700">
|
|
||||||
<p class="font-semibold mb-1">Wichtige Hinweise:</p>
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<li>Änderungen an der IP-Adresse können die Verbindung unterbrechen</li>
|
|
||||||
<li>Stellen Sie sicher, dass die Tapo-Steckdose unter der neuen IP erreichbar ist</li>
|
|
||||||
<li>Bei Status-Änderungen werden laufende Jobs möglicherweise beeinflusst</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Aktionen -->
|
|
||||||
<div class="flex space-x-3 pt-4">
|
|
||||||
<button type="submit"
|
|
||||||
class="flex-1 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors">
|
|
||||||
<i class="fas fa-save mr-2"></i>Änderungen speichern
|
|
||||||
</button>
|
|
||||||
<a href="{{ url_for('admin_page', tab='printers') }}"
|
|
||||||
class="flex-1 bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg text-center transition-colors">
|
|
||||||
<i class="fas fa-times mr-2"></i>Abbrechen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Zusätzliche Aktionen -->
|
|
||||||
<div class="bg-white rounded-lg shadow-md p-6 mt-6">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">
|
|
||||||
<i class="fas fa-tools mr-2"></i>Drucker-Aktionen
|
|
||||||
</h3>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<button data-printer-id="{{ printer.id }}"
|
|
||||||
data-action="test"
|
|
||||||
class="printer-action-btn bg-green-600 hover:bg-green-700 text-white px-4 py-3 rounded-lg transition-colors">
|
|
||||||
<i class="fas fa-plug mr-2"></i>Verbindung testen
|
|
||||||
</button>
|
|
||||||
<button data-printer-id="{{ printer.id }}"
|
|
||||||
data-action="toggle"
|
|
||||||
class="printer-action-btn bg-orange-600 hover:bg-orange-700 text-white px-4 py-3 rounded-lg transition-colors">
|
|
||||||
<i class="fas fa-power-off mr-2"></i>Ein/Ausschalten
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Flash Messages -->
|
{% block extra_js %}
|
||||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
<!-- JavaScript -->
|
||||||
{% if messages %}
|
<script>
|
||||||
<div class="fixed top-4 right-4 z-50 space-y-2">
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
{% for category, message in messages %}
|
const form = document.querySelector('form');
|
||||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} bg-{{ 'red' if category == 'error' else 'green' }}-100 border border-{{ 'red' if category == 'error' else 'green' }}-400 text-{{ 'red' if category == 'error' else 'green' }}-700 px-4 py-3 rounded-lg shadow-md">
|
const nameInput = document.getElementById('name');
|
||||||
<div class="flex items-center">
|
const ipInput = document.getElementById('ip_address');
|
||||||
<i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'check-circle' }} mr-2"></i>
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<!-- JavaScript -->
|
// IP-Adresse-Validierung
|
||||||
<script>
|
ipInput.addEventListener('blur', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
const ip = this.value;
|
||||||
const form = document.querySelector('form');
|
const ipRegex = /^(?:(?: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]?)$/;
|
||||||
const nameInput = document.getElementById('name');
|
|
||||||
const ipInput = document.getElementById('ip_address');
|
if (ip && !ipRegex.test(ip)) {
|
||||||
|
this.classList.add('border-red-500');
|
||||||
// IP-Adresse-Validierung
|
this.classList.remove('border-slate-300', 'dark:border-slate-600');
|
||||||
ipInput.addEventListener('blur', function() {
|
} else {
|
||||||
const ip = this.value;
|
this.classList.remove('border-red-500');
|
||||||
const ipRegex = /^(?:(?: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]?)$/;
|
this.classList.add('border-slate-300', 'dark:border-slate-600');
|
||||||
|
}
|
||||||
if (ip && !ipRegex.test(ip)) {
|
|
||||||
this.classList.add('border-red-500');
|
|
||||||
this.classList.remove('border-gray-300');
|
|
||||||
} else {
|
|
||||||
this.classList.remove('border-red-500');
|
|
||||||
this.classList.add('border-gray-300');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form-Submit-Validierung
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
const name = nameInput.value.trim();
|
|
||||||
const ip = ipInput.value.trim();
|
|
||||||
const ipRegex = /^(?:(?: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]?)$/;
|
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
e.preventDefault();
|
|
||||||
alert('Bitte geben Sie einen Drucker-Namen ein.');
|
|
||||||
nameInput.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ip || !ipRegex.test(ip)) {
|
|
||||||
e.preventDefault();
|
|
||||||
alert('Bitte geben Sie eine gültige IP-Adresse ein.');
|
|
||||||
ipInput.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event-Listener für Drucker-Aktions-Buttons
|
|
||||||
document.querySelectorAll('.printer-action-btn').forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
const printerId = this.getAttribute('data-printer-id');
|
|
||||||
const action = this.getAttribute('data-action');
|
|
||||||
|
|
||||||
if (action === 'test') {
|
|
||||||
testPrinterConnection(printerId, this);
|
|
||||||
} else if (action === 'toggle') {
|
|
||||||
togglePrinterPower(printerId, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verbindungstest
|
// Form-Submit-Validierung
|
||||||
function testPrinterConnection(printerId, button) {
|
form.addEventListener('submit', function(e) {
|
||||||
const originalText = button.innerHTML;
|
const name = nameInput.value.trim();
|
||||||
|
const ip = ipInput.value.trim();
|
||||||
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Teste...';
|
const ipRegex = /^(?:(?: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]?)$/;
|
||||||
button.disabled = true;
|
|
||||||
|
|
||||||
fetch(`/api/admin/printers/${printerId}/test-tapo`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': '{{ csrf_token() }}'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.tapo_test && data.tapo_test.success) {
|
|
||||||
alert('✅ Verbindung erfolgreich!\n\nStatus: ' + (data.tapo_test.device_info ? data.tapo_test.device_info.device_on ? 'EIN' : 'AUS' : 'Unbekannt'));
|
|
||||||
} else {
|
|
||||||
alert('❌ Verbindung fehlgeschlagen!\n\nFehler: ' + (data.tapo_test ? data.tapo_test.error : 'Unbekannter Fehler'));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
alert('❌ Verbindungstest fehlgeschlagen!\n\nFehler: ' + error.message);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
button.innerHTML = originalText;
|
|
||||||
button.disabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drucker ein/ausschalten
|
if (!name) {
|
||||||
function togglePrinterPower(printerId, button) {
|
e.preventDefault();
|
||||||
const originalText = button.innerHTML;
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Bitte geben Sie einen Drucker-Namen ein.', 'error');
|
||||||
if (!confirm('Möchten Sie den Drucker ein-/ausschalten?')) {
|
} else {
|
||||||
|
alert('Bitte geben Sie einen Drucker-Namen ein.');
|
||||||
|
}
|
||||||
|
nameInput.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Schaltet...';
|
if (!ip || !ipRegex.test(ip)) {
|
||||||
button.disabled = true;
|
e.preventDefault();
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
fetch(`/api/admin/printers/${printerId}/toggle`, {
|
showFlashMessage('Bitte geben Sie eine gültige IP-Adresse ein.', 'error');
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': '{{ csrf_token() }}'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
alert('✅ Drucker erfolgreich ' + data.action + '!');
|
|
||||||
// Seite neu laden um aktuellen Status zu zeigen
|
|
||||||
location.reload();
|
|
||||||
} else {
|
} else {
|
||||||
alert('❌ Fehler beim Schalten!\n\nFehler: ' + (data.error || 'Unbekannter Fehler'));
|
alert('Bitte geben Sie eine gültige IP-Adresse ein.');
|
||||||
|
}
|
||||||
|
ipInput.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event-Listener für Drucker-Aktions-Buttons
|
||||||
|
document.querySelectorAll('.printer-action-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const printerId = this.getAttribute('data-printer-id');
|
||||||
|
const action = this.getAttribute('data-action');
|
||||||
|
|
||||||
|
if (action === 'test') {
|
||||||
|
testPrinterConnection(printerId, this);
|
||||||
|
} else if (action === 'toggle') {
|
||||||
|
togglePrinterPower(printerId, this);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
alert('❌ Schaltvorgang fehlgeschlagen!\n\nFehler: ' + error.message);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
button.innerHTML = originalText;
|
|
||||||
button.disabled = false;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verbindungstest
|
||||||
|
function testPrinterConnection(printerId, button) {
|
||||||
|
const originalText = button.innerHTML;
|
||||||
|
|
||||||
|
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Teste...';
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
fetch(`/api/admin/printers/${printerId}/test-tapo`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.tapo_test && data.tapo_test.success) {
|
||||||
|
const message = '✅ Verbindung erfolgreich!\n\nStatus: ' + (data.tapo_test.device_info ? data.tapo_test.device_info.device_on ? 'EIN' : 'AUS' : 'Unbekannt');
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'success');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const message = '❌ Verbindung fehlgeschlagen!\n\nFehler: ' + (data.tapo_test ? data.tapo_test.error : 'Unbekannter Fehler');
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'error');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
const message = '❌ Verbindungstest fehlgeschlagen!\n\nFehler: ' + error.message;
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'error');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drucker ein/ausschalten
|
||||||
|
function togglePrinterPower(printerId, button) {
|
||||||
|
const originalText = button.innerHTML;
|
||||||
|
|
||||||
|
if (!confirm('Möchten Sie den Drucker ein-/ausschalten?')) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
</body>
|
button.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Schaltet...';
|
||||||
</html>
|
button.disabled = true;
|
||||||
|
|
||||||
|
fetch(`/api/admin/printers/${printerId}/toggle`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const message = '✅ Drucker erfolgreich ' + data.action + '!';
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'success');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
// Seite neu laden um aktuellen Status zu zeigen
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
} else {
|
||||||
|
const message = '❌ Fehler beim Schalten!\n\nFehler: ' + (data.error || 'Unbekannter Fehler');
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'error');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
const message = '❌ Schaltvorgang fehlgeschlagen!\n\nFehler: ' + error.message;
|
||||||
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage(message, 'error');
|
||||||
|
} else {
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -5,164 +5,592 @@
|
|||||||
{% block head %}
|
{% block head %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<style>
|
||||||
|
/* Modern Toggle Switch */
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 52px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(135deg, #e2e8f0, #cbd5e1);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-radius: 28px;
|
||||||
|
border: 2px solid rgba(148, 163, 184, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 2px 4px rgba(0, 0, 0, 0.1),
|
||||||
|
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background: linear-gradient(135deg, #ffffff, #f8fafc);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.15),
|
||||||
|
0 1px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider {
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 2px 4px rgba(29, 78, 216, 0.2),
|
||||||
|
0 0 0 3px rgba(59, 130, 246, 0.1),
|
||||||
|
0 4px 12px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .toggle-slider:before {
|
||||||
|
transform: translateX(24px);
|
||||||
|
background: linear-gradient(135deg, #ffffff, #f1f5f9);
|
||||||
|
box-shadow:
|
||||||
|
0 3px 12px rgba(0, 0, 0, 0.2),
|
||||||
|
0 1px 6px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Premium Input Fields */
|
||||||
|
.premium-input {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 250, 252, 0.95));
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-clip: padding-box;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.premium-input:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
padding: 2px;
|
||||||
|
background: linear-gradient(135deg, #e2e8f0, #cbd5e1, #e2e8f0);
|
||||||
|
border-radius: inherit;
|
||||||
|
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||||
|
mask-composite: exclude;
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.premium-input:focus {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 25px rgba(59, 130, 246, 0.15),
|
||||||
|
0 4px 10px rgba(59, 130, 246, 0.1),
|
||||||
|
0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.premium-input:focus:before {
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #1d4ed8, #3b82f6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode styles */
|
||||||
|
.dark .premium-input {
|
||||||
|
background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95));
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .premium-input:before {
|
||||||
|
background: linear-gradient(135deg, #475569, #334155, #475569);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .toggle-slider {
|
||||||
|
background: linear-gradient(135deg, #475569, #334155);
|
||||||
|
border-color: rgba(71, 85, 105, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Permission Card Animations */
|
||||||
|
.permission-card {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating Labels */
|
||||||
|
.floating-label {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-label input:focus + label,
|
||||||
|
.floating-label input:not(:placeholder-shown) + label {
|
||||||
|
transform: translateY(-24px) scale(0.875);
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-label label {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 14px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
pointer-events: none;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Hover Effects */
|
||||||
|
.btn-gradient {
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-gradient:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-gradient:hover:before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glass Effect Cards */
|
||||||
|
.glass-card {
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
0 25px 50px -12px rgba(0, 0, 0, 0.1),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.05) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .glass-card {
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-slate-900 dark:via-blue-900 dark:to-indigo-900 relative overflow-hidden">
|
||||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<!-- Animated Background Elements -->
|
||||||
|
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div class="absolute -top-40 -right-32 w-96 h-96 bg-gradient-to-br from-blue-400/20 to-indigo-600/20 rounded-full blur-3xl animate-pulse"></div>
|
||||||
|
<div class="absolute -bottom-40 -left-32 w-96 h-96 bg-gradient-to-tr from-purple-400/20 to-pink-600/20 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||||
|
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-gradient-to-r from-blue-300/10 to-indigo-300/10 rounded-full blur-2xl animate-pulse delay-500"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative z-10 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Modern Header with Glass Effect -->
|
||||||
<div class="mb-8">
|
<div class="mb-12">
|
||||||
<div class="flex items-center justify-between">
|
<div class="glass-card rounded-3xl p-8 mb-8">
|
||||||
<div>
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Benutzer bearbeiten</h1>
|
<div class="flex items-center space-x-6">
|
||||||
<p class="text-slate-600 dark:text-slate-400 mt-2">Bearbeiten Sie die Daten von {{ user.name or user.email }}</p>
|
<div class="w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center shadow-xl">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-4xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-gray-200 bg-clip-text text-transparent">
|
||||||
|
Benutzer bearbeiten
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-slate-600 dark:text-slate-400 mt-2 flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
|
</svg>
|
||||||
|
{{ user.name or user.email }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="{{ url_for('admin_page', tab='users') }}"
|
||||||
|
class="group inline-flex items-center px-6 py-3 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-white dark:hover:bg-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl border border-white/20 dark:border-slate-700/50">
|
||||||
|
<svg class="w-5 h-5 mr-2 transition-transform group-hover:-translate-x-1" 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 class="font-medium">Zurück zur Verwaltung</span>
|
||||||
|
</a>
|
||||||
</div>
|
</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 Benutzerverwaltung
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Modern Form with Glass Effect -->
|
||||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
|
<div class="glass-card rounded-3xl p-10 shadow-2xl">
|
||||||
<form method="POST" action="{{ url_for('admin_update_user_form', user_id=user.id) }}" class="space-y-6">
|
<form method="POST" action="{{ url_for('admin_update_user_form', user_id=user.id) }}" class="space-y-8" id="userEditForm">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
<input type="hidden" name="_method" value="PUT"/>
|
<input type="hidden" name="_method" value="PUT"/>
|
||||||
|
|
||||||
<!-- Benutzername -->
|
<!-- Form Header -->
|
||||||
<div>
|
<div class="border-b border-slate-200 dark:border-slate-700 pb-8">
|
||||||
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-3 flex items-center">
|
||||||
Benutzername
|
<svg class="w-6 h-6 mr-3 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</label>
|
<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"/>
|
||||||
<input type="text" name="username" id="username" required
|
</svg>
|
||||||
value="{{ user.username }}"
|
Benutzerdaten bearbeiten
|
||||||
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"
|
</h2>
|
||||||
placeholder="max.mustermann">
|
<p class="text-slate-600 dark:text-slate-400 text-lg">
|
||||||
|
Bearbeiten Sie die Informationen und Berechtigungen für diesen Benutzer
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name -->
|
<!-- Personal Information Section -->
|
||||||
<div>
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
<!-- Left Column: Basic Info -->
|
||||||
Vollständiger Name
|
<div class="space-y-6">
|
||||||
</label>
|
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-6 border border-blue-200/50 dark:border-blue-800/50">
|
||||||
<input type="text" name="name" id="name"
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6 flex items-center">
|
||||||
value="{{ user.name }}"
|
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 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"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
placeholder="Max Mustermann">
|
</svg>
|
||||||
</div>
|
Persönliche Informationen
|
||||||
|
</h3>
|
||||||
<!-- Neues Passwort (optional) -->
|
|
||||||
<div>
|
<!-- Username Field -->
|
||||||
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
<div class="mb-6">
|
||||||
Neues Passwort (leer lassen, um beizubehalten)
|
<label for="username" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
</label>
|
Benutzername
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Berechtigungen -->
|
|
||||||
<div class="border-t border-slate-200 dark:border-slate-600 pt-6">
|
|
||||||
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-4">Benutzerberechtigungen</h3>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<!-- Jobs ohne Genehmigung starten -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<label for="can_start_jobs" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
||||||
Jobs ohne Genehmigung starten
|
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
<div class="relative group">
|
||||||
Benutzer kann eigene Druckjobs ohne Admin-Genehmigung starten
|
<input type="text"
|
||||||
</p>
|
name="username"
|
||||||
|
id="username"
|
||||||
|
required
|
||||||
|
value="{{ user.username }}"
|
||||||
|
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||||
|
placeholder="max.mustermann">
|
||||||
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
|
||||||
<input type="checkbox" name="can_start_jobs" id="can_start_jobs"
|
<!-- Full Name Field -->
|
||||||
{% if user.permissions and user.permissions.can_start_jobs %}checked{% endif %}
|
<div class="mb-6">
|
||||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
<label for="name" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
<label for="can_start_jobs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
Vollständiger Name
|
||||||
|
</label>
|
||||||
|
<div class="relative group">
|
||||||
|
<input type="text"
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
value="{{ user.name }}"
|
||||||
|
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||||
|
placeholder="Max Mustermann">
|
||||||
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password Field -->
|
||||||
|
<div>
|
||||||
|
<label for="password" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
|
Neues Passwort
|
||||||
|
<span class="text-xs font-normal text-slate-500 dark:text-slate-400 ml-2">(optional)</span>
|
||||||
|
</label>
|
||||||
|
<div class="relative group">
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||||
|
placeholder="Leer lassen, um beizubehalten">
|
||||||
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 0h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-2 ml-1">
|
||||||
|
Lassen Sie das Feld leer, um das aktuelle Passwort beizubehalten
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Genehmigungspflicht -->
|
<!-- Right Column: Role & Status -->
|
||||||
<div class="flex items-center justify-between">
|
<div class="space-y-6">
|
||||||
<div>
|
<div class="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200/50 dark:border-green-800/50">
|
||||||
<label for="needs_approval" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6 flex items-center">
|
||||||
Genehmigungspflicht für Jobs
|
<svg class="w-5 h-5 mr-2 text-green-500" 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>
|
||||||
|
Rolle & Status
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Role Selection -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="role" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
|
Benutzerrolle
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
<div class="relative">
|
||||||
Jobs des Benutzers müssen von einem Admin genehmigt werden
|
<select name="role"
|
||||||
</p>
|
id="role"
|
||||||
|
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white focus:outline-none appearance-none cursor-pointer">
|
||||||
|
<option value="user" {% if not user.is_admin %}selected{% endif %}>
|
||||||
|
👤 Standard-Benutzer
|
||||||
|
</option>
|
||||||
|
<option value="admin" {% if user.is_admin %}selected{% endif %}>
|
||||||
|
👑 Administrator
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
|
||||||
<input type="checkbox" name="needs_approval" id="needs_approval"
|
|
||||||
{% if not user.permissions or user.permissions.needs_approval %}checked{% endif %}
|
|
||||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
|
||||||
<label for="needs_approval" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Jobs genehmigen -->
|
<!-- Status Selection -->
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
<div>
|
||||||
<label for="can_approve_jobs" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
<label for="is_active" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||||
Jobs genehmigen
|
Kontostatus
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
<div class="relative">
|
||||||
Benutzer kann Gastanfragen und fremde Jobs genehmigen
|
<select name="is_active"
|
||||||
</p>
|
id="is_active"
|
||||||
</div>
|
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white focus:outline-none appearance-none cursor-pointer">
|
||||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
<option value="true" {% if user.active %}selected{% endif %}>
|
||||||
<input type="checkbox" name="can_approve_jobs" id="can_approve_jobs"
|
✅ Aktiv
|
||||||
{% if user.permissions and user.permissions.can_approve_jobs %}checked{% endif %}
|
</option>
|
||||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
<option value="false" {% if not user.active %}selected{% endif %}>
|
||||||
<label for="can_approve_jobs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
❌ Deaktiviert
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Advanced Permissions Section -->
|
||||||
<div class="flex items-center justify-end space-x-4 pt-4">
|
<div class="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-2xl p-8 border border-purple-200/50 dark:border-purple-800/50">
|
||||||
<a href="{{ url_for('admin_page', tab='users') }}"
|
<div class="mb-8">
|
||||||
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">
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-3 flex items-center">
|
||||||
Abbrechen
|
<svg class="w-6 h-6 mr-3 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
</a>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 0h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||||
<button type="submit"
|
</svg>
|
||||||
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">
|
Erweiterte Berechtigungen
|
||||||
Änderungen speichern
|
</h3>
|
||||||
</button>
|
<p class="text-slate-600 dark:text-slate-400">
|
||||||
|
Konfigurieren Sie die spezifischen Zugriffsrechte für diesen Benutzer
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<!-- Permission: Start Jobs -->
|
||||||
|
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<svg class="w-5 h-5 text-green-500 mr-2" 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 4h.01M19 10a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
<h4 class="font-semibold text-slate-900 dark:text-white">Jobs starten</h4>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||||
|
Benutzer kann eigene Druckjobs ohne Admin-Genehmigung starten
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="can_start_jobs"
|
||||||
|
{% if user.permissions and user.permissions.can_start_jobs %}checked{% endif %}>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Permission: Needs Approval -->
|
||||||
|
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<svg class="w-5 h-5 text-orange-500 mr-2" 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>
|
||||||
|
<h4 class="font-semibold text-slate-900 dark:text-white">Genehmigungspflicht</h4>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||||
|
Jobs des Benutzers müssen von einem Admin genehmigt werden
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="needs_approval"
|
||||||
|
{% if not user.permissions or user.permissions.needs_approval %}checked{% endif %}>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Permission: Can Approve -->
|
||||||
|
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||||
|
<div class="flex items-start justify-between mb-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||||
|
</svg>
|
||||||
|
<h4 class="font-semibold text-slate-900 dark:text-white">Jobs genehmigen</h4>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||||
|
Benutzer kann Gastanfragen und fremde Jobs genehmigen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="can_approve_jobs"
|
||||||
|
{% if user.permissions and user.permissions.can_approve_jobs %}checked{% endif %}>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="flex items-center text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
<span>Änderungen werden sofort gespeichert</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<a href="{{ url_for('admin_page', tab='users') }}"
|
||||||
|
class="group inline-flex items-center px-8 py-4 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-white dark:hover:bg-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl border border-white/20 dark:border-slate-700/50">
|
||||||
|
<svg class="w-5 h-5 mr-2 transition-transform group-hover:-translate-x-1" 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>
|
||||||
|
<span class="font-medium">Abbrechen</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="btn-gradient group inline-flex items-center px-8 py-4 text-white rounded-2xl hover:shadow-2xl transition-all duration-300 shadow-xl font-semibold text-lg relative overflow-hidden">
|
||||||
|
<svg class="w-5 h-5 mr-3 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
|
</svg>
|
||||||
|
<span>Änderungen speichern</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Enhanced JavaScript for better UX -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const form = document.getElementById('userEditForm');
|
||||||
|
const submitButton = form.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
// Add smooth loading state
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
submitButton.disabled = true;
|
||||||
|
submitButton.innerHTML = `
|
||||||
|
<svg class="w-5 h-5 mr-3 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" 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"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Speichere...</span>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enhanced input focus effects
|
||||||
|
const inputs = form.querySelectorAll('.premium-input');
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('focus', function() {
|
||||||
|
this.parentElement.classList.add('focused');
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', function() {
|
||||||
|
this.parentElement.classList.remove('focused');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth scroll on form errors
|
||||||
|
const errors = document.querySelectorAll('.error-message');
|
||||||
|
if (errors.length > 0) {
|
||||||
|
errors[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle switch animations
|
||||||
|
const toggles = document.querySelectorAll('.toggle-switch input');
|
||||||
|
toggles.forEach(toggle => {
|
||||||
|
toggle.addEventListener('change', function() {
|
||||||
|
const slider = this.nextElementSibling;
|
||||||
|
if (this.checked) {
|
||||||
|
slider.style.transform = 'scale(1.1)';
|
||||||
|
setTimeout(() => {
|
||||||
|
slider.style.transform = 'scale(1)';
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation feedback
|
||||||
|
const requiredInputs = form.querySelectorAll('input[required]');
|
||||||
|
requiredInputs.forEach(input => {
|
||||||
|
input.addEventListener('blur', function() {
|
||||||
|
if (this.value.trim() === '') {
|
||||||
|
this.classList.add('border-red-300');
|
||||||
|
this.classList.remove('border-green-300');
|
||||||
|
} else {
|
||||||
|
this.classList.add('border-green-300');
|
||||||
|
this.classList.remove('border-red-300');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-save notification (mock)
|
||||||
|
let saveTimeout;
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
clearTimeout(saveTimeout);
|
||||||
|
saveTimeout = setTimeout(() => {
|
||||||
|
// Could implement auto-save here
|
||||||
|
console.log('Changes detected - auto-save ready');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -214,7 +214,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3 ml-6">
|
||||||
<button id="refresh-btn"
|
<button id="refresh-btn"
|
||||||
class="inline-flex items-center px-4 py-3 bg-blue-500 dark:bg-blue-600 text-white rounded-xl hover:bg-blue-600 dark:hover:bg-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-105">
|
class="inline-flex items-center px-4 py-3 bg-blue-500 dark:bg-blue-600 text-white rounded-xl hover:bg-blue-600 dark:hover:bg-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl hover:scale-105">
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
@ -1,412 +1,442 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
{% block title %}Gastauftrag Status-Abfrage - Mercedes-Benz TBA Marienfelde{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% block extra_css %}
|
||||||
<title>Gastauftrag Status-Abfrage - Mercedes-Benz TBA Marienfelde</title>
|
<!-- Zusätzliche Styles für diese Seite -->
|
||||||
<link href="{{ url_for('static', filename='css/output.css') }}" rel="stylesheet">
|
<style>
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
.status-card {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
.dark .status-card {
|
||||||
.status-card {
|
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
|
||||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
border-color: rgba(51, 65, 85, 0.8);
|
||||||
border: 1px solid #e2e8f0;
|
}
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
.otp-input {
|
||||||
transition: all 0.3s ease;
|
font-family: 'Courier New', monospace;
|
||||||
}
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
.dark .status-card {
|
letter-spacing: 0.5rem;
|
||||||
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
text-transform: uppercase;
|
||||||
border-color: #334155;
|
}
|
||||||
}
|
|
||||||
|
.status-badge {
|
||||||
.otp-input {
|
display: inline-flex;
|
||||||
font-family: 'Courier New', monospace;
|
align-items: center;
|
||||||
font-size: 1.5rem;
|
padding: 0.5rem 1rem;
|
||||||
text-align: center;
|
border-radius: 9999px;
|
||||||
letter-spacing: 0.5rem;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-pending {
|
||||||
display: inline-flex;
|
background-color: rgba(254, 243, 199, 0.8);
|
||||||
align-items: center;
|
color: #92400e;
|
||||||
padding: 0.5rem 1rem;
|
}
|
||||||
border-radius: 9999px;
|
.status-approved {
|
||||||
font-weight: 500;
|
background-color: rgba(209, 250, 229, 0.8);
|
||||||
font-size: 0.875rem;
|
color: #065f46;
|
||||||
}
|
}
|
||||||
|
.status-rejected {
|
||||||
.status-pending { background-color: #fef3c7; color: #92400e; }
|
background-color: rgba(254, 226, 226, 0.8);
|
||||||
.status-approved { background-color: #d1fae5; color: #065f46; }
|
color: #991b1b;
|
||||||
.status-rejected { background-color: #fee2e2; color: #991b1b; }
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.dark .status-pending {
|
||||||
border: 2px solid #f3f4f6;
|
background-color: rgba(146, 64, 14, 0.2);
|
||||||
border-top: 2px solid #0073ce;
|
color: #fbbf24;
|
||||||
border-radius: 50%;
|
}
|
||||||
width: 1rem;
|
.dark .status-approved {
|
||||||
height: 1rem;
|
background-color: rgba(6, 95, 70, 0.2);
|
||||||
animation: spin 1s linear infinite;
|
color: #34d399;
|
||||||
}
|
}
|
||||||
|
.dark .status-rejected {
|
||||||
@keyframes spin {
|
background-color: rgba(153, 27, 27, 0.2);
|
||||||
0% { transform: rotate(0deg); }
|
color: #f87171;
|
||||||
100% { transform: rotate(360deg); }
|
}
|
||||||
}
|
|
||||||
</style>
|
.loading-spinner {
|
||||||
</head>
|
border: 2px solid rgba(243, 244, 246, 0.3);
|
||||||
<body class="bg-gray-50 font-mercedes">
|
border-top: 2px solid #0073ce;
|
||||||
<div class="min-h-screen flex items-center justify-center p-4">
|
border-radius: 50%;
|
||||||
<div class="max-w-md w-full space-y-8">
|
width: 1rem;
|
||||||
<!-- Header -->
|
height: 1rem;
|
||||||
<div class="text-center">
|
animation: spin 1s linear infinite;
|
||||||
<div class="mx-auto h-12 w-12 bg-mercedes-blue rounded-full flex items-center justify-center">
|
}
|
||||||
<svg class="h-6 w-6 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"/>
|
@keyframes spin {
|
||||||
</svg>
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mercedes-blue {
|
||||||
|
background-color: #0073ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-mercedes {
|
||||||
|
font-family: 'Mercedes-Benz Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="min-h-screen flex items-center justify-center p-4">
|
||||||
|
<div class="max-w-md w-full space-y-8">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="mx-auto h-12 w-12 mercedes-blue rounded-full flex items-center justify-center">
|
||||||
|
<svg class="h-6 w-6 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>
|
||||||
|
<h2 class="mt-6 text-3xl font-bold text-slate-900 dark:text-white">
|
||||||
|
Auftragsstatus prüfen
|
||||||
|
</h2>
|
||||||
|
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Geben Sie Ihren Statuscode ein, um Informationen über Ihren Gastauftrag zu erhalten
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status-Abfrage-Formular -->
|
||||||
|
<div class="status-card p-6" id="query-form">
|
||||||
|
<form id="status-form" class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label for="otp_code" class="block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
Statuscode (16 Zeichen)
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="otp_code"
|
||||||
|
name="otp_code"
|
||||||
|
maxlength="16"
|
||||||
|
class="otp-input mt-1 appearance-none relative block w-full px-3 py-2 border border-slate-300 dark:border-slate-600 placeholder-slate-500 dark:placeholder-slate-400 text-slate-900 dark:text-white bg-white dark:bg-slate-800 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||||
|
placeholder="XXXXXXXXXXXXXXXX"
|
||||||
|
required>
|
||||||
|
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
Der Code wurde Ihnen bei der Antragsstellung mitgeteilt
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="mt-6 text-3xl font-bold text-gray-900">
|
|
||||||
Auftragsstatus prüfen
|
<div>
|
||||||
</h2>
|
<label for="email" class="block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
E-Mail-Adresse (optional)
|
||||||
Geben Sie Ihren Statuscode ein, um Informationen über Ihren Gastauftrag zu erhalten
|
</label>
|
||||||
</p>
|
<input type="email"
|
||||||
</div>
|
id="email"
|
||||||
|
name="email"
|
||||||
<!-- Status-Abfrage-Formular -->
|
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-slate-300 dark:border-slate-600 placeholder-slate-500 dark:placeholder-slate-400 text-slate-900 dark:text-white bg-white dark:bg-slate-800 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||||
<div class="status-card p-6" id="query-form">
|
placeholder="ihre.email@example.com">
|
||||||
<form id="status-form" class="space-y-6">
|
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
||||||
<div>
|
Zusätzliche Sicherheit (empfohlen)
|
||||||
<label for="otp_code" class="block text-sm font-medium text-gray-700">
|
</p>
|
||||||
Statuscode (16 Zeichen)
|
|
||||||
</label>
|
|
||||||
<input type="text"
|
|
||||||
id="otp_code"
|
|
||||||
name="otp_code"
|
|
||||||
maxlength="16"
|
|
||||||
class="otp-input mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-mercedes-blue focus:border-mercedes-blue focus:z-10 sm:text-sm"
|
|
||||||
placeholder="XXXXXXXXXXXXXXXX"
|
|
||||||
required>
|
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
|
||||||
Der Code wurde Ihnen bei der Antragsstellung mitgeteilt
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="email" class="block text-sm font-medium text-gray-700">
|
|
||||||
E-Mail-Adresse (optional)
|
|
||||||
</label>
|
|
||||||
<input type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
class="mt-1 appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-mercedes-blue focus:border-mercedes-blue focus:z-10 sm:text-sm"
|
|
||||||
placeholder="ihre.email@example.com">
|
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
|
||||||
Zusätzliche Sicherheit (empfohlen)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button type="submit"
|
|
||||||
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-mercedes-blue hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
||||||
<span id="submit-text">Status prüfen</span>
|
|
||||||
<span id="loading-spinner" class="loading-spinner ml-2 hidden"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status-Ergebnis -->
|
|
||||||
<div id="status-result" class="hidden">
|
|
||||||
<div class="status-card p-6">
|
|
||||||
<div id="status-content">
|
|
||||||
<!-- Wird per JavaScript gefüllt -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex gap-3">
|
|
||||||
<button onclick="resetForm()"
|
|
||||||
class="flex-1 py-2 px-4 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
||||||
Neue Abfrage
|
|
||||||
</button>
|
|
||||||
<button onclick="refreshStatus()"
|
|
||||||
class="flex-1 py-2 px-4 border border-transparent rounded-md text-sm font-medium text-white bg-mercedes-blue hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
||||||
Aktualisieren
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Fehleranzeige -->
|
<div>
|
||||||
<div id="error-message" class="hidden">
|
<button type="submit"
|
||||||
<div class="status-card p-6 border-l-4 border-red-500">
|
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white mercedes-blue hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
<div class="flex">
|
<span id="submit-text">Status prüfen</span>
|
||||||
<div class="flex-shrink-0">
|
<span id="loading-spinner" class="loading-spinner ml-2 hidden"></span>
|
||||||
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
</button>
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium text-red-800">Fehler</h3>
|
|
||||||
<div class="mt-2 text-sm text-red-700" id="error-text">
|
|
||||||
<!-- Wird per JavaScript gefüllt -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<button onclick="resetForm()"
|
|
||||||
class="py-2 px-4 border border-transparent rounded-md text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
|
||||||
Erneut versuchen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Status-Ergebnis -->
|
||||||
<div class="text-center">
|
<div id="status-result" class="hidden">
|
||||||
<p class="text-xs text-gray-500">
|
<div class="status-card p-6">
|
||||||
Mercedes-Benz Technische Berufsausbildung Marienfelde<br>
|
<div id="status-content">
|
||||||
<a href="/guest/request" class="text-mercedes-blue hover:underline">Neuen Antrag stellen</a>
|
<!-- Wird per JavaScript gefüllt -->
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex gap-3">
|
||||||
|
<button onclick="resetForm()"
|
||||||
|
class="flex-1 py-2 px-4 border border-slate-300 dark:border-slate-600 rounded-md text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Neue Abfrage
|
||||||
|
</button>
|
||||||
|
<button onclick="refreshStatus()"
|
||||||
|
class="flex-1 py-2 px-4 border border-transparent rounded-md text-sm font-medium text-white mercedes-blue hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Aktualisieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<!-- Fehleranzeige -->
|
||||||
let currentRequestData = null;
|
<div id="error-message" class="hidden">
|
||||||
|
<div class="status-card p-6 border-l-4 border-red-500">
|
||||||
// Formular-Submit-Handler
|
<div class="flex">
|
||||||
document.getElementById('status-form').addEventListener('submit', async function(e) {
|
<div class="flex-shrink-0">
|
||||||
e.preventDefault();
|
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||||
const otpCode = document.getElementById('otp_code').value.trim();
|
</svg>
|
||||||
const email = document.getElementById('email').value.trim();
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
if (!otpCode) {
|
<h3 class="text-sm font-medium text-red-800 dark:text-red-400">Fehler</h3>
|
||||||
showError('Bitte geben Sie Ihren Statuscode ein.');
|
<div class="mt-2 text-sm text-red-700 dark:text-red-300" id="error-text">
|
||||||
return;
|
<!-- Wird per JavaScript gefüllt -->
|
||||||
}
|
</div>
|
||||||
|
</div>
|
||||||
if (otpCode.length !== 16) {
|
|
||||||
showError('Der Statuscode muss genau 16 Zeichen lang sein.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
hideAll();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/guest/api/guest/status', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
otp_code: otpCode,
|
|
||||||
email: email || undefined
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
currentRequestData = data.request;
|
|
||||||
showStatus(data.request);
|
|
||||||
} else {
|
|
||||||
showError(data.message || 'Ungültiger Code oder E-Mail-Adresse');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler bei Status-Abfrage:', error);
|
|
||||||
showError('Verbindungsfehler. Bitte versuchen Sie es später erneut.');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Status anzeigen
|
|
||||||
function showStatus(request) {
|
|
||||||
const statusContent = document.getElementById('status-content');
|
|
||||||
|
|
||||||
// Status-Badge
|
|
||||||
let statusBadge = '';
|
|
||||||
let statusIcon = '';
|
|
||||||
|
|
||||||
switch (request.status) {
|
|
||||||
case 'pending':
|
|
||||||
statusBadge = '<span class="status-badge status-pending">🕒 In Bearbeitung</span>';
|
|
||||||
statusIcon = '🕒';
|
|
||||||
break;
|
|
||||||
case 'approved':
|
|
||||||
statusBadge = '<span class="status-badge status-approved">✅ Genehmigt</span>';
|
|
||||||
statusIcon = '✅';
|
|
||||||
break;
|
|
||||||
case 'rejected':
|
|
||||||
statusBadge = '<span class="status-badge status-rejected">❌ Abgelehnt</span>';
|
|
||||||
statusIcon = '❌';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
statusBadge = '<span class="status-badge">❓ Unbekannt</span>';
|
|
||||||
statusIcon = '❓';
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = `
|
|
||||||
<div class="text-center mb-6">
|
|
||||||
<div class="text-4xl mb-2">${statusIcon}</div>
|
|
||||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
|
||||||
Antrag von ${request.name}
|
|
||||||
</h3>
|
|
||||||
${statusBadge}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="mt-4">
|
||||||
<div class="bg-gray-50 p-4 rounded-lg">
|
<button onclick="resetForm()"
|
||||||
<h4 class="font-medium text-gray-900 mb-2">Antragsdetails</h4>
|
class="py-2 px-4 border border-transparent rounded-md text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||||
|
Erneut versuchen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
Mercedes-Benz Technische Berufsausbildung Marienfelde<br>
|
||||||
|
<a href="/guest/request" class="text-blue-600 dark:text-blue-400 hover:underline">Neuen Antrag stellen</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
let currentRequestData = null;
|
||||||
|
|
||||||
|
// Formular-Submit-Handler
|
||||||
|
document.getElementById('status-form').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const otpCode = document.getElementById('otp_code').value.trim();
|
||||||
|
const email = document.getElementById('email').value.trim();
|
||||||
|
|
||||||
|
if (!otpCode) {
|
||||||
|
showError('Bitte geben Sie Ihren Statuscode ein.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otpCode.length !== 16) {
|
||||||
|
showError('Der Statuscode muss genau 16 Zeichen lang sein.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
hideAll();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/guest/api/guest/status', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
otp_code: otpCode,
|
||||||
|
email: email || undefined
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
currentRequestData = data.request;
|
||||||
|
showStatus(data.request);
|
||||||
|
} else {
|
||||||
|
showError(data.message || 'Ungültiger Code oder E-Mail-Adresse');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler bei Status-Abfrage:', error);
|
||||||
|
showError('Verbindungsfehler. Bitte versuchen Sie es später erneut.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Status anzeigen
|
||||||
|
function showStatus(request) {
|
||||||
|
const statusContent = document.getElementById('status-content');
|
||||||
|
|
||||||
|
// Status-Badge
|
||||||
|
let statusBadge = '';
|
||||||
|
let statusIcon = '';
|
||||||
|
|
||||||
|
switch (request.status) {
|
||||||
|
case 'pending':
|
||||||
|
statusBadge = '<span class="status-badge status-pending">🕒 In Bearbeitung</span>';
|
||||||
|
statusIcon = '🕒';
|
||||||
|
break;
|
||||||
|
case 'approved':
|
||||||
|
statusBadge = '<span class="status-badge status-approved">✅ Genehmigt</span>';
|
||||||
|
statusIcon = '✅';
|
||||||
|
break;
|
||||||
|
case 'rejected':
|
||||||
|
statusBadge = '<span class="status-badge status-rejected">❌ Abgelehnt</span>';
|
||||||
|
statusIcon = '❌';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusBadge = '<span class="status-badge">❓ Unbekannt</span>';
|
||||||
|
statusIcon = '❓';
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="text-4xl mb-2">${statusIcon}</div>
|
||||||
|
<h3 class="text-xl font-semibold text-slate-900 dark:text-white mb-2">
|
||||||
|
Antrag von ${request.name}
|
||||||
|
</h3>
|
||||||
|
${statusBadge}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="bg-slate-50 dark:bg-slate-800/50 p-4 rounded-lg">
|
||||||
|
<h4 class="font-medium text-slate-900 dark:text-white mb-2">Antragsdetails</h4>
|
||||||
|
<dl class="space-y-1 text-sm">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<dt class="text-slate-600 dark:text-slate-400">Erstellt am:</dt>
|
||||||
|
<dd class="text-slate-900 dark:text-white">${formatDate(request.created_at)}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<dt class="text-slate-600 dark:text-slate-400">Dauer:</dt>
|
||||||
|
<dd class="text-slate-900 dark:text-white">${request.duration_min} Minuten</dd>
|
||||||
|
</div>
|
||||||
|
${request.file_name ? `
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<dt class="text-slate-600 dark:text-slate-400">Datei:</dt>
|
||||||
|
<dd class="text-slate-900 dark:text-white">${request.file_name}</dd>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||||
|
<p class="text-sm text-blue-800 dark:text-blue-300">
|
||||||
|
${request.message}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${request.status === 'approved' && request.can_start_job ? `
|
||||||
|
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-green-800 dark:text-green-300 mb-2">🎯 Bereit zum Drucken!</h4>
|
||||||
|
<p class="text-sm text-green-700 dark:text-green-400 mb-3">
|
||||||
|
Ihr Auftrag wurde genehmigt. Sie können mit dem 3D-Druck beginnen.
|
||||||
|
</p>
|
||||||
|
<a href="/guest/start-job"
|
||||||
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700">
|
||||||
|
🚀 Jetzt drucken
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${request.status === 'rejected' && request.rejection_reason ? `
|
||||||
|
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-red-800 dark:text-red-300 mb-2">Ablehnungsgrund:</h4>
|
||||||
|
<p class="text-sm text-red-700 dark:text-red-400">${request.rejection_reason}</p>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
${request.job ? `
|
||||||
|
<div class="bg-indigo-50 dark:bg-indigo-900/20 border border-indigo-200 dark:border-indigo-800 rounded-lg p-4">
|
||||||
|
<h4 class="font-medium text-indigo-800 dark:text-indigo-300 mb-2">📋 Job-Informationen</h4>
|
||||||
<dl class="space-y-1 text-sm">
|
<dl class="space-y-1 text-sm">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600">Erstellt am:</dt>
|
<dt class="text-indigo-600 dark:text-indigo-400">Job-Name:</dt>
|
||||||
<dd class="text-gray-900">${formatDate(request.created_at)}</dd>
|
<dd class="text-indigo-900 dark:text-indigo-200">${request.job.name}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600">Dauer:</dt>
|
<dt class="text-indigo-600 dark:text-indigo-400">Status:</dt>
|
||||||
<dd class="text-gray-900">${request.duration_min} Minuten</dd>
|
<dd class="text-indigo-900 dark:text-indigo-200">${request.job.status}</dd>
|
||||||
</div>
|
</div>
|
||||||
${request.file_name ? `
|
${request.job.printer_name ? `
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<dt class="text-gray-600">Datei:</dt>
|
<dt class="text-indigo-600 dark:text-indigo-400">Drucker:</dt>
|
||||||
<dd class="text-gray-900">${request.file_name}</dd>
|
<dd class="text-indigo-900 dark:text-indigo-200">${request.job.printer_name}</dd>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
` : ''}
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
</div>
|
||||||
<p class="text-sm text-blue-800">
|
`;
|
||||||
${request.message}
|
|
||||||
</p>
|
statusContent.innerHTML = html;
|
||||||
</div>
|
document.getElementById('status-result').classList.remove('hidden');
|
||||||
|
}
|
||||||
${request.status === 'approved' && request.can_start_job ? `
|
|
||||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
// Fehler anzeigen
|
||||||
<h4 class="font-medium text-green-800 mb-2">🎯 Bereit zum Drucken!</h4>
|
function showError(message) {
|
||||||
<p class="text-sm text-green-700 mb-3">
|
document.getElementById('error-text').textContent = message;
|
||||||
Ihr Auftrag wurde genehmigt. Sie können mit dem 3D-Druck beginnen.
|
document.getElementById('error-message').classList.remove('hidden');
|
||||||
</p>
|
}
|
||||||
<a href="/guest/start-job"
|
|
||||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700">
|
// Loading-Zustand setzen
|
||||||
🚀 Jetzt drucken
|
function setLoading(loading) {
|
||||||
</a>
|
const submitText = document.getElementById('submit-text');
|
||||||
</div>
|
const loadingSpinner = document.getElementById('loading-spinner');
|
||||||
` : ''}
|
const submitButton = document.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
${request.status === 'rejected' && request.rejection_reason ? `
|
if (loading) {
|
||||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
|
submitText.textContent = 'Prüfe...';
|
||||||
<h4 class="font-medium text-red-800 mb-2">Ablehnungsgrund:</h4>
|
loadingSpinner.classList.remove('hidden');
|
||||||
<p class="text-sm text-red-700">${request.rejection_reason}</p>
|
submitButton.disabled = true;
|
||||||
</div>
|
} else {
|
||||||
` : ''}
|
submitText.textContent = 'Status prüfen';
|
||||||
|
loadingSpinner.classList.add('hidden');
|
||||||
${request.job ? `
|
submitButton.disabled = false;
|
||||||
<div class="bg-indigo-50 border border-indigo-200 rounded-lg p-4">
|
}
|
||||||
<h4 class="font-medium text-indigo-800 mb-2">📋 Job-Informationen</h4>
|
}
|
||||||
<dl class="space-y-1 text-sm">
|
|
||||||
<div class="flex justify-between">
|
// Alle Anzeigen ausblenden
|
||||||
<dt class="text-indigo-600">Job-Name:</dt>
|
function hideAll() {
|
||||||
<dd class="text-indigo-900">${request.job.name}</dd>
|
document.getElementById('status-result').classList.add('hidden');
|
||||||
</div>
|
document.getElementById('error-message').classList.add('hidden');
|
||||||
<div class="flex justify-between">
|
}
|
||||||
<dt class="text-indigo-600">Status:</dt>
|
|
||||||
<dd class="text-indigo-900">${request.job.status}</dd>
|
// Formular zurücksetzen
|
||||||
</div>
|
function resetForm() {
|
||||||
${request.job.printer_name ? `
|
document.getElementById('status-form').reset();
|
||||||
<div class="flex justify-between">
|
hideAll();
|
||||||
<dt class="text-indigo-600">Drucker:</dt>
|
document.getElementById('query-form').classList.remove('hidden');
|
||||||
<dd class="text-indigo-900">${request.job.printer_name}</dd>
|
currentRequestData = null;
|
||||||
</div>
|
}
|
||||||
` : ''}
|
|
||||||
</dl>
|
// Status aktualisieren
|
||||||
</div>
|
function refreshStatus() {
|
||||||
` : ''}
|
if (currentRequestData) {
|
||||||
</div>
|
const otpCode = document.getElementById('otp_code').value;
|
||||||
`;
|
const email = document.getElementById('email').value;
|
||||||
|
|
||||||
statusContent.innerHTML = html;
|
// Formular erneut abschicken
|
||||||
document.getElementById('status-result').classList.remove('hidden');
|
document.getElementById('status-form').dispatchEvent(new Event('submit'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fehler anzeigen
|
// Datum formatieren
|
||||||
function showError(message) {
|
function formatDate(dateString) {
|
||||||
document.getElementById('error-text').textContent = message;
|
if (!dateString) return 'Unbekannt';
|
||||||
document.getElementById('error-message').classList.remove('hidden');
|
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('de-DE', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return dateString;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loading-Zustand setzen
|
// OTP-Input Formatierung
|
||||||
function setLoading(loading) {
|
document.getElementById('otp_code').addEventListener('input', function(e) {
|
||||||
const submitText = document.getElementById('submit-text');
|
// Nur alphanumerische Zeichen erlauben
|
||||||
const loadingSpinner = document.getElementById('loading-spinner');
|
e.target.value = e.target.value.replace(/[^A-Fa-f0-9]/g, '').toUpperCase();
|
||||||
const submitButton = document.querySelector('button[type="submit"]');
|
});
|
||||||
|
</script>
|
||||||
if (loading) {
|
{% endblock %}
|
||||||
submitText.textContent = 'Prüfe...';
|
|
||||||
loadingSpinner.classList.remove('hidden');
|
|
||||||
submitButton.disabled = true;
|
|
||||||
} else {
|
|
||||||
submitText.textContent = 'Status prüfen';
|
|
||||||
loadingSpinner.classList.add('hidden');
|
|
||||||
submitButton.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alle Anzeigen ausblenden
|
|
||||||
function hideAll() {
|
|
||||||
document.getElementById('status-result').classList.add('hidden');
|
|
||||||
document.getElementById('error-message').classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formular zurücksetzen
|
|
||||||
function resetForm() {
|
|
||||||
document.getElementById('status-form').reset();
|
|
||||||
hideAll();
|
|
||||||
document.getElementById('query-form').classList.remove('hidden');
|
|
||||||
currentRequestData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status aktualisieren
|
|
||||||
function refreshStatus() {
|
|
||||||
if (currentRequestData) {
|
|
||||||
const otpCode = document.getElementById('otp_code').value;
|
|
||||||
const email = document.getElementById('email').value;
|
|
||||||
|
|
||||||
// Formular erneut abschicken
|
|
||||||
document.getElementById('status-form').dispatchEvent(new Event('submit'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datum formatieren
|
|
||||||
function formatDate(dateString) {
|
|
||||||
if (!dateString) return 'Unbekannt';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
return date.toLocaleDateString('de-DE', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return dateString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OTP-Input Formatierung
|
|
||||||
document.getElementById('otp_code').addEventListener('input', function(e) {
|
|
||||||
// Nur alphanumerische Zeichen erlauben
|
|
||||||
e.target.value = e.target.value.replace(/[^A-Fa-f0-9]/g, '').toUpperCase();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -149,8 +149,8 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<!-- Chart.js CDN -->
|
<!-- Chart.js - Lokale Version -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.min.js"></script>
|
<script src="{{ url_for('static', filename='js/charts/chart.min.js') }}"></script>
|
||||||
|
|
||||||
<!-- Global Refresh Functions -->
|
<!-- Global Refresh Functions -->
|
||||||
<script src="{{ url_for('static', filename='js/global-refresh-functions.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/global-refresh-functions.js') }}"></script>
|
||||||
@ -196,7 +196,9 @@ async function loadBasicStats() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden der Basis-Statistiken:', error);
|
console.error('Fehler beim Laden der Basis-Statistiken:', error);
|
||||||
showToast('Fehler beim Laden der Statistiken', 'error');
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Fehler beim Laden der Statistiken', 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +253,9 @@ function animateCounter(element, start, end, finalText) {
|
|||||||
// Statistiken neu laden
|
// Statistiken neu laden
|
||||||
function refreshStats() {
|
function refreshStats() {
|
||||||
// Feedback für den Benutzer
|
// Feedback für den Benutzer
|
||||||
showToast('Statistiken werden aktualisiert...', 'info');
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Statistiken werden aktualisiert...', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
// Basis-Statistiken laden
|
// Basis-Statistiken laden
|
||||||
loadBasicStats();
|
loadBasicStats();
|
||||||
@ -263,7 +267,9 @@ function refreshStats() {
|
|||||||
|
|
||||||
// Erfolgsmeldung
|
// Erfolgsmeldung
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showToast('Statistiken erfolgreich aktualisiert', 'success');
|
if (typeof showFlashMessage === 'function') {
|
||||||
|
showFlashMessage('Statistiken erfolgreich aktualisiert', 'success');
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,19 +278,5 @@ function exportStats() {
|
|||||||
// Direkter Download vom API-Endpunkt
|
// Direkter Download vom API-Endpunkt
|
||||||
window.location.href = '/api/stats/export';
|
window.location.href = '/api/stats/export';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper-Funktion für Toast-Benachrichtigungen
|
|
||||||
function showToast(message, type) {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast(message, type);
|
|
||||||
} else {
|
|
||||||
// Fallback für einfache Alert
|
|
||||||
if (type === 'error') {
|
|
||||||
alert('Fehler: ' + message);
|
|
||||||
} else {
|
|
||||||
console.log(type + ': ' + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user