809 lines
56 KiB
HTML
809 lines
56 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>
|
|
<script src="{{ url_for('static', filename='js/admin-system.js') }}" defer></script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Modernes Admin Panel mit Tailwind CSS -->
|
|
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
|
|
|
<!-- Hero Header -->
|
|
<div class="relative overflow-hidden bg-gradient-to-r from-slate-900 via-blue-900 to-indigo-900 text-white rounded-3xl mx-4 mt-4">
|
|
<div class="absolute inset-0 bg-black/20"></div>
|
|
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent"></div>
|
|
|
|
<!-- Animated Background Pattern -->
|
|
<div class="absolute inset-0 opacity-10 rounded-3xl overflow-hidden">
|
|
<div class="absolute inset-0" style="background-image: radial-gradient(circle at 25% 25%, white 2px, transparent 2px), radial-gradient(circle at 75% 75%, white 2px, transparent 2px); background-size: 50px 50px;"></div>
|
|
</div>
|
|
|
|
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
|
<div class="text-center">
|
|
<!-- Mercedes-Benz Logo -->
|
|
<div class="inline-flex items-center justify-center w-20 h-20 bg-white/10 backdrop-blur-sm rounded-full mb-6 border border-white/20">
|
|
<svg class="w-10 h-10 text-white" viewBox="0 0 80 80" fill="currentColor">
|
|
<path d="M58.6,4.5C53,1.6,46.7,0,40,0c-6.7,0-13,1.6-18.6,4.5v0C8.7,11.2,0,24.6,0,40c0,15.4,8.7,28.8,21.5,35.5
|
|
C27,78.3,33.3,80,40,80c6.7,0,12.9-1.7,18.5-4.6C71.3,68.8,80,55.4,80,40C80,24.6,71.3,11.2,58.6,4.5z M4,40
|
|
c0-13.1,7-24.5,17.5-30.9v0C26.6,6,32.5,4.2,39,4l-4.5,32.7L21.5,46.8v0L8.3,57.1C5.6,52,4,46.2,4,40z M58.6,70.8
|
|
C53.1,74.1,46.8,76,40,76c-6.8,0-13.2-1.9-18.6-5.2c-4.9-2.9-8.9-6.9-11.9-11.7l11.9-4.9v0L40,46.6l18.6,7.5v0l12,4.9
|
|
C67.6,63.9,63.4,67.9,58.6,70.8z M58.6,46.8L58.6,46.8l-12.9-10L41.1,4c6.3,0.2,12.3,2,17.4,5.1v0C69,15.4,76,26.9,76,40
|
|
c0,6.2-1.5,12-4.3,17.1L58.6,46.8z"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<h1 class="text-5xl md:text-6xl font-bold mb-4 tracking-tight">
|
|
<span class="bg-gradient-to-r from-white to-blue-200 bg-clip-text text-transparent">
|
|
Admin Panel
|
|
</span>
|
|
</h1>
|
|
<p class="text-xl md:text-2xl text-blue-100 max-w-3xl mx-auto leading-relaxed">
|
|
Verwalten Sie Ihr MYP-System mit modernster Technologie und Mercedes-Benz Qualität
|
|
</p>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="flex flex-wrap justify-center gap-4 mt-8">
|
|
<button class="inline-flex items-center px-6 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl text-white hover:bg-white/20 transition-all duration-300 hover:scale-105">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
System Status
|
|
</button>
|
|
<button class="inline-flex items-center px-6 py-3 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl text-white hover:bg-white/20 transition-all duration-300 hover:scale-105">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
Analytics
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-8 relative z-10">
|
|
|
|
<!-- Stats Dashboard -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mb-16">
|
|
<!-- Benutzer Stats -->
|
|
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
|
|
<div class="absolute inset-0 bg-gradient-to-br from-blue-500/10 to-indigo-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
|
<div class="relative">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl shadow-lg">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_users }}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">Registrierte Benutzer</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
|
<div class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full" style="width: 75%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drucker Stats -->
|
|
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
|
|
<div class="absolute inset-0 bg-gradient-to-br from-green-500/10 to-emerald-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
|
<div class="relative">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-green-500 to-green-600 rounded-xl shadow-lg">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 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-right">
|
|
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.total_printers }}</div>
|
|
<div class="text-sm text-green-500">{{ stats.online_printers }} online</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Verbundene Drucker</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
|
<div class="bg-gradient-to-r from-green-500 to-green-600 h-2 rounded-full" style="width: 90%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Aktive Jobs Stats -->
|
|
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
|
|
<div class="absolute inset-0 bg-gradient-to-br from-purple-500/10 to-violet-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
|
<div class="relative">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl shadow-lg">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.active_jobs }}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">{{ stats.queued_jobs }} in Warteschlange</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Laufende Druckaufträge</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
|
<div class="bg-gradient-to-r from-purple-500 to-purple-600 h-2 rounded-full" style="width: 60%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erfolgsrate Stats -->
|
|
<div class="group relative bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-8 shadow-xl hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
|
|
<div class="absolute inset-0 bg-gradient-to-br from-orange-500/10 to-red-500/10 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
|
<div class="relative">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="p-3 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl shadow-lg">
|
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="text-2xl font-bold text-slate-900 dark:text-white">{{ stats.success_rate }}%</div>
|
|
<div class="text-sm text-green-500">+5% Verbesserung</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm font-medium text-slate-600 dark:text-slate-300 mb-2">Erfolgreiche Druckaufträge</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
|
|
<div class="bg-gradient-to-r from-orange-500 to-orange-600 h-2 rounded-full" style="width: {{ stats.success_rate }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation Tabs -->
|
|
<div class="bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 p-4 mb-12 shadow-xl">
|
|
<nav class="flex space-x-2" aria-label="Tabs">
|
|
<a href="{{ url_for('admin_page', tab='users') }}"
|
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'users' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'users' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
</svg>
|
|
Benutzer
|
|
</a>
|
|
|
|
<a href="{{ url_for('admin_page', tab='printers') }}"
|
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'printers' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'printers' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="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='jobs') }}"
|
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'jobs' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'jobs' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
</svg>
|
|
Druckaufträge
|
|
</a>
|
|
|
|
<a href="{{ url_for('admin_page', tab='system') }}"
|
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'system' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'system' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
</svg>
|
|
System
|
|
</a>
|
|
|
|
<a href="{{ url_for('admin_page', tab='logs') }}"
|
|
class="group flex items-center px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 {{ 'bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg' if active_tab == 'logs' else 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/50 hover:text-slate-900 dark:hover:text-white' }}">
|
|
<svg class="w-5 h-5 mr-2 {{ 'text-white' if active_tab == 'logs' else 'text-slate-400 group-hover:text-slate-600 dark:group-hover:text-slate-300' }}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
</svg>
|
|
Logs
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-3xl border border-white/20 dark:border-slate-700/50 shadow-xl overflow-hidden">
|
|
|
|
{% if active_tab == 'users' %}
|
|
<!-- Benutzer Tab -->
|
|
<div class="p-12">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Benutzerverwaltung</h2>
|
|
<button class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 shadow-lg hover:shadow-xl">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
Neuer Benutzer
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Benutzer Tabelle -->
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
|
|
<thead class="bg-slate-50 dark:bg-slate-900/50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Benutzer</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">E-Mail</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Rolle</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Letzte Aktivität</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-slate-800/50 divide-y divide-slate-200 dark:divide-slate-700">
|
|
{% for user in users %}
|
|
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
<div class="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white font-semibold">
|
|
{{ user.username[0].upper() }}
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">{{ user.username }}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">{{ user.first_name }} {{ user.last_name }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ user.email }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full {{ 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200' if user.is_admin else 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' }}">
|
|
{{ 'Administrator' if user.is_admin else 'Benutzer' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full {{ 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' if user.is_active else 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' }}">
|
|
<span class="w-2 h-2 mr-1 rounded-full {{ 'bg-green-400' if user.is_active else 'bg-red-400' }}"></span>
|
|
{{ 'Aktiv' if user.is_active else 'Inaktiv' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
|
|
{{ user.last_login.strftime('%d.%m.%Y %H:%M') if user.last_login else 'Nie' }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<div class="flex space-x-2">
|
|
<button class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
</svg>
|
|
</button>
|
|
<button class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{% elif active_tab == 'printers' %}
|
|
<!-- Drucker Tab -->
|
|
<div class="p-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Druckerverwaltung</h2>
|
|
<button class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl hover:from-green-600 hover:to-green-700 transition-all duration-300 shadow-lg hover:shadow-xl">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
</svg>
|
|
Drucker hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Drucker Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{% for printer in printers %}
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">{{ printer.name }}</h3>
|
|
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
|
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
|
|
{{ printer.status.title() }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="space-y-3">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Modell:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ printer.model }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Standort:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ printer.location }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Aktuelle Aufgabe:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">
|
|
{% if printer.current_job %}
|
|
{{ printer.current_job.filename[:20] }}...
|
|
{% else %}
|
|
Keine
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
|
|
{% if printer.current_job %}
|
|
<div class="mt-4">
|
|
<div class="flex justify-between text-sm mb-1">
|
|
<span class="text-slate-600 dark:text-slate-400">Fortschritt:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ printer.current_job.progress }}%</span>
|
|
</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2">
|
|
<div class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full transition-all duration-500" style="width: {{ printer.current_job.progress }}%"></div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="flex space-x-2 mt-6">
|
|
<button class="flex-1 px-3 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium">
|
|
Verwalten
|
|
</button>
|
|
<button class="px-3 py-2 bg-slate-200 dark:bg-slate-600 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-300 dark:hover:bg-slate-500 transition-colors text-sm">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{% elif active_tab == 'jobs' %}
|
|
<!-- Jobs Tab -->
|
|
<div class="p-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Druckaufträge</h2>
|
|
<div class="flex space-x-3">
|
|
<select class="px-4 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option>Alle Status</option>
|
|
<option>Warteschlange</option>
|
|
<option>Druckt</option>
|
|
<option>Abgeschlossen</option>
|
|
<option>Fehler</option>
|
|
</select>
|
|
<button class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-slate-500 to-slate-600 text-white rounded-xl hover:from-slate-600 hover:to-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
|
</svg>
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jobs Tabelle -->
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
|
|
<thead class="bg-slate-50 dark:bg-slate-900/50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Datei</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Benutzer</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Drucker</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Fortschritt</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Erstellt</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-slate-800/50 divide-y divide-slate-200 dark:divide-slate-700">
|
|
{% for job in jobs %}
|
|
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors duration-200">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0 h-10 w-10">
|
|
<div class="h-10 w-10 rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div class="ml-4">
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">{{ job.filename }}</div>
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">{{ job.file_size_mb }} MB</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ job.user.username }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-white">{{ job.printer.name if job.printer else 'Nicht zugewiesen' }}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
{% set status_colors = {
|
|
'queued': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
|
'printing': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
|
'completed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
|
'failed': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
|
'cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'
|
|
} %}
|
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full {{ status_colors.get(job.status, 'bg-gray-100 text-gray-800') }}">
|
|
{{ job.status.title() }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div class="w-16 bg-slate-200 dark:bg-slate-600 rounded-full h-2 mr-2">
|
|
<div class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full" style="width: {{ job.progress }}%"></div>
|
|
</div>
|
|
<span class="text-sm text-slate-900 dark:text-white">{{ job.progress }}%</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
|
|
{{ job.created_at.strftime('%d.%m.%Y %H:%M') }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<div class="flex space-x-2">
|
|
{% if job.status == 'queued' %}
|
|
<button class="text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h8m2-10h.01M5 20h14a2 2 0 002-2V7a2 2 0 00-2-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2zM9 9h6v6H9V9z"/>
|
|
</svg>
|
|
</button>
|
|
{% endif %}
|
|
{% if job.status in ['queued', 'printing'] %}
|
|
<button class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
{% endif %}
|
|
<button class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{% elif active_tab == 'system' %}
|
|
<!-- System Tab -->
|
|
<div class="p-8">
|
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-6">Systemverwaltung</h2>
|
|
|
|
<!-- System Status Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Server Status</h3>
|
|
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 server-status">
|
|
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
|
|
Online
|
|
</span>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Uptime:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.uptime if system_info.uptime else 'Unbekannt' }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">CPU:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.cpu_usage if system_info.cpu_usage else 0 }}%</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">RAM:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.memory_usage if system_info.memory_usage else 0 }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Datenbank</h3>
|
|
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 database-status">
|
|
<span class="w-2 h-2 mr-1 rounded-full bg-green-400 animate-pulse"></span>
|
|
Verbunden
|
|
</span>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Größe:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_size if system_info.db_size else 'Unbekannt' }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Verbindungen:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.db_connections if system_info.db_connections else 'Unbekannt' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">Scheduler</h3>
|
|
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 scheduler-status">
|
|
<span class="w-2 h-2 mr-1 rounded-full bg-blue-400 animate-pulse"></span>
|
|
{{ 'Läuft' if system_info.scheduler_running else 'Gestoppt' }}
|
|
</span>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Jobs:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.scheduler_jobs if system_info.scheduler_jobs else 0 }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">Nächster Job:</span>
|
|
<span class="text-slate-900 dark:text-white font-medium">{{ system_info.next_job if system_info.next_job else 'Keine geplanten Jobs' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Actions -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Wartung</h3>
|
|
<div class="space-y-3">
|
|
<button onclick="clearCache()" class="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors text-sm font-medium">
|
|
Cache leeren
|
|
</button>
|
|
<button onclick="optimizeDatabase()" class="w-full px-4 py-2 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600 transition-colors text-sm font-medium">
|
|
Datenbank optimieren
|
|
</button>
|
|
<button onclick="createBackup()" class="w-full px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm font-medium">
|
|
Backup erstellen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-xl border border-slate-200 dark:border-slate-600 p-6 shadow-lg">
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-4">Konfiguration</h3>
|
|
<div class="space-y-3">
|
|
<button onclick="editSettings()" class="w-full px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors text-sm font-medium">
|
|
Einstellungen bearbeiten
|
|
</button>
|
|
<button onclick="updatePrinters()" class="w-full px-4 py-2 bg-indigo-500 text-white rounded-lg hover:bg-indigo-600 transition-colors text-sm font-medium">
|
|
Drucker aktualisieren
|
|
</button>
|
|
<button onclick="restartSystem()" class="w-full px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors text-sm font-medium">
|
|
System neustarten
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% elif active_tab == 'logs' %}
|
|
<!-- Logs Tab -->
|
|
<div class="p-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">System Logs</h2>
|
|
<div class="flex space-x-3">
|
|
<select class="px-4 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option>Alle Logs</option>
|
|
<option>Fehler</option>
|
|
<option>Warnungen</option>
|
|
<option>Info</option>
|
|
<option>Debug</option>
|
|
</select>
|
|
<button class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-slate-500 to-slate-600 text-white rounded-xl hover:from-slate-600 hover:to-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
|
</svg>
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Log Entries -->
|
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
|
{% for log in logs %}
|
|
<div class="bg-white/60 dark:bg-slate-700/60 backdrop-blur-sm rounded-lg border border-slate-200 dark:border-slate-600 p-4 shadow-sm">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex items-start space-x-3">
|
|
{% set log_colors = {
|
|
'ERROR': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
|
'WARNING': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
|
'INFO': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
|
'DEBUG': 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200'
|
|
} %}
|
|
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full {{ log_colors.get(log.level, 'bg-gray-100 text-gray-800') }}">
|
|
{{ log.level }}
|
|
</span>
|
|
<div>
|
|
<p class="text-sm text-slate-900 dark:text-white">{{ log.message }}</p>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">{{ log.module }} - {{ log.timestamp if log.timestamp is string else log.timestamp.strftime('%d.%m.%Y %H:%M:%S') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{% else %}
|
|
<!-- Default Tab -->
|
|
<div class="p-8 text-center">
|
|
<div class="max-w-md mx-auto">
|
|
<svg class="w-16 h-16 mx-auto text-slate-400 dark:text-slate-500 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-2">Willkommen im Admin Panel</h3>
|
|
<p class="text-slate-500 dark:text-slate-400">Wählen Sie einen Tab aus, um zu beginnen.</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div id="loading-overlay" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
|
|
<div class="flex items-center justify-center h-full">
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-8 shadow-2xl">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
<span class="text-slate-900 dark:text-white font-medium">Wird geladen...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// CSRF Token für AJAX-Anfragen
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
|
|
// Hilfsfunktion für API-Aufrufe
|
|
async function makeApiCall(url, method = 'GET', data = null) {
|
|
const options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': csrfToken
|
|
}
|
|
};
|
|
|
|
if (data) {
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, options);
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
showNotification(result.message || 'Aktion erfolgreich ausgeführt', 'success');
|
|
return result;
|
|
} else {
|
|
showNotification(result.error || 'Ein Fehler ist aufgetreten', 'error');
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
showNotification('Netzwerkfehler: ' + error.message, 'error');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Notification anzeigen
|
|
function showNotification(message, type = 'info') {
|
|
// Erstelle Notification-Element falls nicht vorhanden
|
|
let notification = document.getElementById('admin-notification');
|
|
if (!notification) {
|
|
notification = document.createElement('div');
|
|
notification.id = 'admin-notification';
|
|
notification.className = 'fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 transform translate-x-full';
|
|
document.body.appendChild(notification);
|
|
}
|
|
|
|
// Setze Farbe basierend auf Typ
|
|
const colors = {
|
|
success: 'bg-green-500 text-white',
|
|
error: 'bg-red-500 text-white',
|
|
warning: 'bg-yellow-500 text-white',
|
|
info: 'bg-blue-500 text-white'
|
|
};
|
|
|
|
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm transition-all duration-300 ${colors[type] || colors.info}`;
|
|
notification.textContent = message;
|
|
|
|
// Zeige Notification
|
|
notification.style.transform = 'translateX(0)';
|
|
|
|
// Verstecke nach 5 Sekunden
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
}, 5000);
|
|
}
|
|
|
|
// Cache leeren
|
|
async function clearCache() {
|
|
if (confirm('Möchten Sie wirklich den Cache leeren?')) {
|
|
const result = await makeApiCall('/api/admin/cache/clear', 'POST');
|
|
if (result) {
|
|
setTimeout(() => location.reload(), 2000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Datenbank optimieren
|
|
async function optimizeDatabase() {
|
|
if (confirm('Möchten Sie wirklich die Datenbank optimieren? Dies kann einige Minuten dauern.')) {
|
|
const result = await makeApiCall('/api/admin/database/optimize', 'POST');
|
|
if (result) {
|
|
setTimeout(() => location.reload(), 2000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Backup erstellen
|
|
async function createBackup() {
|
|
if (confirm('Möchten Sie wirklich ein Backup erstellen?')) {
|
|
const result = await makeApiCall('/api/admin/backup/create', 'POST');
|
|
}
|
|
}
|
|
|
|
// Drucker aktualisieren
|
|
async function updatePrinters() {
|
|
if (confirm('Möchten Sie alle Drucker-Verbindungen aktualisieren?')) {
|
|
const result = await makeApiCall('/api/admin/printers/update', 'POST');
|
|
if (result) {
|
|
setTimeout(() => location.reload(), 2000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// System neustarten
|
|
async function restartSystem() {
|
|
if (confirm('WARNUNG: Möchten Sie wirklich das System neustarten? Alle aktiven Verbindungen werden getrennt.')) {
|
|
const result = await makeApiCall('/api/admin/system/restart', 'POST');
|
|
if (result) {
|
|
showNotification('System wird neugestartet...', 'warning');
|
|
setTimeout(() => {
|
|
window.location.href = '/';
|
|
}, 3000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Einstellungen bearbeiten
|
|
function editSettings() {
|
|
window.location.href = '/settings';
|
|
}
|
|
|
|
// Systemstatus automatisch aktualisieren
|
|
async function updateSystemStatus() {
|
|
if (window.location.search.includes('tab=system')) {
|
|
const result = await makeApiCall('/api/admin/system/status');
|
|
if (result) {
|
|
// Aktualisiere die Anzeige
|
|
const elements = {
|
|
'cpu_usage': result.cpu_usage + '%',
|
|
'memory_usage': result.memory_usage + '%',
|
|
'disk_usage': result.disk_usage + '%',
|
|
'uptime': result.uptime,
|
|
'db_size': result.db_size,
|
|
'scheduler_jobs': result.scheduler_jobs,
|
|
'next_job': result.next_job
|
|
};
|
|
|
|
Object.keys(elements).forEach(key => {
|
|
const element = document.querySelector(`[data-status="${key}"]`);
|
|
if (element) {
|
|
element.textContent = elements[key];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Auto-Update alle 30 Sekunden
|
|
setInterval(updateSystemStatus, 30000);
|
|
|
|
// Initial load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updateSystemStatus();
|
|
});
|
|
</script>
|
|
{% endblock %} |