AstraOS Ver. 1.10:

- Benachrichtigung aktualisiert
- Logs hinzugefügt
This commit is contained in:
MrWhiff 2025-12-11 16:56:14 +01:00
parent 9b5d3cdc58
commit eec2a01618
10 changed files with 560 additions and 222 deletions

View file

@ -2,6 +2,10 @@
{% block title %}AstraOS — Altersüberprüfung{% endblock %}
{% block head %}
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{% endblock %}
{% block content %}
<header class="flex justify-between items-center mb-6">
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Altersüberprüfung</h2>
@ -10,11 +14,35 @@
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div class="mb-4 rounded-md bg-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/20 p-4 text-sm text-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-300 border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/30">
{{ message }}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
@ -31,7 +59,7 @@
</div>
<div>
<label for="steam_id" class="block text-sm font-medium text-gray-300">SteamID</label>
<input type="text" name="steam_id" id="steam_id" placeholder="z.B. 76561199045670704@steam" required
<input type="text" name="steam_id" id="steam_id" placeholder="z.B. 76561199045670704" required
class="mt-1 w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 {% if maintenance_active %}focus:ring-yellow-500{% else %}focus:ring-purple-500{% endif %}">
</div>
<div>
@ -116,4 +144,3 @@ function copyToClipboard(elementId) {
}
</script>
{% endblock %}

View file

@ -37,11 +37,35 @@
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div class="rounded-md bg-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/20 p-4 text-sm text-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-300 border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/30">
{{ message }}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}

View file

@ -18,6 +18,40 @@
{% block content %}
<div x-data="{ showUploadModal: false, showDeleteModal: false, botToDelete: null }">
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- HEADER -->
<header class="flex justify-between items-center mb-10">
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Discord Bot Management</h2>

View file

@ -0,0 +1,64 @@
{% extends "sidebar.html" %}
{% block title %}AstraOS — Logs{% endblock %}
{% block content %}
<header class="flex justify-between items-center mb-10">
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">System-Logs</h2>
</header>
<div class="bg-black/40 rounded-xl backdrop-blur-lg overflow-hidden
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
<div class="overflow-x-auto">
<table class="min-w-full divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/20{% endif %}">
<thead class="bg-black/20">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Benutzer</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Aktion</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Details</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Zeitstempel (UTC)</th>
</tr>
</thead>
<tbody class="divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/30{% endif %}">
{% for log in logs %}
<tr class="transition {% if maintenance_active %}hover:bg-yellow-500/5{% else %}hover:bg-purple-500/5{% endif %}">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">{{ log.username }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ log.action }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ log.details if log.details else '-' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ log.timestamp.strftime('%d.%m.%Y %H:%M:%S') }}</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="px-6 py-4 text-center text-gray-400">Keine Logs gefunden.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- PAGINATION -->
{% if total_pages > 1 %}
<nav class="mt-6 flex items-center justify-between">
<div>
<p class="text-sm text-gray-400">
Seite <span class="font-medium">{{ page }}</span> von <span class="font-medium">{{ total_pages }}</span>
</p>
</div>
<div class="flex-1 flex justify-end gap-2">
<a href="{{ url_for('logs', page=page-1) }}"
class="relative inline-flex items-center px-4 py-2 border border-gray-700 text-sm font-medium rounded-md text-gray-300 bg-gray-800 hover:bg-gray-700
{% if page <= 1 %}pointer-events-none opacity-50{% endif %}">
Vorherige
</a>
<a href="{{ url_for('logs', page=page+1) }}"
class="relative inline-flex items-center px-4 py-2 border border-gray-700 text-sm font-medium rounded-md text-gray-300 bg-gray-800 hover:bg-gray-700
{% if page >= total_pages %}pointer-events-none opacity-50{% endif %}">
Nächste
</a>
</div>
</nav>
{% endif %}
{% endblock %}

View file

@ -9,6 +9,40 @@
{% block content %}
<div x-data="{ showAddRoleModal: false, showDeleteModal: false, roleToDelete: null, roleToEdit: null }">
<main class="flex-1 p-8 space-y-10" id="mainContent">
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- HEADER -->
<header class="flex justify-between items-center mb-10">
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Rollen verwalten</h2>

View file

@ -2,6 +2,10 @@
{% block title %}AstraOS — Einstellungen{% endblock %}
{% block head %}
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{% endblock %}
{% block content %}
<div class="space-y-10">
<header>
@ -11,11 +15,35 @@
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div class="rounded-md bg-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/20 p-4 text-sm text-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-300 border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/30">
{{ message }}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}

View file

