Astra OS Ver. 1.1 hinzugefügt:
- Bugfixes - Funktionen mit Dahboard zsw hinzugefügt - Bereit für release - Icon hizugefügt
This commit is contained in:
parent
644437067a
commit
e0ab950431
|
|
@ -12,6 +12,20 @@
|
||||||
box-shadow: 0 0 20px rgba(192,132,252,0.3);
|
box-shadow: 0 0 20px rgba(192,132,252,0.3);
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
/* Schriftgröße für Systemstatus-Text im Wartungsmodus noch kleiner und mittig */
|
||||||
|
#system-status-text.small {
|
||||||
|
font-size: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#system-status-desc .wartung-desc {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
@ -62,16 +76,30 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Systemstatus -->
|
<!-- Systemstatus -->
|
||||||
<div class="glow-card opacity-0 translate-y-8 bg-black/40 border border-green-500/40 rounded-xl p-6 backdrop-blur">
|
<div id="system-status-card" class="glow-card opacity-0 translate-y-8 bg-black/40 border border-green-500/40 rounded-xl p-6 backdrop-blur">
|
||||||
<h3 class="text-xl font-semibold mb-2 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Systemstatus</h3>
|
<h3 class="text-xl font-semibold mb-2 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Systemstatus</h3>
|
||||||
{% set status = stats.system_status.status %}
|
{% set status = stats.system_status.status %}
|
||||||
<p class="text-4xl font-bold {% if status.lower() == 'online' %}text-green-400{% else %}text-red-400{% endif %}">{{ status }}</p>
|
<p id="system-status-text"
|
||||||
<p class="text-gray-400 text-sm mt-1">Alle Systeme funktionsfähig</p>
|
class="text-4xl font-bold
|
||||||
|
{% if 'online' in status.lower() %}text-green-400
|
||||||
|
{% elif 'wartung' in status.lower() or 'systeme im wartungsmodus' in status.lower() %}text-yellow-400 small
|
||||||
|
{% else %}text-red-400{% endif %}">
|
||||||
|
{{ status }}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 text-sm mt-1" id="system-status-desc">
|
||||||
|
{% if 'wartung' in status.lower() or 'systeme im wartungsmodus' in status.lower() %}
|
||||||
|
<span class="wartung-desc">Systeme im Wartungsmodus</span>
|
||||||
|
{% elif 'online' in status.lower() %}
|
||||||
|
Alle Systeme funktionsfähig
|
||||||
|
{% else %}
|
||||||
|
Teilweise oder komplett offline
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Geplante Wartung -->
|
<!-- Geplante Wartung -->
|
||||||
<div class="glow-card opacity-0 translate-y-8 bg-black/40 rounded-xl p-6 backdrop-blur
|
<div class="glow-card opacity-0 translate-y-8 bg-black/40 rounded-xl p-6 backdrop-blur
|
||||||
{% if maintenance_active %}border border-yellow-500/30{% else %}border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
<h3 class="text-xl font-semibold mb-2 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Geplante Wartung</h3>
|
<h3 class="text-xl font-semibold mb-2 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Geplante Wartung</h3>
|
||||||
{% set maint = stats.scheduled_maintenance %}
|
{% set maint = stats.scheduled_maintenance %}
|
||||||
{% if maint and maint.enabled %}
|
{% if maint and maint.enabled %}
|
||||||
|
|
@ -165,5 +193,26 @@ function updateServerLoad() {
|
||||||
}
|
}
|
||||||
setInterval(updateServerLoad, 5000);
|
setInterval(updateServerLoad, 5000);
|
||||||
updateServerLoad();
|
updateServerLoad();
|
||||||
|
|
||||||
|
// Systemstatus dynamisch Randfarbe anpassen
|
||||||
|
function updateSystemStatusCard() {
|
||||||
|
const card = document.getElementById('system-status-card');
|
||||||
|
const statusText = document.getElementById('system-status-text');
|
||||||
|
if (!card || !statusText) return;
|
||||||
|
card.classList.remove('border-green-500/40', 'border-yellow-500/40', 'border-red-500/40');
|
||||||
|
statusText.classList.remove('small');
|
||||||
|
const status = statusText.textContent.toLowerCase();
|
||||||
|
if (status.includes('wartung') || status.includes('systeme im wartungsmodus')) {
|
||||||
|
card.classList.add('border-yellow-500/40');
|
||||||
|
statusText.classList.add('small');
|
||||||
|
} else if (status.includes('online')) {
|
||||||
|
card.classList.add('border-green-500/40');
|
||||||
|
} else {
|
||||||
|
card.classList.add('border-red-500/40');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(updateSystemStatusCard, 100); // initial nach Render
|
||||||
|
// Optional: falls Systemstatus dynamisch per AJAX kommt, hier regelmäßig prüfen
|
||||||
|
setInterval(updateSystemStatusCard, 2000);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>AstraOS — Login</title>
|
<title>AstraOS — Login</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='media/AstraOS_Logo.ico') }}">
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,18 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Discord Bots Wartungsmodus Toggle -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-gray-900/50 rounded-lg">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-semibold {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Discord Bots Wartungsmodus</h3>
|
||||||
|
<p class="text-gray-400 text-sm mt-1">Setzt alle Discord Bots in den Wartungsmodus (Status wird auf der Status-Seite angezeigt).</p>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" name="discord_bots_maintenance" {% if maint_info.discord_bots_maintenance %}checked{% endif %}>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Maintenance Details -->
|
<!-- Maintenance Details -->
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}AstraOS{% endblock %}</title>
|
<title>{% block title %}AstraOS{% endblock %}</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='media/AstraOS_Logo.ico') }}">
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||||
|
|
@ -15,7 +16,6 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
/* Custom Checkbox */
|
|
||||||
.custom-checkbox-label {
|
.custom-checkbox-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
.custom-checkbox-box {
|
.custom-checkbox-box {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border: 2px solid #4a5568; /* gray-600 */
|
border: 2px solid #4a5568;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -44,18 +44,16 @@
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box {
|
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box {
|
||||||
background-color: #8b5cf6; /* purple-500 */
|
background-color: #8b5cf6;
|
||||||
border-color: #8b5cf6;
|
border-color: #8b5cf6;
|
||||||
}
|
}
|
||||||
.custom-checkbox-label.maintenance input[type="checkbox"]:checked + .custom-checkbox-box {
|
.custom-checkbox-label.maintenance input[type="checkbox"]:checked + .custom-checkbox-box {
|
||||||
background-color: #ca8a04; /* yellow-500 */
|
background-color: #ca8a04;
|
||||||
border-color: #ca8a04;
|
border-color: #ca8a04;
|
||||||
}
|
}
|
||||||
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box svg {
|
.custom-checkbox-label input[type="checkbox"]:checked + .custom-checkbox-box svg {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
@ -63,7 +61,6 @@
|
||||||
color: #888;
|
color: #888;
|
||||||
background: rgba(0,0,0,0.3);
|
background: rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
/* Loading Screen fix: wirklich alles abdecken */
|
|
||||||
#startup {
|
#startup {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
|
@ -127,7 +124,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body class="text-white flex flex-col min-h-screen">
|
<body class="text-white flex flex-col min-h-screen">
|
||||||
|
|
||||||
<!-- Loading Screen überall -->
|
|
||||||
<div id="startup">
|
<div id="startup">
|
||||||
<div id="star">
|
<div id="star">
|
||||||
<div class="blur"></div>
|
<div class="blur"></div>
|
||||||
|
|
@ -139,12 +135,9 @@
|
||||||
<p>AstraOS</p>
|
<p>AstraOS</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Animierter Hintergrund überall -->
|
|
||||||
<div class="animated-bg"></div>
|
<div class="animated-bg"></div>
|
||||||
|
|
||||||
<!-- Layout: Sidebar und Content nebeneinander, volle Höhe -->
|
|
||||||
<div class="flex flex-grow">
|
<div class="flex flex-grow">
|
||||||
<!-- SIDEBAR -->
|
|
||||||
<aside class="w-64 h-screen bg-black/40 border-r p-6 backdrop-blur-lg flex flex-col flex-shrink-0
|
<aside class="w-64 h-screen bg-black/40 border-r p-6 backdrop-blur-lg flex flex-col flex-shrink-0
|
||||||
{% if maintenance_active %}border-yellow-500/30{% else %}border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border-yellow-500/30{% else %}border-purple-500/20{% endif %}">
|
||||||
<h1 class="text-3xl font-bold mb-8 tracking-wide
|
<h1 class="text-3xl font-bold mb-8 tracking-wide
|
||||||
|
|
@ -166,8 +159,6 @@
|
||||||
Status Page
|
Status Page
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
<!-- ADMINISTRATION -->
|
|
||||||
<div>
|
<div>
|
||||||
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
||||||
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
||||||
|
|
@ -203,7 +194,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PRIVATE TOOLS -->
|
|
||||||
<div>
|
<div>
|
||||||
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
||||||
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
||||||
|
|
@ -228,7 +218,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MOONLAB TOOLS -->
|
|
||||||
<div>
|
<div>
|
||||||
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
<button class="w-full text-left px-3 py-2 rounded-md transition flex justify-between items-center collapsible
|
||||||
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}"
|
||||||
|
|
@ -259,13 +248,11 @@
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- MAIN CONTENT -->
|
|
||||||
<main class="flex-1 p-8 space-y-10 overflow-y-auto" id="mainContent">
|
<main class="flex-1 p-8 space-y-10 overflow-y-auto" id="mainContent">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- LOGOUT BUTTON -->
|
|
||||||
<a href="{{ url_for('logout') }}" class="fixed bottom-6 left-6 text-white font-bold py-3 px-5 rounded-full shadow-lg transition-all backdrop-blur-sm flex items-center gap-2
|
<a href="{{ url_for('logout') }}" class="fixed bottom-6 left-6 text-white font-bold py-3 px-5 rounded-full shadow-lg transition-all backdrop-blur-sm flex items-center gap-2
|
||||||
{% if maintenance_active %}
|
{% if maintenance_active %}
|
||||||
bg-yellow-600/50 hover:bg-yellow-500/80 hover:shadow-yellow-500/40 border border-yellow-500/20
|
bg-yellow-600/50 hover:bg-yellow-500/80 hover:shadow-yellow-500/40 border border-yellow-500/20
|
||||||
|
|
@ -278,7 +265,6 @@
|
||||||
<span>Logout</span>
|
<span>Logout</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- FOOTER -->
|
|
||||||
<footer>
|
<footer>
|
||||||
© 2025 AstraOS
|
© 2025 AstraOS
|
||||||
</footer>
|
</footer>
|
||||||
|
|
@ -302,7 +288,6 @@ setTimeout(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 400);
|
}, 400);
|
||||||
// Animated Background Dots
|
|
||||||
const bg = document.querySelector('.animated-bg');
|
const bg = document.querySelector('.animated-bg');
|
||||||
if (bg) {
|
if (bg) {
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
|
|
|
||||||
|
|
@ -35,45 +35,74 @@
|
||||||
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">System Status</h2>
|
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">System Status</h2>
|
||||||
</header>
|
</header>
|
||||||
<!-- STATUS LIST -->
|
<!-- STATUS LIST -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4" id="status-list">
|
||||||
<!-- Chainsaw Hood SCP:SL server -->
|
|
||||||
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
||||||
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
<span class="text-xl font-semibold text-gray-200">Chainsaw Hood SCP:SL Server</span>
|
<span class="text-xl font-semibold text-gray-200">Chainsaw Hood SCP:SL Server</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-green-400 font-semibold">Online</span>
|
<span id="scpsl-status-text" class="font-semibold">Lädt...</span>
|
||||||
<div class="status-indicator status-online"></div>
|
<div id="scpsl-status-indicator" class="status-indicator status-offline"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Kettensägen Gamepanel -->
|
|
||||||
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
||||||
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
<span class="text-xl font-semibold text-gray-200">Kettensägen Gamepanel</span>
|
<span class="text-xl font-semibold text-gray-200">Kettensägen Gamepanel</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-green-400 font-semibold">Online</span>
|
<span id="gamepanel-status-text" class="font-semibold">Lädt...</span>
|
||||||
<div class="status-indicator status-online"></div>
|
<div id="gamepanel-status-indicator" class="status-indicator status-offline"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Discord Bots -->
|
|
||||||
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
||||||
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
<span class="text-xl font-semibold text-gray-200">Discord Bots</span>
|
<span class="text-xl font-semibold text-gray-200">Discord Bots</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-yellow-400 font-semibold">Wartungsarbeiten</span>
|
<span id="discordbots-status-text" class="font-semibold">Lädt...</span>
|
||||||
<div class="status-indicator status-wartung"></div>
|
<div id="discordbots-status-indicator" class="status-indicator status-offline"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Datenbank -->
|
|
||||||
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
<div class="bg-black/40 rounded-xl p-6 backdrop-blur-lg flex justify-between items-center
|
||||||
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/30{% else %}border border-purple-500/20{% endif %}">
|
||||||
<span class="text-xl font-semibold text-gray-200">Datenbank</span>
|
<span class="text-xl font-semibold text-gray-200">Datenbank</span>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-red-400 font-semibold">Offline</span>
|
<span id="db-status-text" class="font-semibold">Lädt...</span>
|
||||||
<div class="status-indicator status-offline"></div>
|
<div id="db-status-indicator" class="status-indicator status-offline"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
function updateStatusPage() {
|
||||||
|
fetch('/api/system_status')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
// Helper
|
||||||
|
function setStatus(id, status) {
|
||||||
|
let text = "";
|
||||||
|
let indicator = "";
|
||||||
|
if (status === "online") {
|
||||||
|
text = "Online";
|
||||||
|
indicator = "status-online";
|
||||||
|
} else if (status === "offline") {
|
||||||
|
text = "Offline";
|
||||||
|
indicator = "status-offline";
|
||||||
|
} else if (status === "wartung") {
|
||||||
|
text = "Wartungsarbeiten";
|
||||||
|
indicator = "status-wartung";
|
||||||
|
} else {
|
||||||
|
text = status;
|
||||||
|
indicator = "status-offline";
|
||||||
|
}
|
||||||
|
document.getElementById(id + "-status-text").textContent = text;
|
||||||
|
let el = document.getElementById(id + "-status-indicator");
|
||||||
|
el.classList.remove("status-online", "status-offline", "status-wartung");
|
||||||
|
el.classList.add(indicator);
|
||||||
|
}
|
||||||
|
setStatus("scpsl", data.scpsl);
|
||||||
|
setStatus("gamepanel", data.gamepanel);
|
||||||
|
setStatus("discordbots", data.discord_bots);
|
||||||
|
setStatus("db", data.database);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setInterval(updateStatusPage, 5000);
|
||||||
|
updateStatusPage();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,25 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div x-data="{ showAddUserModal: false, showDeleteModal: false, userToDelete: null }">
|
<div x-data="{
|
||||||
|
showAddUserModal: false,
|
||||||
|
showDeleteModal: false,
|
||||||
|
userToDelete: null,
|
||||||
|
userToEdit: null,
|
||||||
|
usersData: {},
|
||||||
|
init() {
|
||||||
|
this.usersData = JSON.parse(document.getElementById('users-data').textContent);
|
||||||
|
},
|
||||||
|
openEditModal(userId) {
|
||||||
|
this.userToEdit = this.usersData[userId];
|
||||||
|
this.showAddUserModal = true;
|
||||||
|
}
|
||||||
|
}">
|
||||||
<div class="space-y-10">
|
<div class="space-y-10">
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<header class="flex justify-between items-center">
|
<header class="flex justify-between items-center">
|
||||||
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">User Management</h2>
|
<h2 class="text-4xl font-bold tracking-wide {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">User Management</h2>
|
||||||
<button @click="showAddUserModal = true" class="font-bold py-2 px-4 rounded-lg transition text-white
|
<button @click="userToEdit = null; showAddUserModal = true" 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 %}">
|
{% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">
|
||||||
+ Neuen Benutzer hinzufügen
|
+ Neuen Benutzer hinzufügen
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -32,13 +45,14 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/30{% endif %}">
|
<tbody class="divide-y {% if maintenance_active %}divide-yellow-500/30{% else %}divide-purple-500/30{% endif %}">
|
||||||
{% for user in users %}
|
{% for user_table_item in users_for_table %}
|
||||||
<tr class="transition {% if maintenance_active %}hover:bg-yellow-500/5{% else %}hover:bg-purple-500/5{% endif %}">
|
<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">{{ user.username }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">{{ user_table_item.username }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ user.role_name }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ user_table_item.role_name }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ user.creation_date.strftime('%d.%m.%Y') if user.creation_date else 'N/A' }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">{{ user_table_item.creation_date.strftime('%d.%m.%Y') if user_table_item.creation_date else 'N/A' }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<button @click="userToDelete = '{{ user._id }}'; showDeleteModal = true" class="text-red-400 hover:text-red-300">Löschen</button>
|
<button @click="openEditModal('{{ user_table_item._id | string }}')" class="mr-4 {% if maintenance_active %}text-yellow-400 hover:text-yellow-300{% else %}text-indigo-400 hover:text-indigo-300{% endif %}">Bearbeiten</button>
|
||||||
|
<button @click="userToDelete = '{{ user_table_item._id }}'; showDeleteModal = true" class="text-red-400 hover:text-red-300">Löschen</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -47,33 +61,58 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ADD USER MODAL -->
|
<!-- ADD/EDIT USER MODAL -->
|
||||||
<div x-show="showAddUserModal" @keydown.escape.window="showAddUserModal = false" class="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50" style="display: none;">
|
<div x-show="showAddUserModal" @keydown.escape.window="showAddUserModal = false" class="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50" style="display: none;">
|
||||||
<div @click.away="showAddUserModal = false" class="bg-gray-900 rounded-xl shadow-2xl p-8 w-full max-w-md
|
<div @click.away="showAddUserModal = false" class="bg-gray-900 rounded-xl shadow-2xl p-8 w-full max-w-md
|
||||||
{% if maintenance_active %}border border-yellow-500/50{% else %}border border-purple-500/50{% endif %}">
|
{% if maintenance_active %}border border-yellow-500/50{% else %}border border-purple-500/50{% endif %}">
|
||||||
<h3 class="text-2xl font-bold mb-6 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}">Neuen Benutzer erstellen</h3>
|
<h3 class="text-2xl font-bold mb-6 {% if maintenance_active %}text-yellow-300{% else %}text-purple-300{% endif %}" x-text="userToEdit ? 'Benutzer bearbeiten' : 'Neuen Benutzer erstellen'"></h3>
|
||||||
<form action="{{ url_for('add_user') }}" method="POST" class="space-y-4">
|
<form :action="userToEdit ? '/edit_user/' + userToEdit._id : '/add_user'" method="POST" class="space-y-4">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm font-medium text-gray-300">Benutzername</label>
|
<label for="username" class="block text-sm font-medium text-gray-300">Benutzername</label>
|
||||||
<input type="text" name="username" id="username" 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 %}">
|
<input type="text" name="username" id="username" required :value="userToEdit ? userToEdit.username : ''" 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>
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm font-medium text-gray-300">Passwort</label>
|
<label for="password" class="block text-sm font-medium text-gray-300">Neues Passwort</label>
|
||||||
<input type="password" name="password" id="password" 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 %}">
|
<input type="password" name="password" id="password" :required="!userToEdit" :placeholder="userToEdit ? 'Leer lassen, um nicht zu ändern' : ''" 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>
|
||||||
|
<!-- Custom Role Selector -->
|
||||||
|
<div x-data="{
|
||||||
|
open: false,
|
||||||
|
roles: {{ roles | tojson }},
|
||||||
|
selectedRole: null,
|
||||||
|
init() {
|
||||||
|
if (this.userToEdit && this.userToEdit.role_id) {
|
||||||
|
this.selectedRole = this.roles.find(r => r._id === this.userToEdit.role_id);
|
||||||
|
} else {
|
||||||
|
this.selectedRole = this.roles.length > 0 ? this.roles[0] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}" @click.away="open = false" class="relative">
|
||||||
|
<label class="block text-sm font-medium text-gray-300">Rolle</label>
|
||||||
|
<input type="hidden" name="role" :value="selectedRole ? selectedRole._id : ''">
|
||||||
|
<button type="button" @click="open = !open" class="mt-1 w-full flex justify-between items-center 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 %}">
|
||||||
|
<span x-text="selectedRole ? selectedRole.name : 'Rolle auswählen'"></span>
|
||||||
|
<svg class="w-5 h-5 text-gray-400 transform transition-transform" :class="{ 'rotate-180': open }" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div x-show="open" x-transition
|
||||||
|
class="absolute z-10 mt-1 w-full bg-gray-800 border border-gray-700 rounded-lg shadow-lg max-h-60 overflow-y-auto" style="display: none;">
|
||||||
|
<template x-for="role in roles" :key="role._id">
|
||||||
|
<div @click="selectedRole = role; open = false"
|
||||||
|
class="px-3 py-2 text-white cursor-pointer hover:bg-gray-700"
|
||||||
|
:class="{ '{% if maintenance_active %}bg-yellow-600/20{% else %}bg-purple-600/20{% endif %}': selectedRole && selectedRole._id === role._id }"
|
||||||
|
x-text="role.name">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label for="role" class="block text-sm font-medium text-gray-300">Rolle</label>
|
|
||||||
<select name="role" id="role" 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 %}">
|
|
||||||
{% for role in roles %}
|
|
||||||
<option value="{{ role._id }}">{{ role.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end gap-4 pt-4">
|
<div class="flex justify-end gap-4 pt-4">
|
||||||
<button type="button" @click="showAddUserModal = false" class="px-4 py-2 rounded-lg text-gray-300 hover:bg-gray-700 transition">Abbrechen</button>
|
<button type="button" @click="showAddUserModal = false; userToEdit = null" class="px-4 py-2 rounded-lg text-gray-300 hover:bg-gray-700 transition">Abbrechen</button>
|
||||||
<button type="submit" class="px-4 py-2 font-semibold rounded-lg transition text-white
|
<button type="submit" class="px-4 py-2 font-semibold rounded-lg transition text-white
|
||||||
{% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">Benutzer erstellen</button>
|
{% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}"
|
||||||
|
x-text="userToEdit ? 'Änderungen speichern' : 'Benutzer erstellen'"></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,6 +129,11 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Daten für Alpine.js bereitstellen -->
|
||||||
|
<script id="users-data" type="application/json">
|
||||||
|
{{ users_for_modal | tojson | safe }}
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
AstraOS/Frontend/media/AstraOS_Logo.ico
Normal file
BIN
AstraOS/Frontend/media/AstraOS_Logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
|
|
@ -15,6 +15,8 @@ import threading
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import requests
|
import requests
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
|
||||||
# Verwenden Sie eventlet für die Produktion
|
# Verwenden Sie eventlet für die Produktion
|
||||||
async_mode = "threading"
|
async_mode = "threading"
|
||||||
|
|
@ -33,10 +35,14 @@ app.config['BOT_UPLOAD_FOLDER'] = BOT_UPLOAD_FOLDER
|
||||||
# Dictionary zum Speichern laufender Bot-Prozesse
|
# Dictionary zum Speichern laufender Bot-Prozesse
|
||||||
running_bots = {}
|
running_bots = {}
|
||||||
|
|
||||||
# Filter, um die GET /api/server_load Anfragen aus den Logs auszublenden
|
# Filter, um die GET /api/server_load und GET /api/system_status Anfragen aus den Logs auszublenden
|
||||||
class SuppressServerLoadFilter(logging.Filter):
|
class SuppressServerLoadFilter(logging.Filter):
|
||||||
def filter(self, record):
|
def filter(self, record):
|
||||||
return "GET /api/server_load" not in record.getMessage()
|
msg = record.getMessage()
|
||||||
|
return (
|
||||||
|
"GET /api/server_load" not in msg
|
||||||
|
and "GET /api/system_status" not in msg
|
||||||
|
)
|
||||||
|
|
||||||
# Den Filter zum Werkzeug-Logger hinzufügen
|
# Den Filter zum Werkzeug-Logger hinzufügen
|
||||||
log = logging.getLogger('werkzeug')
|
log = logging.getLogger('werkzeug')
|
||||||
|
|
@ -233,6 +239,95 @@ def login():
|
||||||
|
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
|
@app.route('/api/server_load')
|
||||||
|
@login_required
|
||||||
|
def api_server_load():
|
||||||
|
# SSH zu 89.144.42.175, user root, pw iwkms@1812:(
|
||||||
|
try:
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
# Timeout zur Verbindung hinzugefügt, um Hängenbleiben zu verhindern
|
||||||
|
ssh.connect("89.144.42.175", username="root", password="iwkms@1812:(", timeout=10)
|
||||||
|
stdin, stdout, stderr = ssh.exec_command("top -bn1 | grep 'Cpu(s)'")
|
||||||
|
output = stdout.read().decode()
|
||||||
|
ssh.close()
|
||||||
|
# Parse CPU usage
|
||||||
|
import re
|
||||||
|
match = re.search(r"(\d+\.\d+)\s*id", output)
|
||||||
|
if match:
|
||||||
|
idle = float(match.group(1))
|
||||||
|
usage = 100 - idle
|
||||||
|
return jsonify({"percent": round(usage, 1), "status": "Online"})
|
||||||
|
else:
|
||||||
|
return jsonify({"percent": 0, "status": "Fehler"})
|
||||||
|
except paramiko.AuthenticationException:
|
||||||
|
print("SSH-Fehler: Authentifizierung fehlgeschlagen.")
|
||||||
|
return jsonify({"percent": 0, "status": "Auth Fehler"})
|
||||||
|
except paramiko.SSHException as e:
|
||||||
|
print(f"SSH-Fehler: {e}")
|
||||||
|
return jsonify({"percent": 0, "status": "SSH Fehler"})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Allgemeiner Fehler bei Server-Auslastung: {e}")
|
||||||
|
return jsonify({"percent": 0, "status": "Nicht erreichbar"})
|
||||||
|
|
||||||
|
@app.route('/api/system_status')
|
||||||
|
@login_required
|
||||||
|
def api_system_status():
|
||||||
|
# Wartungsstatus prüfen
|
||||||
|
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
|
||||||
|
maintenance_active = maintenance_status and maintenance_status.get('enabled', False)
|
||||||
|
discord_bots_maintenance = maintenance_status.get('discord_bots_maintenance', False) if maintenance_status else False
|
||||||
|
|
||||||
|
# SCP:SL Server Status (UDP-Ping statt TCP)
|
||||||
|
scpsl_status = "offline"
|
||||||
|
try:
|
||||||
|
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
udp_sock.settimeout(2)
|
||||||
|
udp_sock.sendto(b"\x00", ("89.144.42.175", 7101))
|
||||||
|
# Wenn keine Exception, dann ist der Port erreichbar (Server antwortet nicht, aber Port offen)
|
||||||
|
scpsl_status = "online"
|
||||||
|
udp_sock.close()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DEBUG] SCP:SL UDP-Ping failed: {e}")
|
||||||
|
scpsl_status = "offline"
|
||||||
|
if maintenance_active:
|
||||||
|
scpsl_status = "wartung"
|
||||||
|
|
||||||
|
# Gamepanel Status
|
||||||
|
gamepanel_status = "offline"
|
||||||
|
try:
|
||||||
|
resp = requests.get("https://gamepanel.mrsniff.de", timeout=3)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
gamepanel_status = "online"
|
||||||
|
except Exception:
|
||||||
|
gamepanel_status = "offline"
|
||||||
|
if maintenance_active:
|
||||||
|
gamepanel_status = "wartung"
|
||||||
|
|
||||||
|
# Discord Bots Status (online/wartung)
|
||||||
|
discord_bots_status = "wartung" if discord_bots_maintenance or maintenance_active else "online"
|
||||||
|
|
||||||
|
# Datenbank Status
|
||||||
|
db_status = "online" if db is not None else "offline"
|
||||||
|
if maintenance_active:
|
||||||
|
db_status = "wartung"
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"scpsl": scpsl_status,
|
||||||
|
"gamepanel": gamepanel_status,
|
||||||
|
"discord_bots": discord_bots_status,
|
||||||
|
"database": db_status,
|
||||||
|
"maintenance": maintenance_active
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/status')
|
||||||
|
@login_required
|
||||||
|
def status_page():
|
||||||
|
# Wartungsstatus prüfen
|
||||||
|
maintenance_status = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
|
||||||
|
maintenance_active = maintenance_status and maintenance_status.get('enabled', False)
|
||||||
|
return render_template('status_page.html', active_page='status_page', maintenance_active=maintenance_active)
|
||||||
|
|
||||||
@app.route('/dashboard')
|
@app.route('/dashboard')
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
|
|
@ -252,8 +347,25 @@ def dashboard():
|
||||||
# Server-Auslastung (wird per AJAX geladen, hier nur Platzhalter)
|
# Server-Auslastung (wird per AJAX geladen, hier nur Platzhalter)
|
||||||
server_load = {"percent": 0, "status": "Lädt..."}
|
server_load = {"percent": 0, "status": "Lädt..."}
|
||||||
|
|
||||||
# Systemstatus (hier statisch, ggf. anpassen)
|
# Systemstatus per API holen (wie Status-Seite)
|
||||||
system_status = {"status": "Online"}
|
system_status = {"status": "Online"}
|
||||||
|
try:
|
||||||
|
resp = requests.get(request.url_root.rstrip('/') + '/api/system_status', cookies=request.cookies, timeout=2)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
# Priorität: Wartung > Wartungsmodus > Offline > Online
|
||||||
|
if data.get("maintenance"):
|
||||||
|
system_status = {"status": "Online (Wartungsarbeiten)"}
|
||||||
|
elif any(data.get(k) == "wartung" for k in ["scpsl", "gamepanel", "discord_bots", "database"]):
|
||||||
|
system_status = {"status": "Systeme im Wartungsmodus"}
|
||||||
|
elif all(data.get(k) == "online" for k in ["scpsl", "gamepanel", "discord_bots", "database"]):
|
||||||
|
system_status = {"status": "Online"}
|
||||||
|
else:
|
||||||
|
system_status = {"status": "Offline"}
|
||||||
|
else:
|
||||||
|
system_status = {"status": "Offline"}
|
||||||
|
except Exception:
|
||||||
|
system_status = {"status": "Offline"}
|
||||||
|
|
||||||
# Geplante Wartung (aus dashboard_stats falls vorhanden)
|
# Geplante Wartung (aus dashboard_stats falls vorhanden)
|
||||||
scheduled_maintenance = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
|
scheduled_maintenance = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else None
|
||||||
|
|
@ -268,7 +380,7 @@ def dashboard():
|
||||||
maint["time_str"] = _dt_to_str(maint.get("time"))
|
maint["time_str"] = _dt_to_str(maint.get("time"))
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
"active_users": {"value": user_count, "percent": 0}, # Prozent ggf. später berechnen
|
"active_users": {"value": user_count, "percent": 0},
|
||||||
"server_load": server_load,
|
"server_load": server_load,
|
||||||
"open_tickets": {"count": open_tickets},
|
"open_tickets": {"count": open_tickets},
|
||||||
"system_status": system_status,
|
"system_status": system_status,
|
||||||
|
|
@ -282,29 +394,6 @@ def dashboard():
|
||||||
|
|
||||||
return render_template('dashboard.html', stats=stats, active_page='dashboard')
|
return render_template('dashboard.html', stats=stats, active_page='dashboard')
|
||||||
|
|
||||||
@app.route('/api/server_load')
|
|
||||||
@login_required
|
|
||||||
def api_server_load():
|
|
||||||
# SSH zu 89.144.42.175, user root, pw iwkms@1812:(
|
|
||||||
try:
|
|
||||||
ssh = paramiko.SSHClient()
|
|
||||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
||||||
ssh.connect("89.144.42.175", username="root", password="iwkms@1812:(")
|
|
||||||
stdin, stdout, stderr = ssh.exec_command("top -bn1 | grep 'Cpu(s)'")
|
|
||||||
output = stdout.read().decode()
|
|
||||||
ssh.close()
|
|
||||||
# Parse CPU usage
|
|
||||||
import re
|
|
||||||
match = re.search(r"(\d+\.\d+)\s*id", output)
|
|
||||||
if match:
|
|
||||||
idle = float(match.group(1))
|
|
||||||
usage = 100 - idle
|
|
||||||
return jsonify({"percent": round(usage, 1), "status": "Online"})
|
|
||||||
else:
|
|
||||||
return jsonify({"percent": 0, "status": "Fehler"})
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({"percent": 0, "status": "Nicht erreichbar"})
|
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
|
|
@ -312,10 +401,7 @@ def logout():
|
||||||
flash("Sie wurden erfolgreich abgemeldet.", "info")
|
flash("Sie wurden erfolgreich abgemeldet.", "info")
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/status')
|
# --- User Management ---
|
||||||
@login_required
|
|
||||||
def status_page():
|
|
||||||
return render_template('status_page.html', active_page='status_page')
|
|
||||||
|
|
||||||
@app.route('/user_management')
|
@app.route('/user_management')
|
||||||
@login_required
|
@login_required
|
||||||
|
|
@ -328,12 +414,32 @@ def user_management():
|
||||||
users_list = list(accounts_collection.find())
|
users_list = list(accounts_collection.find())
|
||||||
roles_list = list(roles_collection.find())
|
roles_list = list(roles_collection.find())
|
||||||
|
|
||||||
role_map = {str(role['_id']): role['name'] for role in roles_list}
|
# Konvertiere ObjectId in String für die JSON-Serialisierung in roles_list
|
||||||
|
for role in roles_list:
|
||||||
|
role['_id'] = str(role['_id'])
|
||||||
|
|
||||||
|
role_map = {role['_id']: role['name'] for role in roles_list}
|
||||||
|
|
||||||
|
# Daten für das Alpine.js Modal vorbereiten (ohne Datum)
|
||||||
|
users_for_modal = {}
|
||||||
for user in users_list:
|
for user in users_list:
|
||||||
|
user_id_str = str(user['_id'])
|
||||||
|
users_for_modal[user_id_str] = {
|
||||||
|
'_id': user_id_str,
|
||||||
|
'username': user['username'],
|
||||||
|
'role_id': str(user.get('role_id')) if user.get('role_id') else None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Erstelle eine separate Liste für die Anzeige in der Tabelle, die das Datum enthält
|
||||||
|
users_for_table = list(accounts_collection.find())
|
||||||
|
for user in users_for_table:
|
||||||
user['role_name'] = role_map.get(str(user.get('role_id')), 'Unbekannt')
|
user['role_name'] = role_map.get(str(user.get('role_id')), 'Unbekannt')
|
||||||
|
|
||||||
return render_template('user_management.html', users=users_list, roles=roles_list, active_page='user_management')
|
return render_template('user_management.html',
|
||||||
|
users_for_modal=users_for_modal,
|
||||||
|
users_for_table=users_for_table,
|
||||||
|
roles=roles_list,
|
||||||
|
active_page='user_management')
|
||||||
|
|
||||||
@app.route('/add_user', methods=['POST'])
|
@app.route('/add_user', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
|
@ -382,6 +488,45 @@ def add_user():
|
||||||
flash(f"Benutzer '{username}' wurde erfolgreich erstellt.", "success")
|
flash(f"Benutzer '{username}' wurde erfolgreich erstellt.", "success")
|
||||||
return redirect(url_for('user_management'))
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
|
@app.route('/edit_user/<user_id>', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@permission_required('manage_users')
|
||||||
|
def edit_user(user_id):
|
||||||
|
if accounts_collection is None:
|
||||||
|
flash("Datenbankverbindung nicht verfügbar.", "danger")
|
||||||
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
|
username = request.form.get('username')
|
||||||
|
password = request.form.get('password')
|
||||||
|
role_id = request.form.get('role')
|
||||||
|
|
||||||
|
if not username or not role_id:
|
||||||
|
flash("Benutzername und Rolle sind erforderlich.", "danger")
|
||||||
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
|
# Überprüfen, ob der neue Benutzername bereits von einem anderen Benutzer verwendet wird
|
||||||
|
existing_user = accounts_collection.find_one({'username': username})
|
||||||
|
if existing_user and str(existing_user['_id']) != user_id:
|
||||||
|
flash("Ein anderer Benutzer mit diesem Namen existiert bereits.", "danger")
|
||||||
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
|
update_data = {
|
||||||
|
'username': username,
|
||||||
|
'role_id': ObjectId(role_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Passwort nur aktualisieren, wenn ein neues eingegeben wurde
|
||||||
|
if password:
|
||||||
|
update_data['password'] = generate_password_hash(password)
|
||||||
|
|
||||||
|
accounts_collection.update_one(
|
||||||
|
{'_id': ObjectId(user_id)},
|
||||||
|
{'$set': update_data}
|
||||||
|
)
|
||||||
|
|
||||||
|
flash(f"Benutzer '{username}' wurde erfolgreich aktualisiert.", "success")
|
||||||
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
@app.route('/delete_user/<user_id>', methods=['POST'])
|
@app.route('/delete_user/<user_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('manage_users')
|
@permission_required('manage_users')
|
||||||
|
|
@ -398,6 +543,8 @@ def delete_user(user_id):
|
||||||
flash("Benutzer wurde erfolgreich gelöscht.", "success")
|
flash("Benutzer wurde erfolgreich gelöscht.", "success")
|
||||||
return redirect(url_for('user_management'))
|
return redirect(url_for('user_management'))
|
||||||
|
|
||||||
|
# --- Role Management ---
|
||||||
|
|
||||||
@app.route('/role_management')
|
@app.route('/role_management')
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('view_role_management')
|
@permission_required('view_role_management')
|
||||||
|
|
@ -781,8 +928,10 @@ def maintenance_login():
|
||||||
def maintenance_management():
|
def maintenance_management():
|
||||||
maint_info = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else {}
|
maint_info = dashboard_collection.find_one({"key": "scheduled_maintenance"}) if dashboard_collection is not None else {}
|
||||||
if maint_info and maint_info.get('time'):
|
if maint_info and maint_info.get('time'):
|
||||||
# Konvertiere UTC-Zeit aus DB in ein für datetime-local passendes Format
|
|
||||||
maint_info['time_local_str'] = maint_info['time'].strftime('%Y-%m-%dT%H:%M')
|
maint_info['time_local_str'] = maint_info['time'].strftime('%Y-%m-%dT%H:%M')
|
||||||
|
# Discord Bots Wartungsstatus für das Template bereitstellen
|
||||||
|
if 'discord_bots_maintenance' not in maint_info:
|
||||||
|
maint_info['discord_bots_maintenance'] = False
|
||||||
return render_template('maintenance_management.html', active_page='maintenance_management', maint_info=maint_info)
|
return render_template('maintenance_management.html', active_page='maintenance_management', maint_info=maint_info)
|
||||||
|
|
||||||
@app.route('/update_maintenance', methods=['POST'])
|
@app.route('/update_maintenance', methods=['POST'])
|
||||||
|
|
@ -797,12 +946,14 @@ def update_maintenance():
|
||||||
status_message = request.form.get('status_message', 'Keine')
|
status_message = request.form.get('status_message', 'Keine')
|
||||||
duration = request.form.get('duration_minutes')
|
duration = request.form.get('duration_minutes')
|
||||||
time_str = request.form.get('maintenance_time')
|
time_str = request.form.get('maintenance_time')
|
||||||
|
discord_bots_maintenance = request.form.get('discord_bots_maintenance') == 'on'
|
||||||
|
|
||||||
update_data = {
|
update_data = {
|
||||||
"enabled": is_enabled,
|
"enabled": is_enabled,
|
||||||
"status": status_message,
|
"status": status_message,
|
||||||
"duration_minutes": int(duration) if duration and duration.isdigit() else None,
|
"duration_minutes": int(duration) if duration and duration.isdigit() else None,
|
||||||
"time": datetime.fromisoformat(time_str) if time_str else None
|
"time": datetime.fromisoformat(time_str) if time_str else None,
|
||||||
|
"discord_bots_maintenance": discord_bots_maintenance
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard_collection.update_one(
|
dashboard_collection.update_one(
|
||||||
|
|
@ -815,4 +966,26 @@ def update_maintenance():
|
||||||
return redirect(url_for('maintenance_management'))
|
return redirect(url_for('maintenance_management'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# Optionale SSL-Unterstützung:
|
||||||
|
# Legen Sie entweder die Umgebungsvariablen SSL_CERT_FILE und SSL_KEY_FILE fest
|
||||||
|
# oder legen Sie die Dateien certs/fullchain.pem und certs/privkey.pem im Projektordner ab.
|
||||||
|
cert_file = os.environ.get('SSL_CERT_FILE', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'certs', 'fullchain.pem'))
|
||||||
|
key_file = os.environ.get('SSL_KEY_FILE', os.path.join(os.path.dirname(os.path.abspath(__file__)), 'certs', 'privkey.pem'))
|
||||||
|
|
||||||
|
use_ssl = os.path.exists(cert_file) and os.path.exists(key_file)
|
||||||
|
|
||||||
|
# Sitzungscookies nur als Secure markieren, wenn tatsächlich HTTPS verwendet wird
|
||||||
|
app.config['SESSION_COOKIE_SECURE'] = bool(use_ssl)
|
||||||
|
|
||||||
|
if use_ssl:
|
||||||
|
print(f"INFO: Starte mit SSL, cert={cert_file}, key={key_file}")
|
||||||
|
# Für bessere Kontrolle können Sie auch ein SSLContext erstellen:
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
||||||
|
context.load_cert_chain(certfile=cert_file, keyfile=key_file)
|
||||||
|
# In Produktion debug=False setzen und Port 443 verwenden (oder Reverse Proxy)
|
||||||
|
socketio.run(app, host='0.0.0.0', port=443, debug=False, ssl_context=context)
|
||||||
|
else:
|
||||||
|
print("WARNUNG: SSL-Zertifikat nicht gefunden. Starte ohne HTTPS (nur für Entwicklung).")
|
||||||
|
# allow_unsafe_werkzeug bleibt hier, da dies Entwicklungsmodus ist
|
||||||
socketio.run(app, host='0.0.0.0', port=5000, debug=True, allow_unsafe_werkzeug=True)
|
socketio.run(app, host='0.0.0.0', port=5000, debug=True, allow_unsafe_werkzeug=True)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue