Files
Projektarbeit-MYP/backend/app/templates/admin.html

967 lines
45 KiB
HTML

{% extends "base.html" %}
{% block title %}Admin Panel - Mercedes-Benz MYP Platform{% endblock %}
{% block head %}
{{ super() }}
<!-- CSRF Token für AJAX-Anfragen -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="{{ url_for('static', filename='js/admin.js') }}" defer></script>
<style>
/* Mercedes-Benz Admin Dashboard Styles */
.admin-hero {
background: linear-gradient(135deg, #0072ce 0%, #004d8c 100%);
position: relative;
overflow: hidden;
}
.admin-hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") repeat;
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.stat-card-modern {
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 20px;
padding: 2rem;
position: relative;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.dark .stat-card-modern {
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
border-color: rgba(51, 65, 85, 0.6);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.stat-card-modern:hover {
transform: translateY(-8px) scale(1.02);
box-shadow: 0 20px 40px rgba(0, 114, 206, 0.15);
}
.dark .stat-card-modern:hover {
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
.stat-card-modern::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #0072ce, #00a8cc, #0072ce);
background-size: 200% 100%;
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { background-position: 200% 0; }
50% { background-position: -200% 0; }
}
.stat-icon-modern {
width: 4rem;
height: 4rem;
background: linear-gradient(135deg, #0072ce, #00a8cc);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-bottom: 1.5rem;
box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3);
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3); }
50% { box-shadow: 0 8px 24px rgba(0, 114, 206, 0.5); }
}
.tab-modern {
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 16px;
padding: 1rem 2rem;
margin: 0 0.5rem;
color: #64748b;
text-decoration: none;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
backdrop-filter: blur(10px);
}
.dark .tab-modern {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(51, 65, 85, 0.6);
color: #94a3b8;
}
.tab-modern:hover {
transform: translateY(-2px);
background: rgba(0, 114, 206, 0.1);
color: #0072ce;
border-color: #0072ce;
}
.tab-modern.active {
background: linear-gradient(135deg, #0072ce, #00a8cc);
color: white;
border-color: transparent;
box-shadow: 0 8px 16px rgba(0, 114, 206, 0.3);
}
.content-card-modern {
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 24px;
padding: 2.5rem;
backdrop-filter: blur(20px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.dark .content-card-modern {
background: rgba(30, 41, 59, 0.95);
border-color: rgba(51, 65, 85, 0.6);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
}
.table-modern {
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(226, 232, 240, 0.8);
}
.dark .table-modern {
border-color: rgba(51, 65, 85, 0.6);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.table-modern thead {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
}
.dark .table-modern thead {
background: linear-gradient(135deg, #1e293b, #0f172a);
}
.table-modern tbody tr:hover {
background: rgba(0, 114, 206, 0.05);
transform: scale(1.01);
transition: all 0.2s ease;
}
.dark .table-modern tbody tr:hover {
background: rgba(0, 114, 206, 0.1);
}
.btn-modern {
background: linear-gradient(135deg, #0072ce, #00a8cc);
border: none;
border-radius: 12px;
padding: 0.75rem 1.5rem;
color: white;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 114, 206, 0.3);
}
.btn-modern:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 114, 206, 0.4);
color: white;
}
.btn-success-modern {
background: linear-gradient(135deg, #10b981, #059669);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
.btn-success-modern:hover {
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
.btn-danger-modern {
background: linear-gradient(135deg, #ef4444, #dc2626);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
.btn-danger-modern:hover {
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.4);
}
.form-modern {
background: rgba(248, 250, 252, 0.8);
border: 1px solid rgba(226, 232, 240, 0.8);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(10px);
}
.dark .form-modern {
background: rgba(30, 41, 59, 0.8);
border-color: rgba(51, 65, 85, 0.6);
}
.input-modern {
background: rgba(255, 255, 255, 0.9);
border: 2px solid rgba(226, 232, 240, 0.8);
border-radius: 12px;
padding: 0.75rem 1rem;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.dark .input-modern {
background: rgba(30, 41, 59, 0.9);
border-color: rgba(51, 65, 85, 0.6);
color: white;
}
.input-modern:focus {
border-color: #0072ce;
box-shadow: 0 0 0 3px rgba(0, 114, 206, 0.1);
outline: none;
}
.status-indicator-modern {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
animation: pulse 2s infinite;
}
.status-online-modern {
background: linear-gradient(135deg, #10b981, #059669);
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
}
.status-offline-modern {
background: linear-gradient(135deg, #ef4444, #dc2626);
box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.progress-bar-modern {
background: rgba(226, 232, 240, 0.5);
border-radius: 10px;
height: 8px;
overflow: hidden;
position: relative;
}
.progress-fill-modern {
height: 100%;
border-radius: 10px;
background: linear-gradient(90deg, #0072ce, #00a8cc);
transition: width 0.5s ease;
position: relative;
}
.progress-fill-modern::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: progress-shine 2s ease-in-out infinite;
}
@keyframes progress-shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@keyframes animate-fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: animate-fade-in-up 0.6s ease-out forwards;
}
</style>
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
<!-- Hero Section -->
<div class="admin-hero text-white py-16 mb-8">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center">
<h1 class="text-5xl font-bold mb-4 tracking-tight">
Mercedes-Benz Admin Panel
</h1>
<p class="text-xl opacity-90 max-w-2xl mx-auto">
Verwalten Sie Ihr MYP-System mit modernster Technologie und Mercedes-Benz Qualität
</p>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12">
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
<div class="stat-card-modern">
<div class="stat-icon-modern">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Benutzer</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.total_users }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Registrierte Benutzer</div>
</div>
<div class="stat-card-modern">
<div class="stat-icon-modern">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Drucker</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.total_printers }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Verbundene Drucker</div>
</div>
<div class="stat-card-modern">
<div class="stat-icon-modern">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Aktive Jobs</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.active_jobs }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Laufende Druckaufträge</div>
</div>
<div class="stat-card-modern">
<div class="stat-icon-modern">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2 uppercase tracking-wider">Erfolgsrate</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white mb-1">{{ stats.success_rate }}%</div>
<div class="text-sm text-slate-500 dark:text-slate-400">Erfolgreiche Druckaufträge</div>
</div>
</div>
<!-- Navigation Tabs -->
<div class="flex flex-wrap justify-center gap-4 mb-8">
<a href="{{ url_for('admin_page', tab='users') }}" class="tab-modern {{ 'active' if active_tab == 'users' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
Benutzer
</a>
<a href="{{ url_for('admin_page', tab='printers') }}" class="tab-modern {{ 'active' if active_tab == 'printers' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
Drucker
</a>
<a href="{{ url_for('admin_page', tab='scheduler') }}" class="tab-modern {{ 'active' if active_tab == 'scheduler' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Scheduler
</a>
<a href="{{ url_for('admin_page', tab='system') }}" class="tab-modern {{ 'active' if active_tab == 'system' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
System
</a>
<a href="{{ url_for('admin_page', tab='logs') }}" class="tab-modern {{ 'active' if active_tab == 'logs' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
Logs
</a>
<a href="{{ url_for('admin_page', tab='ssl') }}" class="tab-modern {{ 'active' if active_tab == 'ssl' else '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 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>
SSL
</a>
</div>
<!-- Tab Content -->
{% if active_tab == 'users' %}
<div class="content-card-modern">
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
<h3 class="text-3xl font-bold text-slate-900 dark:text-white">Benutzerverwaltung</h3>
<form action="{{ url_for('admin_page', tab='users') }}" method="get">
<button type="submit" class="btn-modern">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Aktualisieren
</button>
</form>
</div>
<div class="table-modern mb-8">
<table class="w-full">
<thead>
<tr class="text-left">
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Benutzer</th>
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Rolle</th>
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider">Status</th>
<th class="px-6 py-4 text-sm font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wider text-right">Aktionen</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
{% for user in users %}
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors">
<td class="px-6 py-4">
<div class="flex items-center gap-4">
<div class="flex-shrink-0 h-12 w-12 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg shadow-lg">
{{ user.email[0]|upper if user.email else 'U' }}
</div>
<div>
<div class="text-base font-semibold text-slate-900 dark:text-white">{{ user.name }}</div>
<div class="text-sm text-slate-500 dark:text-slate-400">{{ user.email }}</div>
</div>
</div>
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium {{ 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' if user.is_admin else 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' }}">
{{ 'Administrator' if user.is_admin else 'Benutzer' }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center">
<div class="status-indicator-modern {{ 'status-online-modern' if user.active else 'status-offline-modern' }}"></div>
<span class="text-sm font-medium {{ 'text-green-600 dark:text-green-400' if user.active else 'text-red-600 dark:text-red-400' }}">
{{ 'Aktiv' if user.active else 'Inaktiv' }}
</span>
</div>
</td>
<td class="px-6 py-4 text-right">
<form method="post" action="{{ url_for('delete_user', user_id=user.id) }}" class="inline" onsubmit="return confirm('Benutzer wirklich löschen?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors p-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Add User Form -->
<div class="form-modern">
<h4 class="text-2xl font-semibold text-slate-900 dark:text-white mb-6">Neuen Benutzer hinzufügen</h4>
<form action="{{ url_for('create_user') }}" method="post" class="grid grid-cols-1 md:grid-cols-2 gap-6">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div>
<label for="user-email" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Email</label>
<input type="email" id="user-email" name="email" class="input-modern w-full" required>
</div>
<div>
<label for="user-name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Name</label>
<input type="text" id="user-name" name="name" class="input-modern w-full" required>
</div>
<div>
<label for="user-password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Passwort</label>
<input type="password" id="user-password" name="password" class="input-modern w-full" required>
</div>
<div>
<label for="user-role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Rolle</label>
<select id="user-role" name="role" class="input-modern w-full">
<option value="user">Benutzer</option>
<option value="admin">Administrator</option>
</select>
</div>
<div class="col-span-1 md:col-span-2 flex justify-end">
<button type="submit" class="btn-modern">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Benutzer hinzufügen
</button>
</div>
</form>
</div>
</div>
{% endif %}
<!-- Weitere Tabs bleiben unverändert, aber mit modernem Styling -->
{% if active_tab == 'system' %}
<div class="content-card-modern">
<h3 class="text-3xl font-bold text-slate-900 dark:text-white mb-8">System-Übersicht</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="form-modern">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">System Ressourcen</h4>
<div class="space-y-6">
<div>
<div class="flex justify-between mb-2">
<span class="text-slate-700 dark:text-slate-300">CPU Auslastung</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu if system_info else '0' }}%</span>
</div>
<div class="progress-bar-modern">
<div class="progress-fill-modern" style="width: {{ system_info.cpu if system_info else '0' }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-2">
<span class="text-slate-700 dark:text-slate-300">Arbeitsspeicher</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory if system_info else '0' }}%</span>
</div>
<div class="progress-bar-modern">
<div class="progress-fill-modern" style="width: {{ system_info.memory if system_info else '0' }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-2">
<span class="text-slate-700 dark:text-slate-300">Festplattenspeicher</span>
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.disk if system_info else '0' }}%</span>
</div>
<div class="progress-bar-modern">
<div class="progress-fill-modern" style="width: {{ system_info.disk if system_info else '0' }}%"></div>
</div>
</div>
</div>
</div>
<div class="form-modern">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Dienste Status</h4>
<div class="space-y-4">
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="flex items-center">
<div class="status-indicator-modern status-online-modern"></div>
<span class="text-slate-900 dark:text-white font-medium">Webserver</span>
</div>
<span class="text-green-600 dark:text-green-400 font-semibold">Online</span>
</div>
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="flex items-center">
<div class="status-indicator-modern {{ 'status-online-modern' if scheduler_status.running else 'status-offline-modern' }}"></div>
<span class="text-slate-900 dark:text-white font-medium">Scheduler</span>
</div>
<span class="{{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }} font-semibold">
{{ 'Online' if scheduler_status.running else 'Offline' }}
</span>
</div>
<div class="flex justify-between items-center p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="flex items-center">
<div class="status-indicator-modern status-online-modern"></div>
<span class="text-slate-900 dark:text-white font-medium">Datenbank</span>
</div>
<span class="text-green-600 dark:text-green-400 font-semibold">Online</span>
</div>
</div>
<div class="mt-6">
<form action="{{ url_for('admin_page', tab='system') }}" method="get">
<button type="submit" class="btn-modern w-full justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
System-Informationen aktualisieren
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Scheduler Tab -->
{% if active_tab == 'scheduler' %}
<div class="content-card-modern">
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
<h3 class="text-3xl font-bold text-slate-900 dark:text-white">Scheduler Verwaltung</h3>
<div class="flex space-x-4">
<form action="{{ url_for('start_scheduler') }}" method="post" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn-success-modern">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Scheduler starten
</button>
</form>
<form action="{{ url_for('stop_scheduler') }}" method="post" class="inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit" class="btn-danger-modern">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
</svg>
Scheduler stoppen
</button>
</form>
</div>
</div>
<div class="form-modern">
<div class="flex items-center justify-between p-6 mb-6 bg-white dark:bg-slate-700 rounded-xl">
<div class="flex items-center">
<div class="status-indicator-modern {{ 'status-online-modern' if scheduler_status.running else 'status-offline-modern' }}"></div>
<span class="text-2xl font-semibold ml-3 {{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }}">
{{ 'Aktiv' if scheduler_status.running else 'Inaktiv' }}
</span>
</div>
<div class="text-slate-700 dark:text-slate-300 text-lg">{{ scheduler_status.message }}</div>
</div>
<div class="bg-white dark:bg-slate-700 rounded-xl p-6">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-4">Scheduler Informationen</h4>
<div class="space-y-4 text-slate-700 dark:text-slate-300">
<p>Der Scheduler ist verantwortlich für die automatische Zuweisung und Ausführung von Druckaufträgen. Er überwacht kontinuierlich den Status der Drucker und der anstehenden Jobs.</p>
<ul class="list-disc list-inside space-y-2">
<li>Der Scheduler sollte während des normalen Betriebs immer aktiv sein</li>
<li>Bei Wartungsarbeiten kann der Scheduler vorübergehend deaktiviert werden</li>
<li>Nach einem Neustart des Systems muss der Scheduler manuell gestartet werden</li>
</ul>
</div>
</div>
</div>
</div>
{% endif %}
<!-- SSL Tab -->
{% if active_tab == 'ssl' %}
<div class="content-card-modern">
<div class="flex flex-col md:flex-row md:justify-between md:items-center mb-8 gap-4">
<h3 class="text-3xl font-bold text-slate-900 dark:text-white">SSL-Zertifikatsverwaltung</h3>
<button onclick="loadSSLInfo()" class="btn-modern">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Aktualisieren
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Zertifikatsinformationen -->
<div class="form-modern">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Aktuelles Zertifikat</h4>
<div id="ssl-info-container">
<div class="text-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Zertifikatsinformationen...</p>
</div>
</div>
</div>
<!-- SSL-Aktionen -->
<div class="form-modern">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Zertifikatsverwaltung</h4>
<div class="space-y-4">
<button onclick="generateSSLCertificate()" class="btn-modern w-full justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Neues Zertifikat generieren
</button>
<button onclick="installSSLCertificate()" class="btn-success-modern w-full justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Im System installieren
</button>
<button onclick="copyToRaspberry()" class="btn-modern w-full justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
Auf Raspberry Pi kopieren
</button>
<button onclick="validateSSLCertificate()" class="btn-modern w-full justify-center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Zertifikat validieren
</button>
</div>
</div>
</div>
<!-- Erweiterte Optionen -->
<div class="form-modern mt-8">
<h4 class="text-xl font-semibold text-slate-900 dark:text-white mb-6">Erweiterte Optionen</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="ssl-key-size" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Schlüsselgröße (Bits)</label>
<select id="ssl-key-size" class="input-modern w-full">
<option value="2048">2048 Bit</option>
<option value="4096" selected>4096 Bit (Empfohlen)</option>
<option value="8192">8192 Bit</option>
</select>
</div>
<div>
<label for="ssl-validity" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">Gültigkeitsdauer (Tage)</label>
<input type="number" id="ssl-validity" value="365" min="30" max="3650" class="input-modern w-full">
</div>
</div>
</div>
</div>
{% endif %}
<!-- Weitere Tabs können hier hinzugefügt werden -->
</div>
</div>
<script>
// SSL-Verwaltungsfunktionen
async function loadSSLInfo() {
const container = document.getElementById('ssl-info-container');
container.innerHTML = `
<div class="text-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Zertifikatsinformationen...</p>
</div>
`;
try {
const response = await fetch('/api/ssl/info');
const data = await response.json();
if (data.exists) {
const cert = data.certificate;
const isExpired = cert.is_expired;
const daysUntilExpiry = cert.days_until_expiry;
container.innerHTML = `
<div class="space-y-4">
<div class="flex items-center justify-between p-4 bg-white dark:bg-slate-700 rounded-lg">
<span class="font-medium text-slate-900 dark:text-white">Status</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${isExpired ? 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' : daysUntilExpiry < 30 ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' : 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'}">
${isExpired ? 'Abgelaufen' : daysUntilExpiry < 30 ? 'Läuft bald ab' : 'Gültig'}
</span>
</div>
<div class="p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="text-sm text-slate-600 dark:text-slate-400 mb-1">Gültig bis</div>
<div class="font-medium text-slate-900 dark:text-white">${cert.not_valid_after}</div>
</div>
<div class="p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="text-sm text-slate-600 dark:text-slate-400 mb-1">Verbleibende Tage</div>
<div class="font-medium text-slate-900 dark:text-white">${daysUntilExpiry} Tage</div>
</div>
<div class="p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="text-sm text-slate-600 dark:text-slate-400 mb-1">Schlüsselgröße</div>
<div class="font-medium text-slate-900 dark:text-white">${cert.key_size || 'Unbekannt'} Bit</div>
</div>
<div class="p-4 bg-white dark:bg-slate-700 rounded-lg">
<div class="text-sm text-slate-600 dark:text-slate-400 mb-1">Fingerprint (SHA256)</div>
<div class="font-mono text-xs text-slate-900 dark:text-white break-all">${cert.fingerprint}</div>
</div>
</div>
`;
} else {
container.innerHTML = `
<div class="text-center py-8">
<svg class="w-16 h-16 text-slate-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 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>
<p class="text-slate-600 dark:text-slate-400">Kein SSL-Zertifikat gefunden</p>
<p class="text-sm text-slate-500 dark:text-slate-500 mt-2">Generieren Sie ein neues Zertifikat</p>
</div>
`;
}
} catch (error) {
container.innerHTML = `
<div class="text-center py-8">
<svg class="w-16 h-16 text-red-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Zertifikatsinformationen</p>
<p class="text-sm text-slate-500 dark:text-slate-500 mt-2">${error.message}</p>
</div>
`;
}
}
async function generateSSLCertificate() {
const keySize = document.getElementById('ssl-key-size').value;
const validity = document.getElementById('ssl-validity').value;
try {
const response = await fetch('/api/ssl/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
key_size: parseInt(keySize),
validity_days: parseInt(validity)
})
});
const data = await response.json();
if (data.success) {
alert('SSL-Zertifikat erfolgreich generiert!');
loadSSLInfo();
} else {
alert('Fehler beim Generieren des SSL-Zertifikats: ' + data.error);
}
} catch (error) {
alert('Fehler beim Generieren des SSL-Zertifikats: ' + error.message);
}
}
async function installSSLCertificate() {
try {
const response = await fetch('/api/ssl/install', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
});
const data = await response.json();
if (data.success) {
alert('SSL-Zertifikat erfolgreich im System installiert!');
} else {
alert('Fehler bei der Installation: ' + data.error);
}
} catch (error) {
alert('Fehler bei der Installation: ' + error.message);
}
}
async function copyToRaspberry() {
const host = prompt('Raspberry Pi Hostname:', 'raspberrypi');
if (!host) return;
const user = prompt('Benutzername:', 'pi');
if (!user) return;
try {
const response = await fetch('/api/ssl/copy-raspberry', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: JSON.stringify({
host: host,
user: user
})
});
const data = await response.json();
if (data.success) {
alert('SSL-Zertifikat erfolgreich auf Raspberry Pi kopiert!');
} else {
alert('Fehler beim Kopieren: ' + data.error);
}
} catch (error) {
alert('Fehler beim Kopieren: ' + error.message);
}
}
async function validateSSLCertificate() {
try {
const response = await fetch('/api/ssl/validate');
const data = await response.json();
if (data.valid) {
alert('SSL-Zertifikat ist gültig!');
} else {
alert('SSL-Zertifikat ist ungültig oder läuft bald ab: ' + data.message);
}
} catch (error) {
alert('Fehler bei der Validierung: ' + error.message);
}
}
// SSL-Informationen beim Laden des SSL-Tabs laden
document.addEventListener('DOMContentLoaded', function() {
if (window.location.search.includes('tab=ssl')) {
loadSSLInfo();
}
});
</script>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Smooth animations for cards
const cards = document.querySelectorAll('.stat-card-modern, .content-card-modern');
cards.forEach((card, index) => {
card.style.animationDelay = `${index * 0.1}s`;
card.classList.add('animate-fade-in-up');
});
// Enhanced form validation
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
const inputs = form.querySelectorAll('input[required]');
let isValid = true;
inputs.forEach(input => {
if (!input.value.trim()) {
input.classList.add('border-red-500');
isValid = false;
} else {
input.classList.remove('border-red-500');
}
});
if (!isValid) {
e.preventDefault();
alert('Bitte füllen Sie alle erforderlichen Felder aus.');
}
});
});
});
</script>
{% endblock %}