@ -4,13 +4,26 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}AstraOS{% endblock %}</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='media/AstraOS_Logo.ico') }}">
<link rel="icon" href="{{ url_for('static', filename='logo.png') }}" type="image/png">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
{% block head %}{% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<style>
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #111827;
}
::-webkit-scrollbar-thumb {
background-color: #4b5563;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #6b7280;
}
body {
background: radial-gradient(circle at top, #1b1b26, #0a0a0f 70%);
overflow-x: hidden;
@ -35,24 +48,21 @@
justify-content: center;
transition: background-color 0.2s, border-color 0.2s;
margin-right: 0.75rem;
border-color: #8b5cf6; /* purple-500 */
}
.custom-checkbox-box svg {
width: 14px;
height: 14px;
color: white;
transform: scale(0);
transition: transform 0.2s ease-in-out;
.custom-checkbox-label.maintenance .custom-checkbox-box {
border-color: #f59e0b; /* yellow-600 */
}
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box {
background-color: #8b5cf6;
border-color: #8b5cf6;
input[type="checkbox"]:checked + .custom-checkbox-box {
background-color: #8b5cf6; /* purple-500 */
border-color: #8b5cf6; /* purple-500 */
}
.custom-checkbox-label.maintenance input[type="checkbox"]:checked + .custom-checkbox-box {
background-color: #ca8a04;
border-color: #ca8a04;
background-color: #f59e0b; /* yellow-600 */
border-color: #f59e0b; /* yellow-600 */
}
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box svg {
transform: scale(1);
input[type="checkbox"]:checked + .custom-checkbox-box svg {
display: block;
}
footer {
text-align: center;
@ -120,24 +130,24 @@
0% { transform: translateY(100vh) scale(1); }
100% { transform: translateY(-10vh) scale(1.5); }
}
.toast-container {
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.toast {
transform-origin: top right;
}
</style>
{% block head %}{% endblock %}
</head>
<body class="text-white flex flex-col min-h-screen">
<div id="startup">
<div id="star">
<div class="blur"></div>
<svg class="relative z-10" viewBox="0 0 100 100">
<path d="M50 5 L63 40 L95 50 L63 60 L50 95 L37 60 L5 50 L37 40 Z" fill="#c084fc"/>
<path d="M50 28 L58 45 L76 50 L58 55 L50 72 L42 55 L24 50 L42 45 Z" fill="#fff" opacity="0.9"/>
</svg>
</div>
<p>AstraOS</p>
</div>
<div class="animated-bg"></div>
<div class="flex">
<body class="bg-gray-900 text-gray-200 font-sans antialiased">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<aside class="fixed top-0 left-0 w-64 h-screen bg-black/40 border-r p-6 backdrop-blur-lg flex flex-col flex-shrink-0 z-20
{% if maintenance_active %}border-yellow-500/30{% else %}border-purple-500/20{% endif %}">
<h1 class="text-3xl font-bold mb-8 tracking-wide
@ -192,6 +202,14 @@
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}{% endif %}">
🛡️ Role Management
</a>
{% if 'view_logs' in session.get('permissions', []) %}
<a href="{{ url_for('logs') }}"
class="block px-3 py-1 rounded-md transition
{% if active_page == 'logs' %}{% if maintenance_active %}bg-yellow-500/10 text-yellow-300{% else %}bg-purple-500/10 text-purple-300{% endif %}{% else %}
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}{% endif %}">
📜 Logs
</a>
{% endif %}
<a href="{{ url_for('maintenance_management') }}"
class="block px-3 py-1 rounded-md transition
{% if active_page == 'maintenance_management' %}{% if maintenance_active %}bg-yellow-500/10 text-yellow-300{% else %}bg-purple-500/10 text-purple-300{% endif %}{% else %}

View file

@ -34,11 +34,35 @@
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div class="rounded-md bg-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/20 p-4 text-sm text-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-300 border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else 'blue') }}-500/30">
{{ message }}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}

View file

@ -8,6 +8,40 @@
{% block content %}
<div x-data="{ showDeleteModal: false }">
<!-- FLASH MESSAGES -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="toast-container">
{% for category, message in messages %}
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show"
x-transition:enter="toast transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-x-8"
x-transition:enter-end="opacity-100 translate-x-0"
x-transition:leave="toast transition ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="max-w-sm w-full bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden border border-{{ 'red' if category == 'danger' else ('green' if category == 'success' else ('blue' if category == 'info' else 'gray')) }}-500/40">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
{% if category == 'success' %}
<svg class="h-6 w-6 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% elif category == 'danger' %}
<svg class="h-6 w-6 text-red-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% else %}
<svg class="h-6 w-6 text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{% endif %}
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-gray-100">{{ message }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<header class="flex justify-between items-center mb-6">
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Benutzerdetails</h2>
<a href="{{ url_for('age_verification') }}" class="font-bold py-2 px-4 rounded-lg transition text-white {% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">

View file

@ -180,6 +180,17 @@ try:
except Exception as e:
tickets_collection = None
print(f"FATAL: Verbindung zu Tickets-DB fehlgeschlagen: {e}")
def add_log(action, details=None):
if 'username' in session and logs_collection is not None:
log_entry = {
'username': session['username'],
'action': action,
'details': details,
'timestamp': datetime.utcnow()
}
logs_collection.insert_one(log_entry)
def get_user_permissions(user_id):
user = accounts_collection.find_one({'_id': ObjectId(user_id)})
if not user or 'role_id' not in user:
@ -252,6 +263,7 @@ def login():
session['user_id'] = str(user['_id'])
session['username'] = user['username']
session['permissions'] = get_user_permissions(str(user['_id']))
add_log("User Login")
# Prüfen, ob Discord verknüpft ist
if not user.get('discord_id'):
@ -389,6 +401,7 @@ def dashboard():
@app.route('/logout')
@login_required
def logout():
add_log("User Logout")
session.clear()
flash("Sie wurden erfolgreich abgemeldet.", "info")
return redirect(url_for('login'))
@ -447,12 +460,13 @@ def add_user():
'discord_username': None,
'discord_avatar': None
})
add_log("User Created", f"Username: {username}")
if dashboard_collection is not None:
dashboard_collection.update_one(
{"key": "last_user_created"},
{"$set": {
"time": datetime.utcnow(),
"by": username
"by": session.get('username')
}},
upsert=True
)
@ -491,6 +505,7 @@ def edit_user(user_id):
{'_id': ObjectId(user_id)},
{'$set': update_data}
)
add_log("User Edited", f"Username: {username}")
flash(f"Benutzer '{username}' wurde erfolgreich aktualisiert.", "success")
return redirect(url_for('user_management'))
@app.route('/delete_user/<user_id>', methods=['POST'])
@ -503,7 +518,9 @@ def delete_user(user_id):
if session.get('user_id') == user_id:
flash("Sie können sich nicht selbst löschen.", "danger")
return redirect(url_for('user_management'))
user = accounts_collection.find_one({'_id': ObjectId(user_id)})
accounts_collection.delete_one({'_id': ObjectId(user_id)})
add_log("User Deleted", f"Username: {user.get('username', 'N/A')}")
flash("Benutzer wurde erfolgreich gelöscht.", "success")
return redirect(url_for('user_management'))
@ -536,6 +553,7 @@ def update_password():
hashed_password = generate_password_hash(new_password)
accounts_collection.update_one({'_id': ObjectId(user_id)}, {'$set': {'password': hashed_password}})
add_log("Password Changed")
flash("Dein Passwort wurde erfolgreich geändert.", "success")
return redirect(url_for('settings'))
@ -547,6 +565,7 @@ def unlink_discord():
{'_id': ObjectId(user_id)},
{'$set': {'discord_id': None, 'discord_username': None, 'discord_avatar': None}}
)
add_log("Discord Unlinked")
flash("Deine Discord-Verknüpfung wurde entfernt. Bitte verknüpfe dein Konto erneut.", "info")
return redirect(url_for('link_discord'))
@ -621,6 +640,7 @@ def callback_discord():
'discord_avatar': discord_avatar
}}
)
add_log("Discord Linked")
flash("Discord-Account erfolgreich verknüpft!", "success")
return redirect(url_for('dashboard'))
@ -632,6 +652,7 @@ def callback_discord():
session['user_id'] = str(user['_id'])
session['username'] = user['username']
session['permissions'] = get_user_permissions(str(user['_id']))
add_log("User Login via Discord")
flash("Erfolgreich mit Discord angemeldet!", "success")
return redirect(url_for('dashboard'))
else:
@ -666,6 +687,7 @@ def add_role():
"permissions": permissions,
"is_deletable": True
})
add_log("Role Created", f"Role: {role_name}")
flash(f"Rolle '{role_name}' wurde erstellt.", "success")
return redirect(url_for('role_management'))
@app.route('/edit_role/<role_id>', methods=['POST'])
@ -681,6 +703,7 @@ def edit_role(role_id):
{'_id': ObjectId(role_id)},
{'$set': {'name': role_name, 'permissions': permissions}}
)
add_log("Role Edited", f"Role: {role_name}")
flash(f"Rolle '{role_name}' wurde aktualisiert.", "success")
return redirect(url_for('role_management'))
@app.route('/delete_role/<role_id>', methods=['POST'])
@ -692,6 +715,7 @@ def delete_role(role_id):
flash("Diese Rolle kann nicht gelöscht werden.", "danger")
return redirect(url_for('role_management'))
roles_collection.delete_one({'_id': ObjectId(role_id)})
add_log("Role Deleted", f"Role: {role.get('name', 'N/A')}")
flash("Rolle wurde gelöscht.", "success")
return redirect(url_for('role_management'))
@ -734,6 +758,7 @@ def age_verification():
result['ban_duration_days'] = ban_duration_days
result['ban_command'] = f"oban {steam_id} {ban_duration_days}d"
add_log("Age Verification Added", f"Ingame Name: {ingame_name}, SteamID: {steam_id}")
flash("Altersüberprüfung erfolgreich durchgeführt und gespeichert.", "success")
if search_query:
@ -803,6 +828,7 @@ def delete_age_verification(user_id):
return redirect(url_for('view_user_age_details', user_id=user_id))
age_verification_collection.delete_one({'_id': ObjectId(user_id)})
add_log("Age Verification Deleted", f"Ingame Name: {entry.get('ingame_name', 'N/A')}, SteamID: {entry.get('steam_id', 'N/A')}")
flash("Eintrag zur Altersüberprüfung wurde gelöscht.", "success")
return redirect(url_for('age_verification'))
@app.route('/ip_analyzer', methods=['GET', 'POST'])
@ -844,6 +870,7 @@ def ip_analyzer():
"added_by": session["username"],
"timestamp": datetime.utcnow()
})
add_log("IP Analyzed", f"IP: {ip_address}")
flash("IP-Adresse erfolgreich analysiert und gespeichert.", "success")
else:
error = f"Fehler bei der API-Anfrage: Status {response.status_code}"
@ -856,6 +883,25 @@ def ip_analyzer():
ip_data=ip_data,
error=error,
similar_ips=similar_ips)
@app.route('/logs')
@login_required
@permission_required('view_logs')
def logs():
page = request.args.get('page', 1, type=int)
per_page = 25
skip = (page - 1) * per_page
log_entries = list(logs_collection.find().sort('timestamp', -1).skip(skip).limit(per_page))
total_logs = logs_collection.count_documents({})
total_pages = (total_logs + per_page - 1) // per_page
return render_template('logs.html',
logs=log_entries,
page=page,
total_pages=total_pages,
active_page='logs')
def stream_bot_output(bot_id, process):
def stream_to_socket(pipe, log_type):
try:
@ -913,6 +959,7 @@ def upload_bot():
'created_at': datetime.utcnow(),
'status': 'stopped'
})
add_log("Bot Uploaded", f"Bot Name: {bot_name}")
flash(f"Bot '{bot_name}' wurde erfolgreich hochgeladen.", "success")
return redirect(url_for('discord_bots'))
@app.route('/delete_bot/<bot_id>', methods=['POST'])
@ -939,6 +986,7 @@ def delete_bot(bot_id):
except Exception as e:
flash(f"Fehler beim Löschen der Bot-Datei: {e}", "danger")
discord_bots_collection.delete_one({'_id': ObjectId(bot_id)})
add_log("Bot Deleted", f"Bot Name: {bot_info.get('name', 'N/A')}")
flash(f"Bot '{bot_info['name']}' wurde erfolgreich gelöscht.", "success")
return redirect(url_for('discord_bots'))
@app.route('/bot_console/<bot_id>')
@ -994,6 +1042,7 @@ def handle_toggle_bot(data):
thread.daemon = True
thread.start()
discord_bots_collection.update_one({'_id': ObjectId(bot_id)}, {'$set': {'status': 'running'}})
add_log("Bot Started", f"Bot Name: {bot_info.get('name', 'N/A')}")
emit('status_update', {'bot_id': bot_id, 'status': 'running'}, room=bot_id)
emit('console_output', {'type': 'system', 'data': f'--- Bot-Prozess gestartet (PID: {process.pid}) ---'}, room=bot_id)
except Exception as e:
@ -1008,6 +1057,7 @@ def handle_toggle_bot(data):
if bot_id in running_bots:
del running_bots[bot_id]
discord_bots_collection.update_one({'_id': ObjectId(bot_id)}, {'$set': {'status': 'stopped'}})
add_log("Bot Stopped", f"Bot Name: {bot_info.get('name', 'N/A')}")
emit('status_update', {'bot_id': bot_id, 'status': 'stopped'}, room=bot_id)
emit('console_output', {'type': 'system', 'data': '--- Bot-Prozess wird gestoppt... ---'}, room=bot_id)
@app.route('/maintenance')
@ -1083,6 +1133,7 @@ def update_maintenance():
{"$set": update_data},
upsert=True
)
add_log("Maintenance Settings Updated", f"Enabled: {is_enabled}, Message: {status_message}")
flash("Wartungseinstellungen wurden aktualisiert.", "success")
return redirect(url_for('maintenance_management'))
if __name__ == '__main__':