768 lines
43 KiB
HTML
768 lines
43 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Admin Dashboard - Mercedes-Benz TBA Marienfelde{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ super() }}
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<style>
|
|
/* Modern Admin Dashboard Styles */
|
|
.admin-gradient {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.glass-effect {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.dark .glass-effect {
|
|
background: rgba(15, 23, 42, 0.8);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
animation: fadeIn 0.3s ease-in-out;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.modal {
|
|
display: none;
|
|
animation: modalFadeIn 0.3s ease-out;
|
|
}
|
|
|
|
.modal.active {
|
|
display: flex;
|
|
}
|
|
|
|
@keyframes modalFadeIn {
|
|
from { opacity: 0; transform: scale(0.9); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
|
|
.card-hover {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.card-hover:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.stat-card {
|
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
}
|
|
|
|
.dark .stat-card {
|
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
|
border: 1px solid rgba(51, 65, 85, 0.3);
|
|
}
|
|
|
|
.status-online { @apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200; }
|
|
.status-offline { @apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200; }
|
|
.status-busy { @apply bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200; }
|
|
</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-indigo-900">
|
|
|
|
<!-- Hero Header -->
|
|
<div class="admin-gradient relative overflow-hidden rounded-3xl mx-4 mt-4 shadow-2xl">
|
|
<div class="absolute inset-0 bg-black/20"></div>
|
|
<div class="absolute inset-0">
|
|
<div class="absolute top-0 left-0 w-full h-full opacity-30">
|
|
<svg class="w-full h-full" viewBox="0 0 100 20" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<pattern id="admin-pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
|
<circle cx="10" cy="10" r="2" fill="white" opacity="0.1"/>
|
|
</pattern>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill="url(#admin-pattern)"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relative max-w-7xl mx-auto px-6 py-12">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="w-16 h-16 bg-white/20 backdrop-blur-sm rounded-2xl flex items-center justify-center border border-white/30">
|
|
<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="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>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-4xl font-bold text-white mb-2">Admin Dashboard</h1>
|
|
<p class="text-blue-100 text-lg">Mercedes-Benz TBA Marienfelde - 3D-Drucker Management</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right text-white">
|
|
<div class="text-sm opacity-80">Angemeldet als</div>
|
|
<div class="text-lg font-semibold">{{ current_user.name }}</div>
|
|
<div class="text-sm opacity-60">Administrator</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-6 py-8">
|
|
|
|
<!-- Live Statistics Dashboard -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<!-- Benutzer -->
|
|
<div class="stat-card rounded-2xl p-6 card-hover">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-slate-600 dark:text-slate-400">Benutzer</p>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white">{{ stats.total_users or 0 }}</p>
|
|
<p class="text-sm text-green-600 dark:text-green-400">+2 diese Woche</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/50 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" 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>
|
|
</div>
|
|
|
|
<!-- Drucker -->
|
|
<div class="stat-card rounded-2xl p-6 card-hover">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-slate-600 dark:text-slate-400">Drucker</p>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white">{{ stats.total_printers or 0 }}</p>
|
|
<p class="text-sm text-green-600 dark:text-green-400">{{ stats.online_printers or 0 }} online</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/50 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-green-600 dark:text-green-400" 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 2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jobs -->
|
|
<div class="stat-card rounded-2xl p-6 card-hover">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-slate-600 dark:text-slate-400">Aktive Jobs</p>
|
|
<p class="text-3xl font-bold text-slate-900 dark:text-white">{{ stats.active_jobs or 0 }}</p>
|
|
<p class="text-sm text-blue-600 dark:text-blue-400">{{ stats.total_jobs or 0 }} gesamt</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-yellow-100 dark:bg-yellow-900/50 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" 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>
|
|
</div>
|
|
|
|
<!-- System Health -->
|
|
<div class="stat-card rounded-2xl p-6 card-hover">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-slate-600 dark:text-slate-400">System</p>
|
|
<p class="text-3xl font-bold text-green-600 dark:text-green-400">Online</p>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400">Alle Services</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/50 rounded-xl flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-green-600 dark:text-green-400" 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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Navigation -->
|
|
<div class="glass-effect rounded-2xl p-2 mb-8 shadow-lg">
|
|
<nav class="flex flex-wrap gap-2" role="tablist">
|
|
<button class="tab-button active px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="overview" role="tab">
|
|
<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 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
|
|
</svg>
|
|
<span>Übersicht</span>
|
|
</button>
|
|
<button class="tab-button px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="users" role="tab">
|
|
<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>
|
|
<span>Benutzer</span>
|
|
</button>
|
|
<button class="tab-button px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="printers" role="tab">
|
|
<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 2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"/>
|
|
</svg>
|
|
<span>Drucker</span>
|
|
</button>
|
|
<button class="tab-button px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="system" role="tab">
|
|
<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="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>
|
|
<span>System</span>
|
|
</button>
|
|
<button class="tab-button px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="requests" role="tab">
|
|
<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 2v10a2 2 0 002 2h8a2 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 2m-6 9l2 2 4-4"/>
|
|
</svg>
|
|
<span>Anträge</span>
|
|
</button>
|
|
<button class="tab-button px-6 py-3 text-sm font-medium rounded-xl transition-all duration-300 flex items-center space-x-2"
|
|
data-tab="logs" role="tab">
|
|
<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 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>
|
|
<span>Logs</span>
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Tab Content Panels -->
|
|
<div class="tab-contents">
|
|
|
|
<!-- Overview Tab -->
|
|
<div id="overview-content" class="tab-content active">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-6 flex items-center">
|
|
<svg class="w-6 h-6 mr-2 text-blue-600 dark:text-blue-400" 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>
|
|
Schnellaktionen
|
|
</h3>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<button onclick="openModal('addUserModal')" class="p-4 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-xl border border-blue-200 dark:border-blue-800 transition-all duration-300 card-hover">
|
|
<svg class="w-8 h-8 mx-auto text-blue-600 dark:text-blue-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/>
|
|
</svg>
|
|
<span class="text-sm font-medium text-blue-900 dark:text-blue-300">Benutzer hinzufügen</span>
|
|
</button>
|
|
<button onclick="openModal('addPrinterModal')" class="p-4 bg-green-50 dark:bg-green-900/20 hover:bg-green-100 dark:hover:bg-green-900/30 rounded-xl border border-green-200 dark:border-green-800 transition-all duration-300 card-hover">
|
|
<svg class="w-8 h-8 mx-auto text-green-600 dark:text-green-400 mb-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>
|
|
<span class="text-sm font-medium text-green-900 dark:text-green-300">Drucker hinzufügen</span>
|
|
</button>
|
|
<button onclick="openModal('systemMaintenanceModal')" class="p-4 bg-purple-50 dark:bg-purple-900/20 hover:bg-purple-100 dark:hover:bg-purple-900/30 rounded-xl border border-purple-200 dark:border-purple-800 transition-all duration-300 card-hover">
|
|
<svg class="w-8 h-8 mx-auto text-purple-600 dark:text-purple-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.50 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>
|
|
<span class="text-sm font-medium text-purple-900 dark:text-purple-300">System-Wartung</span>
|
|
</button>
|
|
<a href="{{ url_for('energy.energy_dashboard') }}" class="p-4 bg-yellow-50 dark:bg-yellow-900/20 hover:bg-yellow-100 dark:hover:bg-yellow-900/30 rounded-xl border border-yellow-200 dark:border-yellow-800 transition-all duration-300 card-hover">
|
|
<svg class="w-8 h-8 mx-auto text-yellow-600 dark:text-yellow-400 mb-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>
|
|
<span class="text-sm font-medium text-yellow-900 dark:text-yellow-300">Energiemonitoring</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-6 flex items-center">
|
|
<svg class="w-6 h-6 mr-2 text-green-600 dark:text-green-400" 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>
|
|
Letzte Aktivitäten
|
|
</h3>
|
|
<div class="space-y-4" id="recentActivity">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="flex items-center space-x-3 p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
|
|
<div class="w-8 h-8 bg-blue-100 dark:bg-blue-900/50 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-blue-600 dark:text-blue-400" 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 class="flex-1">
|
|
<p class="text-sm font-medium text-slate-900 dark:text-white">Neuer Benutzer registriert</p>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">vor 5 Minuten</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Users Tab -->
|
|
<div id="users-content" class="tab-content">
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Benutzerverwaltung</h3>
|
|
<button onclick="openModal('addUserModal')" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
<svg class="w-4 h-4 inline 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>
|
|
Benutzer hinzufügen
|
|
</button>
|
|
</div>
|
|
<div id="usersTable">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="text-center py-8">
|
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Benutzerdaten...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Printers Tab -->
|
|
<div id="printers-content" class="tab-content">
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Druckerverwaltung</h3>
|
|
<button onclick="openModal('addPrinterModal')" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
<svg class="w-4 h-4 inline 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>
|
|
<div id="printersTable">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="text-center py-8">
|
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-green-600"></div>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Druckerdaten...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Tab -->
|
|
<div id="system-content" class="tab-content">
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-6">System-Überwachung</h3>
|
|
<div id="systemHealth">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="text-center py-8">
|
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600"></div>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Systemdaten...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requests Tab -->
|
|
<div id="requests-content" class="tab-content">
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Antrags-Management</h3>
|
|
<div class="flex space-x-2">
|
|
<select id="requestStatus" class="px-3 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm">
|
|
<option value="all">Alle Anträge</option>
|
|
<option value="pending">Ausstehend</option>
|
|
<option value="approved">Genehmigt</option>
|
|
<option value="rejected">Abgelehnt</option>
|
|
</select>
|
|
<button onclick="refreshRequests()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="requestsTable">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="text-center py-8">
|
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Antrags-Daten...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Tab -->
|
|
<div id="logs-content" class="tab-content">
|
|
<div class="glass-effect rounded-2xl p-6 shadow-lg">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h3 class="text-xl font-bold text-slate-900 dark:text-white">System-Logs</h3>
|
|
<div class="flex space-x-2">
|
|
<select id="logLevel" class="px-3 py-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 rounded-lg text-sm">
|
|
<option value="all">Alle Level</option>
|
|
<option value="error">Nur Fehler</option>
|
|
<option value="warning">Warnungen</option>
|
|
<option value="info">Info</option>
|
|
</select>
|
|
<button onclick="refreshLogs()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="logsTable">
|
|
<!-- Wird dynamisch geladen -->
|
|
<div class="text-center py-8">
|
|
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400">Lade Log-Daten...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modals -->
|
|
<!-- Add User Modal -->
|
|
<div id="addUserModal" class="modal fixed inset-0 bg-black bg-opacity-50 z-50 items-center justify-center">
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 w-full max-w-md mx-4 shadow-2xl">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-bold text-slate-900 dark:text-white">Neuen Benutzer hinzufügen</h3>
|
|
<button onclick="closeModal('addUserModal')" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
|
|
<svg class="w-6 h-6" 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>
|
|
</div>
|
|
<button onclick="window.location.href='{{ url_for('admin.add_user_page') }}'" class="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
Zum Benutzer-Formular
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Printer Modal -->
|
|
<div id="addPrinterModal" class="modal fixed inset-0 bg-black bg-opacity-50 z-50 items-center justify-center">
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 w-full max-w-md mx-4 shadow-2xl">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-bold text-slate-900 dark:text-white">Neuen Drucker hinzufügen</h3>
|
|
<button onclick="closeModal('addPrinterModal')" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
|
|
<svg class="w-6 h-6" 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>
|
|
</div>
|
|
<button onclick="window.location.href='{{ url_for('admin.add_printer_page') }}'" class="w-full px-4 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
Zum Drucker-Formular
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Maintenance Modal -->
|
|
<div id="systemMaintenanceModal" class="modal fixed inset-0 bg-black bg-opacity-50 z-50 items-center justify-center">
|
|
<div class="bg-white dark:bg-slate-800 rounded-2xl p-6 w-full max-w-md mx-4 shadow-2xl">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-bold text-slate-900 dark:text-white">System-Wartung</h3>
|
|
<button onclick="closeModal('systemMaintenanceModal')" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
|
|
<svg class="w-6 h-6" 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>
|
|
</div>
|
|
<div class="space-y-3">
|
|
<form method="POST" action="{{ url_for('admin.maintenance') }}" class="w-full">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<input type="hidden" name="action" value="clear_cache"/>
|
|
<button type="submit" class="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors duration-300 mb-2">
|
|
🗑️ Cache leeren
|
|
</button>
|
|
</form>
|
|
<form method="POST" action="{{ url_for('admin.maintenance') }}" class="w-full">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<input type="hidden" name="action" value="optimize_db"/>
|
|
<button type="submit" class="w-full px-4 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors duration-300 mb-2">
|
|
🔧 Datenbank optimieren
|
|
</button>
|
|
</form>
|
|
<form method="POST" action="{{ url_for('admin.maintenance') }}" class="w-full">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
|
<input type="hidden" name="action" value="create_backup"/>
|
|
<button type="submit" class="w-full px-4 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors duration-300">
|
|
💾 Backup erstellen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Tab Functionality
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Tab switching
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const targetTab = button.getAttribute('data-tab');
|
|
|
|
// Remove active class from all tabs and contents
|
|
tabButtons.forEach(btn => {
|
|
btn.classList.remove('active', 'bg-blue-600', 'text-white');
|
|
btn.classList.add('text-slate-600', 'dark:text-slate-300', 'hover:bg-white/50');
|
|
});
|
|
tabContents.forEach(content => content.classList.remove('active'));
|
|
|
|
// Add active class to clicked tab and corresponding content
|
|
button.classList.add('active', 'bg-blue-600', 'text-white');
|
|
button.classList.remove('text-slate-600', 'dark:text-slate-300', 'hover:bg-white/50');
|
|
|
|
const targetContent = document.getElementById(targetTab + '-content');
|
|
if (targetContent) {
|
|
targetContent.classList.add('active');
|
|
|
|
// Load content dynamically based on tab
|
|
loadTabContent(targetTab);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Load initial overview content
|
|
loadTabContent('overview');
|
|
});
|
|
|
|
// Load tab content dynamically
|
|
async function loadTabContent(tab) {
|
|
const contentDiv = document.getElementById(tab + '-content');
|
|
if (!contentDiv) return;
|
|
|
|
try {
|
|
switch(tab) {
|
|
case 'users':
|
|
await loadUsers();
|
|
break;
|
|
case 'printers':
|
|
await loadPrinters();
|
|
break;
|
|
case 'system':
|
|
await loadSystemHealth();
|
|
break;
|
|
case 'requests':
|
|
await loadRequests();
|
|
break;
|
|
case 'logs':
|
|
await loadLogs();
|
|
break;
|
|
case 'overview':
|
|
await loadRecentActivity();
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Tab-Inhalte:', error);
|
|
}
|
|
}
|
|
|
|
// Load functions
|
|
async function loadUsers() {
|
|
const tableDiv = document.getElementById('usersTable');
|
|
if (!tableDiv) return;
|
|
|
|
try {
|
|
// In einer echten Implementierung würde hier eine API aufgerufen
|
|
tableDiv.innerHTML = `
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-slate-50 dark:bg-slate-700">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Name</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">E-Mail</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Rolle</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Status</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
|
<tr class="hover:bg-slate-50 dark:hover:bg-slate-700/50">
|
|
<td class="px-4 py-3 text-slate-900 dark:text-white">Admin User</td>
|
|
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">admin@mercedes-benz.com</td>
|
|
<td class="px-4 py-3"><span class="px-2 py-1 bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 rounded-full text-xs">Admin</span></td>
|
|
<td class="px-4 py-3"><span class="px-2 py-1 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 rounded-full text-xs">Aktiv</span></td>
|
|
<td class="px-4 py-3">
|
|
<button class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 mr-2">Bearbeiten</button>
|
|
<button class="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">Löschen</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
tableDiv.innerHTML = '<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Benutzerdaten</p>';
|
|
}
|
|
}
|
|
|
|
async function loadPrinters() {
|
|
const tableDiv = document.getElementById('printersTable');
|
|
if (!tableDiv) return;
|
|
|
|
try {
|
|
tableDiv.innerHTML = `
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<div class="bg-white dark:bg-slate-700 rounded-lg p-4 border border-slate-200 dark:border-slate-600">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h4 class="font-medium text-slate-900 dark:text-white">Prusa i3 MK3S+</h4>
|
|
<span class="status-online px-2 py-1 rounded-full text-xs">Online</span>
|
|
</div>
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 mb-3">IP: 192.168.1.100</p>
|
|
<div class="flex space-x-2">
|
|
<button class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded text-xs">Verwalten</button>
|
|
<button class="px-3 py-1 bg-green-600 hover:bg-green-700 text-white rounded text-xs">Einschalten</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
tableDiv.innerHTML = '<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Druckerdaten</p>';
|
|
}
|
|
}
|
|
|
|
async function loadSystemHealth() {
|
|
const healthDiv = document.getElementById('systemHealth');
|
|
if (!healthDiv) return;
|
|
|
|
try {
|
|
healthDiv.innerHTML = `
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="bg-white dark:bg-slate-700 rounded-lg p-4">
|
|
<h4 class="font-medium text-slate-900 dark:text-white mb-3">CPU & Speicher</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">CPU Nutzung</span>
|
|
<span class="text-slate-900 dark:text-white">15%</span>
|
|
</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2">
|
|
<div class="bg-green-600 h-2 rounded-full" style="width: 15%"></div>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-slate-600 dark:text-slate-400">RAM Nutzung</span>
|
|
<span class="text-slate-900 dark:text-white">45%</span>
|
|
</div>
|
|
<div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2">
|
|
<div class="bg-blue-600 h-2 rounded-full" style="width: 45%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-slate-700 rounded-lg p-4">
|
|
<h4 class="font-medium text-slate-900 dark:text-white mb-3">Services</h4>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-600 dark:text-slate-400">Flask App</span>
|
|
<span class="status-online px-2 py-1 rounded-full text-xs">Online</span>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-600 dark:text-slate-400">Database</span>
|
|
<span class="status-online px-2 py-1 rounded-full text-xs">Online</span>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-sm text-slate-600 dark:text-slate-400">Tapo Controller</span>
|
|
<span class="status-online px-2 py-1 rounded-full text-xs">Online</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
healthDiv.innerHTML = '<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Systemdaten</p>';
|
|
}
|
|
}
|
|
|
|
async function loadLogs() {
|
|
const logsDiv = document.getElementById('logsTable');
|
|
if (!logsDiv) return;
|
|
|
|
try {
|
|
logsDiv.innerHTML = `
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-slate-50 dark:bg-slate-700">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Zeit</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Level</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Komponente</th>
|
|
<th class="px-4 py-3 text-left font-medium text-slate-900 dark:text-white">Nachricht</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
|
<tr>
|
|
<td class="px-4 py-3 text-slate-600 dark:text-slate-400">${new Date().toLocaleString()}</td>
|
|
<td class="px-4 py-3"><span class="px-2 py-1 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 rounded-full text-xs">INFO</span></td>
|
|
<td class="px-4 py-3 text-slate-900 dark:text-white">Admin</td>
|
|
<td class="px-4 py-3 text-slate-900 dark:text-white">Admin-Dashboard aufgerufen</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
} catch (error) {
|
|
logsDiv.innerHTML = '<p class="text-red-600 dark:text-red-400">Fehler beim Laden der Log-Daten</p>';
|
|
}
|
|
}
|
|
|
|
async function loadRecentActivity() {
|
|
const activityDiv = document.getElementById('recentActivity');
|
|
if (!activityDiv) return;
|
|
|
|
// Beispiel für dynamischen Inhalt
|
|
const activities = [
|
|
{ icon: 'user', text: 'Neuer Benutzer registriert', time: 'vor 5 Minuten', color: 'blue' },
|
|
{ icon: 'printer', text: 'Drucker "Prusa i3" gestartet', time: 'vor 12 Minuten', color: 'green' },
|
|
{ icon: 'warning', text: 'System-Wartung durchgeführt', time: 'vor 1 Stunde', color: 'yellow' }
|
|
];
|
|
|
|
activityDiv.innerHTML = activities.map(activity => `
|
|
<div class="flex items-center space-x-3 p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg">
|
|
<div class="w-8 h-8 bg-${activity.color}-100 dark:bg-${activity.color}-900/50 rounded-full flex items-center justify-center">
|
|
<svg class="w-4 h-4 text-${activity.color}-600 dark:text-${activity.color}-400" 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 class="flex-1">
|
|
<p class="text-sm font-medium text-slate-900 dark:text-white">${activity.text}</p>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">${activity.time}</p>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// Modal functions
|
|
function openModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
const modal = document.getElementById(modalId);
|
|
if (modal) {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.classList.contains('modal')) {
|
|
e.target.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
});
|
|
|
|
// Refresh functions
|
|
function refreshLogs() {
|
|
loadLogs();
|
|
}
|
|
|
|
// Auto-refresh stats every 30 seconds
|
|
setInterval(() => {
|
|
loadTabContent('overview');
|
|
}, 30000);
|
|
</script>
|
|
{% endblock %} |