diff --git a/AstraOS/Frontend/HTML/login.html b/AstraOS/Frontend/HTML/login.html
new file mode 100644
index 0000000..cacb323
--- /dev/null
+++ b/AstraOS/Frontend/HTML/login.html
@@ -0,0 +1,162 @@
+
+
+
+
+
+ AstraOS — Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AstraOS/astra_os.py b/AstraOS/astra_os.py
new file mode 100644
index 0000000..e69de29
diff --git a/Outdated Website/static/background.png b/Outdated Website/static/background.png
new file mode 100644
index 0000000..ce08f14
Binary files /dev/null and b/Outdated Website/static/background.png differ
diff --git a/Outdated Website/static/circle_green_checkmark.png b/Outdated Website/static/circle_green_checkmark.png
new file mode 100644
index 0000000..aff7ee0
Binary files /dev/null and b/Outdated Website/static/circle_green_checkmark.png differ
diff --git a/Outdated Website/static/green_check.png b/Outdated Website/static/green_check.png
new file mode 100644
index 0000000..b387c97
Binary files /dev/null and b/Outdated Website/static/green_check.png differ
diff --git a/Outdated Website/static/light-mode.png b/Outdated Website/static/light-mode.png
new file mode 100644
index 0000000..419931f
Binary files /dev/null and b/Outdated Website/static/light-mode.png differ
diff --git a/Outdated Website/static/logo.png b/Outdated Website/static/logo.png
new file mode 100644
index 0000000..f2b466e
Binary files /dev/null and b/Outdated Website/static/logo.png differ
diff --git a/Outdated Website/static/night-mode.png b/Outdated Website/static/night-mode.png
new file mode 100644
index 0000000..a5db5b2
Binary files /dev/null and b/Outdated Website/static/night-mode.png differ
diff --git a/Outdated Website/static/offline.png b/Outdated Website/static/offline.png
new file mode 100644
index 0000000..efdd7f8
Binary files /dev/null and b/Outdated Website/static/offline.png differ
diff --git a/Outdated Website/static/pfps/6789034fae4a5613afaea2e5.png b/Outdated Website/static/pfps/6789034fae4a5613afaea2e5.png
new file mode 100644
index 0000000..dc04285
Binary files /dev/null and b/Outdated Website/static/pfps/6789034fae4a5613afaea2e5.png differ
diff --git a/Outdated Website/static/pfps/6789307ab1ee3ef2a1433fb5.png b/Outdated Website/static/pfps/6789307ab1ee3ef2a1433fb5.png
new file mode 100644
index 0000000..f57df7b
Binary files /dev/null and b/Outdated Website/static/pfps/6789307ab1ee3ef2a1433fb5.png differ
diff --git a/Outdated Website/static/pfps/user.png b/Outdated Website/static/pfps/user.png
new file mode 100644
index 0000000..5fa00c9
Binary files /dev/null and b/Outdated Website/static/pfps/user.png differ
diff --git a/Outdated Website/static/red_cross.png b/Outdated Website/static/red_cross.png
new file mode 100644
index 0000000..a858999
Binary files /dev/null and b/Outdated Website/static/red_cross.png differ
diff --git a/Outdated Website/static/round_arrow.png b/Outdated Website/static/round_arrow.png
new file mode 100644
index 0000000..d71e6b2
Binary files /dev/null and b/Outdated Website/static/round_arrow.png differ
diff --git a/Outdated Website/static/style.css b/Outdated Website/static/style.css
new file mode 100644
index 0000000..33d5413
--- /dev/null
+++ b/Outdated Website/static/style.css
@@ -0,0 +1,234 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: Arial, sans-serif;
+ background-color: #f4f4f9;
+ color: #333;
+}
+
+.top-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #42028b;
+ color: white;
+ padding: 10px 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.logo img {
+ height: 40px;
+ vertical-align: middle;
+}
+
+.title {
+ font-size: 24px;
+ font-weight: bold;
+}
+
+.username {
+ display: flex;
+ align-items: center;
+ position: relative;
+}
+
+.username span {
+ margin-right: 10px;
+}
+
+.username img {
+ height: 30px;
+ width: 30px;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.username .dropdown {
+ display: none;
+ position: absolute;
+ top: 40px;
+ right: 0;
+ background-color: white;
+ border: 1px solid #ccc;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ border-radius: 5px;
+ z-index: 10;
+ opacity: 0;
+ transition: opacity 0.3s ease-out;
+}
+
+.username .dropdown a {
+ display: block;
+ padding: 10px 20px;
+ color: #333;
+ text-decoration: none;
+}
+
+.username .dropdown a:hover {
+ background-color: #f4f4f9;
+}
+
+.username:hover .dropdown {
+ display: block;
+ opacity: 1;
+}
+
+.username .dropdown.hide {
+ opacity: 0;
+ transition: opacity 0.3s ease-out 0.5s;
+}
+
+.settings-button, .logout-button, .black-button {
+ background-color: #007bff;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+ margin: 10px 0;
+ text-decoration: none;
+}
+
+.settings-button:hover, .logout-button:hover, .black-button:hover {
+ background-color: #0056b3;
+}
+
+.black-button {
+ background-color: #000;
+}
+
+.black-button:hover {
+ background-color: #333;
+}
+
+.content {
+ padding: 20px;
+}
+
+h1 {
+ font-size: 28px;
+}
+
+p {
+ font-size: 16px;
+ line-height: 1.5;
+}
+
+button {
+ background-color: #007bff;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 5px;
+ cursor: pointer;
+ font-size: 16px;
+}
+
+button:hover {
+ background-color: #0056b3;
+}
+
+input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 20px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+@media (max-width: 768px) {
+ .top-bar {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .username .dropdown {
+ right: auto;
+ left: 0;
+ }
+}
+
+#popup {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.7);
+ z-index: 1000;
+ justify-content: center;
+ align-items: center;
+}
+
+#popup .popup-content {
+ background: white;
+ padding: 20px;
+ border-radius: 8px;
+ width: 80%;
+ max-width: 600px;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
+ text-align: center;
+ position: relative;
+}
+
+#popup .popup-header {
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+#popup .popup-close {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ cursor: pointer;
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.bar-container {
+ display: flex;
+ justify-content: space-around;
+ margin-top: 30px;
+ height: 300px;
+ align-items: flex-end;
+ position: relative;
+}
+
+.bar {
+ width: 30%;
+ background-color: #007bff;
+ transition: height 2s ease-out;
+ position: relative;
+}
+
+.bar span {
+ position: absolute;
+ bottom: -30px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 16px;
+ font-weight: bold;
+ color: #333;
+}
+
+#criteria-text {
+ margin-bottom: 20px;
+ font-size: 18px;
+ color: #333;
+ text-align: left;
+}
+
+@media (max-width: 768px) {
+ #popup .popup-content {
+ width: 90%;
+ padding: 15px;
+ }
+
+ .bar-container {
+ flex-direction: column;
+ height: auto;
+ }
+}
diff --git a/Outdated Website/static/styles.css b/Outdated Website/static/styles.css
new file mode 100644
index 0000000..4c94b3d
--- /dev/null
+++ b/Outdated Website/static/styles.css
@@ -0,0 +1,211 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background-color: #0D1117;
+ color: #c9d1d9;
+ line-height: 1.6;
+}
+
+header {
+ text-align: center;
+ padding: 20px;
+ background-color: #161b22;
+ border-bottom: 2px solid #30363d;
+}
+
+h1, h2, h3 {
+ color: #58a6ff;
+}
+
+main {
+ max-width: 1200px;
+ margin: 40px auto;
+ padding: 20px;
+}
+
+section {
+ margin-bottom: 40px;
+}
+
+h2, h3 {
+ color: #c9d1d9;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ margin-top: 20px;
+}
+
+th, td {
+ padding: 12px;
+ text-align: left;
+ border-bottom: 1px solid #30363d;
+}
+
+th {
+ background-color: #161b22;
+}
+
+a {
+ color: #58a6ff;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+button {
+ background-color: #238636;
+ color: #fff;
+ border: none;
+ padding: 10px 20px;
+ border-radius: 8px;
+ cursor: pointer;
+}
+
+button:disabled {
+ background-color: #30363d;
+ cursor: not-allowed;
+}
+
+button:hover:enabled {
+ background-color: #2ea043;
+}
+
+.upload-btn {
+ background-color: #238636;
+ color: #fff;
+ padding: 10px 20px;
+ border-radius: 8px;
+ display: inline-block;
+ margin-top: 20px;
+}
+
+.upload-btn:hover {
+ background-color: #2ea043;
+}
+
+/* Custom Datei-Upload */
+.custom-file-upload {
+ background-color: #21262d;
+ border: 2px dashed #58a6ff;
+ padding: 20px;
+ text-align: center;
+ border-radius: 8px;
+ cursor: pointer;
+ margin-bottom: 20px;
+}
+
+.custom-file-upload label {
+ cursor: pointer;
+ color: #58a6ff;
+ font-weight: bold;
+}
+
+#fileNameDisplay {
+ display: block;
+ margin-top: 10px;
+ color: #c9d1d9;
+}
+
+form {
+ background-color: #161b22;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ max-width: 600px;
+ margin: 20px auto;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+label {
+ color: #c9d1d9;
+ font-size: 16px;
+ display: block;
+ margin-bottom: 8px;
+}
+
+input[type="text"], input[type="file"] {
+ width: 100%;
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid #30363d;
+ background-color: #0d1117;
+ color: #c9d1d9;
+}
+
+input[type="text"]:focus, input[type="file"]:focus {
+ outline: none;
+ border-color: #58a6ff;
+}
+
+.upload-icon {
+ width: 24px;
+ height: 24px;
+ vertical-align: middle;
+ margin-left: 10px;
+}
+
+footer {
+ text-align: center;
+ margin-top: 40px;
+}
+
+footer a {
+ color: #58a6ff;
+ text-decoration: none;
+}
+
+footer a:hover {
+ text-decoration: underline;
+}
+
+.btn {
+ display: inline-block;
+ background-color: #238636;
+ color: #ffffff;
+ border: none;
+ padding: 8px 20px;
+ border-radius: 6px;
+ text-align: center;
+ cursor: pointer;
+ text-decoration: none;
+ font-weight: bold;
+ transition: background 0.3s ease;
+}
+
+.btn:hover {
+ background-color: #2ea043;
+}
+
+.btn-danger {
+ background-color: #da3633;
+}
+
+.btn-danger:hover {
+ background-color: #f85149;
+}
+
+.btn-primary {
+ background-color: #0d1117;
+ border: 1px solid #30363d;
+}
+
+.btn-primary:hover {
+ background-color: #161b22;
+}
+
+.inline-form {
+ display: inline;
+ margin-left: 5px;
+}
diff --git a/Outdated Website/static/upload.png b/Outdated Website/static/upload.png
new file mode 100644
index 0000000..b8a240e
Binary files /dev/null and b/Outdated Website/static/upload.png differ
diff --git a/Outdated Website/static/user.png b/Outdated Website/static/user.png
new file mode 100644
index 0000000..5fa00c9
Binary files /dev/null and b/Outdated Website/static/user.png differ
diff --git a/Outdated Website/static/user_small.png b/Outdated Website/static/user_small.png
new file mode 100644
index 0000000..5fa00c9
Binary files /dev/null and b/Outdated Website/static/user_small.png differ
diff --git a/Outdated Website/templates/add_changelog.html b/Outdated Website/templates/add_changelog.html
new file mode 100644
index 0000000..3808c43
--- /dev/null
+++ b/Outdated Website/templates/add_changelog.html
@@ -0,0 +1,18 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Add Changelog{% endblock %}
+
+{% block content %}
+
+
Add a New Changelog
+
+
+{% endblock %}
diff --git a/Outdated Website/templates/appointment_scheduler.html b/Outdated Website/templates/appointment_scheduler.html
new file mode 100644
index 0000000..32dac9e
--- /dev/null
+++ b/Outdated Website/templates/appointment_scheduler.html
@@ -0,0 +1,79 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Termin Planer{% endblock %}
+
+{% block content %}
+
+
+
+
Neuen Termin hinzufügen
+
+
+
+
+{% endblock %}
diff --git a/Outdated Website/templates/appointments.html b/Outdated Website/templates/appointments.html
new file mode 100644
index 0000000..6fb03e3
--- /dev/null
+++ b/Outdated Website/templates/appointments.html
@@ -0,0 +1,159 @@
+{% extends "base.html" if session["theme"] == "lightmode" else "base_dark.html" %}
+
+{% block title %}Termin Planer{% endblock %}
+
+{% block content %}
+
+
+
📅 Termin Planer
+
+
+
+
+
+
+
+
+
+
+
+
📋 Anstehende Termine
+ {% if appointments %}
+
+ {% for appointment in appointments %}
+ -
+ {{ appointment.title }}
+ {{ appointment.start_date }} - {{ appointment.end_date }}
+ {{ appointment.description }}
+
+ {% endfor %}
+
+ {% else %}
+
Keine anstehenden Termine.
+ {% endif %}
+
+
+
+<
+
+
+
+
+
+
+{% endblock %}
diff --git a/Outdated Website/templates/base.html b/Outdated Website/templates/base.html
new file mode 100644
index 0000000..3cef21d
--- /dev/null
+++ b/Outdated Website/templates/base.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+ {% block title %}Moonlab{% endblock %}
+
+
+
+
+
+{% include "top_bar.html" %}
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Outdated Website/templates/base_dark.html b/Outdated Website/templates/base_dark.html
new file mode 100644
index 0000000..c009d71
--- /dev/null
+++ b/Outdated Website/templates/base_dark.html
@@ -0,0 +1,288 @@
+
+
+
+
+
+ {% block title %}Moonlab{% endblock %}
+
+
+
+
+
+
+
+{% include "top_bar.html" %}
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Outdated Website/templates/bot_logs.html b/Outdated Website/templates/bot_logs.html
new file mode 100644
index 0000000..995f8d7
--- /dev/null
+++ b/Outdated Website/templates/bot_logs.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Bot Logs
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Logs für {{ bot_name }}
+ {% for log in logs %}
+
{{ log }}
+ {% endfor %}
+
Zurück
+
+
+
diff --git a/Outdated Website/templates/create_license_key.html b/Outdated Website/templates/create_license_key.html
new file mode 100644
index 0000000..1cb5dca
--- /dev/null
+++ b/Outdated Website/templates/create_license_key.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+ License Key erstellen
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
License Key erstellen
+ {% if license_key %}
+
+
+
+
+
+

+ License key wurde kopiert
+
+ {% elif error %}
+
{{ error }}
+ {% endif %}
+
+
Zurück
+
+
+
+
diff --git a/Outdated Website/templates/create_project.html b/Outdated Website/templates/create_project.html
new file mode 100644
index 0000000..6080a4f
--- /dev/null
+++ b/Outdated Website/templates/create_project.html
@@ -0,0 +1,64 @@
+
+
+
+
+ Neues Projekt erstellen
+
+
+
+
+
Neues Projekt erstellen
+
+
+
+
diff --git a/Outdated Website/templates/dashboard_dark.html b/Outdated Website/templates/dashboard_dark.html
new file mode 100644
index 0000000..dad6460
--- /dev/null
+++ b/Outdated Website/templates/dashboard_dark.html
@@ -0,0 +1,123 @@
+{% extends "base_dark.html" %}
+
+{% block title %}Dashboard (Dark Mode){% endblock %}
+
+{% block content %}
+
+
+
+
+
+
Administration
+ {% if 'user_management' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'delete_requests' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'logs' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'changelogs' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
Private Tools
+ {% if 'discord_bots' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'appointments' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'view_license_keys' in session['permissions'] %}
+
+ {% endif %}
+
+
+
Moonlab Tools
+ {% if 'intranet' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'create_event_feedback' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'view_event_feedback' in session['permissions'] %}
+
+ {% endif %}
+ {% if "verify_age" in session["permissions"] %}
+
+ {% endif %}
+ {% if 'events' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'ip_checker' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/Outdated Website/templates/dashboard_light.html b/Outdated Website/templates/dashboard_light.html
new file mode 100644
index 0000000..f20be3d
--- /dev/null
+++ b/Outdated Website/templates/dashboard_light.html
@@ -0,0 +1,124 @@
+{% extends "base.html" %}
+
+{% block title %}Dashboard{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
Administration
+ {% if 'user_management' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'delete_requests' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'logs' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'changelogs' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
+
Private Tools
+ {% if 'discord_bots' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'appointments' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'view_license_keys' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
Moonlab Tools
+ {% if 'intranet' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'create_event_feedback' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'view_event_feedback' in session['permissions'] %}
+
+ {% endif %}
+ {% if "verify_age" in session["permissions"] %}
+
+ {% endif %}
+ {% if 'events' in session['permissions'] %}
+
+ {% endif %}
+ {% if 'ip_checker' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/Outdated Website/templates/dc_bots.html b/Outdated Website/templates/dc_bots.html
new file mode 100644
index 0000000..75084ff
--- /dev/null
+++ b/Outdated Website/templates/dc_bots.html
@@ -0,0 +1,527 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Discord Bots{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
+
+
+
+
+
+
+
+
+
Discord Bots
+
+
+ {% for bot in bots %}
+
+
{{ bot.name }}
+

+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+{% else %}
+
+
+
+
+
+
+
+
+
+
+
+
+
Discord Bots
+
+
+ {% for bot in bots %}
+
+
{{ bot.name }}
+

+
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/delete_request.html b/Outdated Website/templates/delete_request.html
new file mode 100644
index 0000000..118c0a6
--- /dev/null
+++ b/Outdated Website/templates/delete_request.html
@@ -0,0 +1,237 @@
+ {% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Lösch Anfragen{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
Lösch Anfragen
+ {% if delete_requests|length == 0 %}
+
Zurzeit keine aktive Lösch-Anfragen
+ {% else %}
+
+
+ | Lizenzschlüssel |
+ Benutzername |
+ Aktionen |
+
+ {% for request in delete_requests %}
+
+ | {{ request['license_key'] }} |
+ {{ request['username'] }} |
+
+
+
+ |
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+{% else %}
+
+
+
+
Lösch Anfragen
+ {% if delete_requests|length == 0 %}
+
Zurzeit keine aktive Lösch-Anfragen
+ {% else %}
+
+
+ | Lizenzschlüssel |
+ Benutzername |
+ Aktionen |
+
+ {% for request in delete_requests %}
+
+ | {{ request['license_key'] }} |
+ {{ request['username'] }} |
+
+
+
+ |
+
+ {% endfor %}
+
+ {% endif %}
+
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/edit_user.html b/Outdated Website/templates/edit_user.html
new file mode 100644
index 0000000..3590bbf
--- /dev/null
+++ b/Outdated Website/templates/edit_user.html
@@ -0,0 +1,162 @@
+{% if session['theme'] == 'darkmode' %}
+{% extends "base_dark.html" %}
+{% else %}
+{% extends "base.html" %}
+{% endif %}
+
+{% block title %}Benutzer bearbeiten{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Outdated Website/templates/event_feedback.html b/Outdated Website/templates/event_feedback.html
new file mode 100644
index 0000000..0ae218b
--- /dev/null
+++ b/Outdated Website/templates/event_feedback.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+ Event Feedback
+
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Event Feedback
+
+
+
+
+
diff --git a/Outdated Website/templates/events.html b/Outdated Website/templates/events.html
new file mode 100644
index 0000000..449e8d8
--- /dev/null
+++ b/Outdated Website/templates/events.html
@@ -0,0 +1,332 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Events{% endblock %}
+
+{% block content %}
+
+{% if session['theme'] == 'darkmode' %}
+
+{% else %}
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/get_data.html b/Outdated Website/templates/get_data.html
new file mode 100644
index 0000000..a791f69
--- /dev/null
+++ b/Outdated Website/templates/get_data.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+ Daten anzeigen
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Daten aus der MongoDB
+
+
+
+
+
+

+ Der License Key wurde nicht gefunden
+
+ {% for doc in data %}
+
+ {% endfor %}
+
Zurück
+
+
+
+
diff --git a/Outdated Website/templates/ip_checker.html b/Outdated Website/templates/ip_checker.html
new file mode 100644
index 0000000..ea3839d
--- /dev/null
+++ b/Outdated Website/templates/ip_checker.html
@@ -0,0 +1,100 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}IP Checker{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/Outdated Website/templates/license_keys.html b/Outdated Website/templates/license_keys.html
new file mode 100644
index 0000000..0712e7a
--- /dev/null
+++ b/Outdated Website/templates/license_keys.html
@@ -0,0 +1,148 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}License Keys{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
License Keys
+
+ {% if 'create_license_key' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
+
+{% else %}
+
+
+
+
License Keys
+
+ {% if 'create_license_key' in session['permissions'] %}
+
+ {% endif %}
+
+
+
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/login.html b/Outdated Website/templates/login.html
new file mode 100644
index 0000000..6a97392
--- /dev/null
+++ b/Outdated Website/templates/login.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
Login
+
+ {% if error %}
+
+

+
Du hast ein falsches Passwort bzw einen falschen Benutzernamen eingegeben!
+
+ {% endif %}
+
+
+
diff --git a/Outdated Website/templates/logs.html b/Outdated Website/templates/logs.html
new file mode 100644
index 0000000..4de578c
--- /dev/null
+++ b/Outdated Website/templates/logs.html
@@ -0,0 +1,120 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Logs{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
+
Logs
+ {% for log in logs %}
+
+
Datum: {{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
+
Nachricht: {{ log.message }}
+
+ {% endfor %}
+
+
+{% else %}
+
+
+
+
+
Logs
+ {% for log in logs %}
+
+
Datum: {{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
+
Nachricht: {{ log.message }}
+
+ {% endfor %}
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/robots.txt b/Outdated Website/templates/robots.txt
new file mode 100644
index 0000000..2c80a29
--- /dev/null
+++ b/Outdated Website/templates/robots.txt
@@ -0,0 +1,29 @@
+User-agent: MSNBot
+Disallow: /
+User-agent: Slurp
+Disallow: /
+User-agent: Teoma
+Disallow: /
+User-agent: Gigabot
+Disallow: /
+User-agent: Robozilla
+Disallow: /
+User-agent: Nutch
+Disallow: /
+User-agent: ia_archiver
+Disallow: /
+User-agent: baiduspider
+Disallow: /
+User-agent: naverbot
+Disallow: /
+User-agent: yeti
+Disallow: /
+User-agent: yahoo-mmcrawler
+Disallow: /
+User-agent: psbot
+Disallow: /
+User-agent: yahoo-blogs/v3.9
+Disallow: /
+User-agent: *
+Disallow:
+Disallow: /static
\ No newline at end of file
diff --git a/Outdated Website/templates/test.css b/Outdated Website/templates/test.css
new file mode 100644
index 0000000..6856494
--- /dev/null
+++ b/Outdated Website/templates/test.css
@@ -0,0 +1,91 @@
+:root {
+ --clr-one: rgb(255, 20, 147);
+ --clr-two: rgb(127, 255, 0);
+ --clr-three: rgb(138, 43, 226);
+ --clr-bg: #1d1e22;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+}
+
+body {
+ min-height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ background: var(--clr-bg);
+ font-family: "Balsamiq Sans", cursive;
+ padding: 2rem;
+}
+
+h1, p {
+ color: white;
+ text-align: center;
+ margin-bottom: 1rem;
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ justify-content: center;
+}
+
+.neon-button {
+ font-size: 1rem;
+ text-decoration: none;
+ padding: 0.75em 1.5em;
+ border-radius: 0.25em;
+ position: relative;
+ transition: 0.3s ease;
+ background: transparent;
+ color: inherit;
+ font-weight: bold;
+ text-shadow: 0 0 0.5em currentColor;
+ border: 2px solid currentColor;
+}
+
+/* Farben */
+.neon-button__1 {
+ color: var(--clr-one);
+ box-shadow: 0 0 0.6em var(--clr-one);
+}
+.neon-button__2 {
+ color: var(--clr-two);
+ box-shadow: 0 0 0.6em var(--clr-two);
+}
+.neon-button__3 {
+ color: var(--clr-three);
+ box-shadow: 0 0 0.6em var(--clr-three);
+}
+
+.neon-button:hover,
+.neon-button:focus {
+ background-color: currentColor;
+ color: #000;
+ text-shadow: none;
+ box-shadow:
+ 0 0 0.7em currentColor,
+ 0 0 1.5em currentColor,
+ inset 0 0 1em currentColor;
+ transform: scale(1.05);
+}
+
+.neon-button:active {
+ animation: bounce 0.4s ease;
+}
+
+@keyframes bounce {
+ 0% { transform: translateY(0); }
+ 50% { transform: translateY(-8px); }
+ 100% { transform: translateY(0); }
+}
diff --git a/Outdated Website/templates/top_bar.html b/Outdated Website/templates/top_bar.html
new file mode 100644
index 0000000..efcec64
--- /dev/null
+++ b/Outdated Website/templates/top_bar.html
@@ -0,0 +1,78 @@
+
+
+

+
+
MrSniff's Admin-Tools Dashboard
+
+
+ {{ session['username'] }}
+
+
+
 }})
+
+
+
+
+
\ No newline at end of file
diff --git a/Outdated Website/templates/ui.html b/Outdated Website/templates/ui.html
new file mode 100644
index 0000000..c8cd0b5
--- /dev/null
+++ b/Outdated Website/templates/ui.html
@@ -0,0 +1,178 @@
+
+
+
+
+
+ ScytheNet
+
+
+
+
+
+
+
Willkommen bei ScytheNet
+
Wählen Sie eine Option aus der Seitenleiste
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Outdated Website/templates/user_management.html b/Outdated Website/templates/user_management.html
new file mode 100644
index 0000000..b07aff5
--- /dev/null
+++ b/Outdated Website/templates/user_management.html
@@ -0,0 +1,490 @@
+
+
+
+
+
+ {% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+ {% else %}
+ {% extends "base.html" %}
+ {% endif %}
+
+ {% block title %}User Management{% endblock %}
+
+ {% block content %}
+
+ {% endblock %}
+
+
diff --git a/Outdated Website/templates/verify_age.html b/Outdated Website/templates/verify_age.html
new file mode 100644
index 0000000..ecf1720
--- /dev/null
+++ b/Outdated Website/templates/verify_age.html
@@ -0,0 +1,285 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Altersüberprüfung{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
+
+
Benutzer suchen
+
+
+ {% for user in users %}
+
+ {% endfor %}
+
+
+
+
+
+{% else %}
+
+
+
+
+ Altersüberprüfung
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
+
+
Benutzer suchen
+
+
+ {% for user in users %}
+
+ {% endfor %}
+
+
+
+
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/view_changelogs.html b/Outdated Website/templates/view_changelogs.html
new file mode 100644
index 0000000..c8ea7cb
--- /dev/null
+++ b/Outdated Website/templates/view_changelogs.html
@@ -0,0 +1,169 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Changelogs{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
+
+
Website Changelogs
+ {% for changelog in changelogs %}
+
+
{{ changelog.timestamp }}
+
{{ changelog.text }}
+
+ {% endfor %}
+
+
+{% else %}
+
+
+
+
+
Website Changelogs
+ {% for changelog in changelogs %}
+
+
{{ changelog.timestamp }}
+
{{ changelog.text }}
+
+ {% endfor %}
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/view_event_feedback.html b/Outdated Website/templates/view_event_feedback.html
new file mode 100644
index 0000000..2ec7a2c
--- /dev/null
+++ b/Outdated Website/templates/view_event_feedback.html
@@ -0,0 +1,82 @@
+
+
+
+
+ Event Feedback
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Event Feedback
+
Event: {{ feedback.event_name }}
+
Verlauf des Events: {{ feedback.event_progress }}
+
Ende des Events: {{ feedback.event_end }}
+
Feedback der Community: {{ feedback.community_feedback }}
+
Abgegeben von: {{ feedback.username }}
+
Datum: {{ feedback.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
+
+
+
+
diff --git a/Outdated Website/templates/view_event_feedbacks.html b/Outdated Website/templates/view_event_feedbacks.html
new file mode 100644
index 0000000..f4988d7
--- /dev/null
+++ b/Outdated Website/templates/view_event_feedbacks.html
@@ -0,0 +1,80 @@
+
+
+
+
+
+ Event Feedbacks
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Event Feedbacks
+ {% for feedback in feedbacks %}
+
+
{{ feedback.event_name }}
+
Verlauf des Events: {{ feedback.event_progress }}
+
Ende des Events: {{ feedback.event_end }}
+
Feedback der Community: {{ feedback.community_feedback }}
+
Verbesserungsvorschläge: {{ feedback.improvement_suggestions }}
+
Abgegeben von: {{ feedback.username }} am {{ feedback.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}
+
+ {% endfor %}
+
+
+
+
diff --git a/Outdated Website/templates/view_license.html b/Outdated Website/templates/view_license.html
new file mode 100644
index 0000000..15ef8e0
--- /dev/null
+++ b/Outdated Website/templates/view_license.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+ Lizenzdaten anzeigen
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Lizenzdaten für {{ license_data['ingame_name'] }}
+
Lizenzschlüssel: {{ license_data['license_key'] }}
+
Status: {{ license_data['status'] }}
+
Name: {{ license_data['ingame_name'] }}
+
Gefundene Ordner:
+
+ {% for folder in license_data["found_folders"] %}
+ - {{ folder }}
+ {% endfor %}
+
+ {% if 'delete_requests' in session['permissions'] %}
+
+ {% else %}
+
+ {% endif %}
+
Zurück
+
+
+
diff --git a/Outdated Website/templates/view_user_age.html b/Outdated Website/templates/view_user_age.html
new file mode 100644
index 0000000..994e472
--- /dev/null
+++ b/Outdated Website/templates/view_user_age.html
@@ -0,0 +1,118 @@
+{% if session['theme'] == 'darkmode' %}
+ {% extends "base_dark.html" %}
+{% else %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% block title %}Benutzerdetails{% endblock %}
+
+{% block content %}
+{% if session['theme'] == 'darkmode' %}
+
+
+
Benutzerdetails
+
Ingame Name: {{ user.ingame_name }}
+
SteamID: {{ user.steam_id }}
+
Geburtsdatum: {{ user.birthdate }}
+
Alter: {{ user.age }} Jahre
+
Zurück
+
+{% else %}
+
+
+
+
+ Benutzerdetails
+
+
+
+
+
+ {% include "top_bar.html" %}
+
+
Benutzerdetails
+
Ingame Name: {{ user.ingame_name }}
+
SteamID: {{ user.steam_id }}
+
Geburtsdatum: {{ user.birthdate }}
+
Alter: {{ user.age }} Jahre
+
Zurück
+
+
+
+{% endif %}
+{% endblock %}
diff --git a/Outdated Website/templates/wallpaper.html b/Outdated Website/templates/wallpaper.html
new file mode 100644
index 0000000..834d992
--- /dev/null
+++ b/Outdated Website/templates/wallpaper.html
@@ -0,0 +1,377 @@
+
+
+
+
+
+ SCP:SL Server Status
+
+
+
+
+
+
+
+
+
+
Lade Servernamen...
+
Lade Spieleranzahl...
+
+
+
+

+
+
+
+
+
+
\ No newline at end of file
diff --git a/Outdated Website/website.py b/Outdated Website/website.py
new file mode 100644
index 0000000..839a2f5
--- /dev/null
+++ b/Outdated Website/website.py
@@ -0,0 +1,1535 @@
+from PIL import Image
+import matplotlib
+matplotlib.use('Agg')
+import matplotlib.pyplot as plt
+from io import BytesIO
+import matplotlib.dates as mdates
+from flask import Flask, request, render_template, redirect, url_for, session, jsonify, Response, send_file
+from flask_socketio import SocketIO, join_room, leave_room, emit
+from pymongo import MongoClient
+from bson.objectid import ObjectId
+import uuid
+import datetime
+from flask_wtf.csrf import CSRFProtect
+from werkzeug.security import generate_password_hash, check_password_hash
+import subprocess
+import signal
+import threading
+import psutil
+import time
+import json
+import requests
+import os
+from datetime import datetime, timedelta
+
+app = Flask(__name__, static_folder='/var/www/admin_website/static', template_folder='/var/www/admin_website/templates')
+app.secret_key = os.urandom(24)
+socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")
+
+csrf = CSRFProtect(app)
+
+client = MongoClient("mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/")
+MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
+db = client["moonlab"]
+collection = db["anti_cheat"]
+accounts_collection = db["website_accounts"]
+delete_requests_collection = db["delete_requests"]
+logs_collection = db["logs"]
+user_ages_collection = db["user_ages"]
+projects_collection = db["projects"]
+changelogs_collection = db["changelogs"]
+appointments_collection = db["appointments"]
+scpsl_playerdata_collection = db["scpsl-playerdata"]
+
+ETS2_DB_NAME = "ets2_license_db"
+ETS2_COLLECTION_NAME = "licenses"
+ets2_db = client[ETS2_DB_NAME]
+ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]
+
+SCP_SL_API_URL = "https://api.scplist.kr/api/servers/85734"
+
+app.config.update(
+ SESSION_COOKIE_HTTPONLY=True,
+ SESSION_COOKIE_SECURE=True,
+ SESSION_COOKIE_SAMESITE='Lax',
+ REMEMBER_COOKIE_HTTPONLY=True,
+ REMEMBER_COOKIE_SECURE=True,
+ REMEMBER_COOKIE_SAMESITE='Lax'
+)
+
+if accounts_collection.count_documents({}) == 0:
+ accounts_collection.insert_one({
+ "username": "Admin",
+ "display_name": "Admin",
+ "password": generate_password_hash("1812"),
+ "permissions": ["user_management", "permission_management", "delete_requests", "logs", "create_license_key",
+ "view_license_keys", "create_event_feedback", "view_event_feedback", "verify_age",
+ "project_manager", "ip_checker", "changelogs"]
+ })
+
+
+def generate_license_key():
+ return str(uuid.uuid4())
+
+
+def add_license_key_to_db(license_key):
+ if collection.find_one({"license_key": license_key}):
+ return False
+ else:
+ collection.insert_one({"license_key": license_key, "status": "unused"})
+ return True
+
+
+def add_log(message):
+ logs_collection.insert_one({"timestamp": datetime.now(), "message": message})
+
+
+def stream_logs(process):
+ def generate():
+ for line in iter(process.stdout.readline, b''):
+ yield line.decode('utf-8')
+
+ return Response(generate(), mimetype='text/plain')
+
+
+def load_bot_config():
+ try:
+ with open("bot_config.json", "r") as file:
+ return json.load(file)
+ except FileNotFoundError:
+ return {}
+
+
+def save_bot_config(config):
+ with open("bot_config.json", "w") as file:
+ json.dump(config, file, indent=4)
+
+
+bot_config = load_bot_config()
+
+
+@app.route("/website.py", methods=["GET"])
+def deny_access():
+ return "Access denied", 403
+
+
+@app.route("/", methods=["GET", "POST"])
+def login():
+ error = None
+ if request.method == "POST":
+ username = request.form["username"]
+ password = request.form["password"]
+ remember_me = "remember_me" in request.form
+
+ user = accounts_collection.find_one({"username": username})
+ if user and check_password_hash(user["password"], password):
+ session["user_id"] = str(user["_id"])
+ session["username"] = user["username"]
+ session["display_name"] = user.get("display_name", user["username"])
+ session["permissions"] = user.get("permissions", [])
+ add_log(f"{username} hat sich eingeloggt")
+
+ if remember_me:
+ session.permanent = True
+
+ return redirect(url_for("dashboard"))
+ else:
+ error = "Du hast ein(en) falsches Passwort/Benutzernamen eingegeben"
+ return render_template("login.html", error=error)
+
+
+@app.route("/dashboard")
+def dashboard():
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
+ if not user:
+ return redirect(url_for("login"))
+
+ session["permissions"] = user.get("permissions", [])
+
+ if "Style" not in user:
+ accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": "lightmode"}})
+ session["theme"] = "lightmode"
+ else:
+ session["theme"] = user["Style"]
+
+ if session["theme"] == "darkmode":
+ return render_template("dashboard_dark.html")
+ else:
+ return render_template("dashboard_light.html")
+
+
+@app.route('/view_changelogs', methods=['GET'])
+def view_changelogs():
+ if "username" not in session:
+ return redirect(url_for("login"))
+ changelogs = [
+ {"text": changelog["text"], "timestamp": changelog["timestamp"]}
+ for changelog in changelogs_collection.find().sort("timestamp", -1)
+ ]
+ return render_template("view_changelogs.html", changelogs=changelogs)
+
+
+@app.route("/user_management", methods=["GET", "POST"])
+def user_management():
+ if "username" not in session or "user_management" not in session["permissions"]:
+ return redirect(url_for("login"))
+ users = accounts_collection.find()
+ return render_template("user_management.html", users=users)
+
+
+@app.route("/add_user", methods=["POST"])
+def add_user():
+ if "username" not in session or "user_management" not in session["permissions"]:
+ return redirect(url_for("login"))
+ new_username = request.form["username"]
+ new_display_name = request.form["display_name"]
+ new_password = request.form["password"]
+ new_permissions = request.form.getlist("permissions")
+
+ if accounts_collection.find_one({"username": new_username}):
+ return "Benutzername existiert bereits", 400
+
+ accounts_collection.insert_one({
+ "username": new_username,
+ "display_name": new_display_name,
+ "password": generate_password_hash(new_password),
+ "permissions": new_permissions
+ })
+ add_log(f"{session['username']} created or modified user {new_username}")
+ return redirect(url_for("user_management"))
+
+
+@app.route("/change_permissions/", methods=["POST"])
+def change_permissions(user_id):
+ if "username" not in session or "permission_management" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ user = accounts_collection.find_one({"_id": ObjectId(user_id)})
+ if not user:
+ return "User not found", 404
+
+ if "remove_permission" in request.form:
+ permission_to_remove = request.form["remove_permission"]
+ accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$pull": {"permissions": permission_to_remove}})
+ add_log(f"{session['username']} removed permission {permission_to_remove} from user {user['username']}")
+
+ if "add_permission" in request.form:
+ permission_to_add = request.form["add_permission"]
+ accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$addToSet": {"permissions": permission_to_add}})
+ add_log(f"{session['username']} added permission {permission_to_add} to user {user['username']}")
+
+ if str(user["_id"]) == session.get("user_id"):
+ updated_user = accounts_collection.find_one({"_id": ObjectId(user_id)})
+ session["permissions"] = updated_user.get("permissions", [])
+
+ return redirect(url_for("user_management"))
+
+
+@app.route("/delete_user/", methods=["POST"])
+def delete_user(user_id):
+ if "username" not in session or "user_management" not in session["permissions"]:
+ return redirect(url_for("login"))
+ user = accounts_collection.find_one({"_id": ObjectId(user_id)})
+ if user:
+ accounts_collection.delete_one({"_id": ObjectId(user_id)})
+ add_log(f"{session['username']} deleted user {user['username']}")
+ return redirect(url_for("user_management"))
+
+
+UPLOAD_FOLDER = "/var/www/admin_website/static/pfps"
+ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg"}
+
+
+def allowed_file(filename):
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
+
+
+@app.route("/edit_user/", methods=["GET", "POST"])
+def edit_user(user_id):
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ user = accounts_collection.find_one({"_id": ObjectId(user_id)})
+
+ if "permission_management" not in session["permissions"] and session["user_id"] != str(user["_id"]):
+ return "Access denied", 403
+
+ if request.method == "POST":
+ new_display_name = request.form.get("display_name")
+ new_password = request.form.get("password")
+ profile_pic = request.files.get("pfp")
+
+ update_data = {"display_name": new_display_name}
+
+ pfp_uploaded = False
+ pfp_url = None
+
+ if new_password:
+ update_data["password"] = generate_password_hash(new_password, method='scrypt')
+
+ if profile_pic and profile_pic.filename != "":
+ ext = profile_pic.filename.rsplit('.', 1)[-1].lower()
+
+ if ext in ["png", "jpg", "jpeg"]:
+ filename = f"{user_id}.png"
+ upload_folder = "/var/www/admin_website/static/pfps"
+ os.makedirs(upload_folder, exist_ok=True)
+ save_path = os.path.join(upload_folder, filename)
+ try:
+ img = Image.open(profile_pic.stream)
+ if img.mode == 'RGBA' or (img.mode == 'P' and 'transparency' in img.info):
+ img.save(save_path, "PNG")
+ else:
+ img = img.convert("RGB")
+ img.save(save_path, "PNG")
+ website_data = user.get("website_accounts", {})
+ website_data["pfp_last_updated"] = int(time.time())
+ website_data["pfp_filename"] = filename
+ update_data["website_accounts"] = website_data
+ pfp_uploaded = True
+ pfp_url = f"/static/pfps/{filename}"
+ except Exception as e:
+ print(f"Fehler beim Speichern oder Konvertieren des Profilbildes: {e}")
+ else:
+ print(f"Unerlaubter Dateityp für Profilbild: {ext}")
+
+ accounts_collection.update_one({"_id": ObjectId(user_id)}, {"$set": update_data})
+
+ if user_id == session.get("user_id"):
+ session["display_name"] = new_display_name
+ if pfp_uploaded:
+ session["pfp_last_updated"] = update_data["website_accounts"]["pfp_last_updated"]
+
+ if pfp_uploaded:
+ return jsonify({"success": True, "message": "Profilbild erfolgreich aktualisiert!", "pfp_url": pfp_url})
+ else:
+ return jsonify({"success": True, "message": "Profil erfolgreich aktualisiert!", "pfp_url": None})
+
+ return render_template("edit_user.html", user=user)
+
+
+@app.route('/get_data', methods=['GET'])
+def get_data():
+ if "username" not in session:
+ return redirect(url_for("login"))
+ data = collection.find()
+ result = []
+ for doc in data:
+ if 'ingame_name' in doc and doc['ingame_name']:
+ doc['_id'] = str(doc['_id'])
+ result.append(doc)
+ return render_template("get_data.html", data=result)
+
+
+@app.route('/license=', methods=['GET'])
+def view_license(license_key):
+ if "username" not in session:
+ return redirect(url_for("login"))
+ license_data = collection.find_one({"license_key": license_key})
+ if license_data:
+ license_data['_id'] = str(license_data['_id'])
+ return render_template("view_license.html", license_data=license_data)
+ else:
+ return "Lizenzschlüssel nicht gefunden", 404
+
+
+@app.route('/delete_license/', methods=['POST'])
+def delete_license(license_key):
+ if "username" not in session:
+ return redirect(url_for("login"))
+ collection.delete_one({"license_key": license_key})
+ add_log(f"{session['username']} hat Lizenzschlüssel {license_key} gelöscht")
+ return redirect(url_for("get_data"))
+
+
+@app.route('/request_delete_license/', methods=['POST'])
+def request_delete_license(license_key):
+ if "username" not in session:
+ return redirect(url_for("login"))
+ delete_requests_collection.insert_one({"license_key": license_key, "username": session["username"]})
+ add_log(f"{session['username']} hat eine Delete-Request für Lizenzschlüssel {license_key} gestellt")
+ return redirect(url_for("get_data"))
+
+
+@app.route('/delete_requests', methods=['GET'])
+def delete_requests():
+ if "username" not in session or "delete_requests" not in session["permissions"]:
+ return redirect(url_for("login"))
+ delete_requests = list(delete_requests_collection.find())
+ return render_template("delete_request.html", delete_requests=delete_requests)
+
+
+@app.route('/approve_delete_request/', methods=['POST'])
+def approve_delete_request(request_id):
+ if "username" not in session or "delete_requests" not in session["permissions"]:
+ return redirect(url_for("login"))
+ request = delete_requests_collection.find_one({"_id": ObjectId(request_id)})
+ if request:
+ collection.delete_one({"license_key": request["license_key"]})
+ delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
+ add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request['license_key']} genehmigt")
+ return redirect(url_for("delete_requests"))
+
+
+@app.route('/deny_delete_request/', methods=['POST'])
+def deny_delete_request(request_id):
+ if "username" not in session or "delete_requests" not in session["permissions"]:
+ return redirect(url_for("login"))
+ delete_requests_collection.delete_one({"_id": ObjectId(request_id)})
+ add_log(f"{session['username']} hat die Delete-Request für Lizenzschlüssel {request_id} abgelehnt")
+ return redirect(url_for("delete_requests"))
+
+
+@app.route('/add_data', methods=['POST'])
+def add_data():
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ new_data = request.get_json()
+
+ result = collection.insert_one(new_data)
+
+ return jsonify({"message": "Daten erfolgreich hinzugefügt", "inserted_id": str(result.inserted_id)})
+
+
+@app.route('/create_license_key', methods=['GET', 'POST'])
+def create_license_key():
+ if "username" not in session or "create_license_key" not in session["permissions"]:
+ return redirect(url_for("login"))
+ if request.method == "POST":
+ license_key = generate_license_key()
+ if add_license_key_to_db(license_key):
+ add_log(f"{session['username']} hat Lizenzschlüssel {license_key} erstellt")
+ return render_template("create_license_key.html", license_key=license_key)
+ else:
+ return render_template("create_license_key.html", error="Fehler beim Erstellen des Lizenzschlüssels")
+ return render_template("create_license_key.html")
+
+
+@app.route('/database_entries', methods=['GET'])
+def database_entries():
+ if "username" not in session:
+ return redirect(url_for("login"))
+ data = collection.find()
+ result = []
+ for doc in data:
+ doc['_id'] = str(doc['_id'])
+ result.append(doc)
+ return render_template("view_license.html", data=result)
+
+
+@app.route('/license_keys', methods=['GET'])
+def license_keys():
+ if "username" not in session:
+ return redirect(url_for("login"))
+ return render_template("license_keys.html")
+
+
+@app.route('/search_license_key', methods=['GET'])
+def search_license_key():
+ license_key = request.args.get('license_key')
+ license_data = collection.find_one({"license_key": license_key})
+ if license_data:
+ return jsonify({"found": True})
+ else:
+ return jsonify({"found": False})
+
+
+@app.route('/logs', methods=['GET'])
+def logs():
+ if "username" not in session or "logs" not in session["permissions"]:
+ return redirect(url_for("login"))
+ logs = list(logs_collection.find().sort("timestamp", -1))
+ return render_template("logs.html", logs=logs)
+
+
+@app.route('/event_feedback', methods=['GET', 'POST'])
+def event_feedback():
+ if "username" not in session or "create_event_feedback" not in session["permissions"]:
+ return redirect(url_for("login"))
+ if request.method == "POST":
+ event_name = request.form["event_name"]
+ if event_name == "Anderes":
+ event_name = request.form["other_event_name"]
+ event_progress = request.form["event_progress"]
+ if event_progress == "Anderes":
+ event_progress = request.form["other_event_progress"]
+ event_end = request.form["event_end"]
+ if event_end == "Anderes":
+ event_end = request.form["other_event_end"]
+ community_feedback = request.form["community_feedback"]
+ if community_feedback == "Anderes":
+ community_feedback = request.form["other_community_feedback"]
+ improvement_suggestions = request.form["improvement_suggestions"]
+ feedback_data = {
+ "event_name": event_name,
+ "event_progress": event_progress,
+ "event_end": event_end,
+ "community_feedback": community_feedback,
+ "improvement_suggestions": improvement_suggestions,
+ "timestamp": datetime.now(),
+ "username": session["username"]
+ }
+ db.event_feedback.insert_one(feedback_data)
+ add_log(f"{session['username']} hat Feedback für das Event {event_name} abgegeben")
+ return redirect(url_for("dashboard"))
+ return render_template("event_feedback.html")
+
+
+@app.route('/event=', methods=['GET'])
+def view_event_feedback(event_id):
+ if "username" not in session:
+ return redirect(url_for("login"))
+ feedback = db.event_feedback.find_one({"_id": ObjectId(event_id)})
+ if feedback:
+ feedback['_id'] = str(feedback['_id'])
+ return render_template("view_event_feedback.html", feedback=feedback)
+ else:
+ return "Event-Feedback nicht gefunden", 404
+
+
+@app.route('/view_event_feedbacks', methods=['GET'])
+def view_event_feedbacks():
+ if "username" not in session or "view_event_feedback" not in session["permissions"]:
+ return redirect(url_for("login"))
+ feedbacks = list(db.event_feedback.find().sort("timestamp", -1))
+ return render_template("view_event_feedbacks.html", feedbacks=feedbacks)
+
+
+@app.route("/events", methods=["GET", "POST"])
+@csrf.exempt
+def events():
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ if request.method == "POST":
+ event_name = request.form["event_name"]
+ community_feedback = request.form["community_feedback"]
+ players = int(request.form["players"])
+ time = request.form["time"]
+ weekday = request.form["weekday"]
+ top = 1 if community_feedback == "top" else 0
+ flop = 1 if community_feedback == "flop" else 0
+ etwas = 1 if community_feedback == "etwas" else 0
+ rating = top - flop + etwas
+
+ event = db.events.find_one({"name": event_name, "time": time, "weekday": weekday})
+ if event:
+ db.events.update_one(
+ {"_id": event["_id"]},
+ {"$inc": {"top": top, "flop": flop, "etwas": etwas, "rating": rating}}
+ )
+ add_log(f"{session['username']} updated event {event_name} with rating {rating}")
+ else:
+ db.events.insert_one({
+ "name": event_name,
+ "players": players,
+ "time": time,
+ "weekday": weekday,
+ "top": top,
+ "flop": flop,
+ "etwas": etwas,
+ "rating": rating,
+ "timestamp": datetime.now()
+ })
+ add_log(f"{session['username']} added event {event_name} with rating {rating}")
+
+ events = list(db.events.find().sort("timestamp", -1))
+ best_event = None
+ best_events = None
+ if request.args.get("calculate_best"):
+ current_players = int(request.args.get("current_players"))
+ current_time = request.args.get("current_time")
+ current_weekday = request.args.get("current_weekday")
+ event_type = request.args.get("event_type", None)
+ best_events = calculate_best_events(current_players, current_time, current_weekday, event_type)
+ if best_events:
+ best_event = best_events[0]
+
+ return render_template("events.html", events=events, best_event=best_event)
+
+
+def calculate_best_events(current_players, current_time, current_weekday, event_type=None):
+ events = list(db.events.find())
+ scored_events = []
+ for event in events:
+ similarity_score = 0
+ similarity_score -= abs(event['players'] - current_players) * 0.5
+ if event['weekday'] != current_weekday:
+ similarity_score -= 1
+ similarity_score -= abs(int(event['time'].split(':')[0]) - int(current_time.split(':')[0])) * 0.2
+ similarity_score += (event['rating'] * 1.0)
+ if event_type and event.get('type') == event_type:
+ similarity_score += 0.1
+ scored_events.append((similarity_score, event))
+ scored_events.sort(reverse=True, key=lambda x: x[0])
+ return [e for _, e in scored_events[:5]]
+
+
+@app.route("/dc_bots", methods=["GET"])
+def dc_bots():
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+ bots = list(db.dc_bots.find())
+ return render_template("dc_bots.html", bots=bots)
+
+
+@app.route("/add_dc_bot", methods=["POST"])
+def add_dc_bot():
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+ bot_name = request.form["bot_name"]
+ bot_directory = request.form["bot_directory"]
+ db.dc_bots.insert_one({"name": bot_name, "directory": bot_directory})
+ add_log(f"{session['username']} added bot {bot_name}")
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/upload_bot_version/", methods=["POST"])
+def upload_bot_version(bot_name):
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if not bot:
+ return f"Bot {bot_name} nicht gefunden", 404
+
+ if "bot_file" not in request.files:
+ return "Keine Datei hochgeladen", 400
+
+ bot_file = request.files["bot_file"]
+ if bot_file.filename == "":
+ return "Keine Datei ausgewählt", 400
+
+ version = bot.get("version", "1.0")
+ major, minor = map(int, version.split("."))
+ new_version = f"{major}.{minor + 1}"
+
+ base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
+ bot_directory_with_version = f"{base_directory_name}_v{new_version}"
+
+ os.makedirs(bot_directory_with_version, exist_ok=True)
+ bot_file.save(os.path.join(bot_directory_with_version, bot_file.filename))
+
+ db.dc_bots.update_one({"name": bot_name},
+ {"$set": {"directory": bot_directory_with_version, "version": new_version}})
+ add_log(f"{session['username']} uploaded new version {new_version} for bot {bot_name}")
+
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/revert_bot_version//", methods=["POST"])
+def revert_bot_version(bot_name, version):
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if not bot:
+ return f"Bot {bot_name} nicht gefunden", 404
+
+ base_directory_name = bot['directory'].rsplit('_v', 1)[0] if '_v' in bot['directory'] else bot['directory']
+ bot_directory = f"{base_directory_name}_v{version}"
+
+ if not os.path.exists(bot_directory):
+ return f"Version {version} für Bot {bot_name} nicht gefunden", 404
+
+ db.dc_bots.update_one({"name": bot_name}, {"$set": {"directory": bot_directory, "version": version}})
+ add_log(f"{session['username']} reverted bot {bot_name} to version {version}")
+
+ return redirect(url_for("dc_bots"))
+
+
+def initialize_bot_versions():
+ bots = db.dc_bots.find({"version": {"$exists": False}})
+ for bot in bots:
+ db.dc_bots.update_one({"_id": bot["_id"]}, {"$set": {"version": "1.0"}})
+ add_log(f"Initialized version 1.0 for bot {bot['name']}")
+
+
+initialize_bot_versions()
+
+if db.events.count_documents({}) == 0:
+ db.events.insert_one({
+ "name": "Sample Event",
+ "top": 0,
+ "flop": 0,
+ "rating": 0,
+ "timestamp": datetime.now()
+ })
+
+
+def save_logs_to_file(process, log_file_path):
+ try:
+ with open(log_file_path, "w") as log_file:
+ while True:
+ output = process.stdout.readline()
+ error = process.stderr.readline()
+
+ if output:
+ log_file.write(repr(output) + "\n")
+ log_file.flush()
+ print(output, end="")
+ if error:
+ log_file.write("[ERROR] " + repr(error) + "\n")
+ log_file.flush()
+ print("[ERROR] " + error, end="")
+
+ if process.poll() is not None:
+ for line in process.stdout.readlines():
+ log_file.write(repr(line) + "\n")
+ for line in process.stderr.readlines():
+ log_file.write("[ERROR] " + repr(line) + "\n")
+ break
+
+ time.sleep(0.5)
+ except Exception as e:
+ print(f"Fehler beim Speichern der Logs: {str(e)}")
+
+
+@app.route("/turn_on_bot/", methods=["POST"])
+def turn_on_bot(bot_name):
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if bot:
+ if "pid" in bot and psutil.pid_exists(bot["pid"]):
+ add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
+ return f"Bot {bot_name} läuft bereits.", 400
+
+ bot_directory = bot["directory"]
+ try:
+ command = ["python", bot_directory]
+
+ process = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+
+ log_file_path = f"/tmp/{bot_name}_log.txt"
+ db.dc_bots.update_one({"name": bot_name}, {
+ "$set": {
+ "pid": process.pid,
+ "log_file": log_file_path
+ }
+ })
+
+ threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
+
+ bot_config[bot_name] = {"status": "on", "pid": process.pid}
+ save_bot_config(bot_config)
+
+ add_log(f"{session['username']} turned on {bot_name}")
+ except Exception as e:
+ add_log(f"Error starting bot {bot_name}: {str(e)}")
+ return f"Fehler beim Starten des Bots {bot_name}: {str(e)}", 500
+ else:
+ return f"Bot {bot_name} nicht gefunden", 404
+
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/turn_off_bot/", methods=["POST"])
+def turn_off_bot(bot_name):
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if bot and "pid" in bot:
+ try:
+ pid = bot["pid"]
+
+ if psutil.pid_exists(pid):
+ os.kill(pid, signal.SIGTERM)
+ db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})
+
+ bot_config[bot_name] = {"status": "off"}
+ save_bot_config(bot_config)
+
+ add_log(f"{session['username']} turned off {bot_name}")
+ else:
+ add_log(f"Process for bot {bot_name} does not exist")
+ db.dc_bots.update_one({"name": bot_name}, {"$unset": {"pid": ""}})
+ except Exception as e:
+ add_log(f"Error stopping bot {bot_name}: {str(e)}")
+ return f"Fehler beim Stoppen des Bots {bot_name}: {str(e)}", 500
+ else:
+ return f"Bot {bot_name} nicht gefunden oder kein aktiver Prozess vorhanden", 404
+
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/turn_on_all_bots", methods=["POST"])
+def turn_on_all_bots():
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bots = list(db.dc_bots.find())
+ for bot in bots:
+ if "pid" not in bot or not psutil.pid_exists(bot["pid"]):
+ bot_directory = bot["directory"]
+ try:
+ command = ["python", bot_directory]
+ process = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ log_file_path = f"/tmp/{bot['name']}_log.txt"
+ db.dc_bots.update_one({"name": bot["name"]}, {
+ "$set": {
+ "pid": process.pid,
+ "log_file": log_file_path
+ }
+ })
+ threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
+
+ bot_config[bot["name"]] = {"status": "on", "pid": process.pid}
+ save_bot_config(bot_config)
+
+ add_log(f"{session['username']} turned on {bot['name']}")
+ except Exception as e:
+ add_log(f"Error starting bot {bot['name']}: {str(e)}")
+
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/turn_off_all_bots", methods=["POST"])
+def turn_off_all_bots():
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bots = list(db.dc_bots.find())
+ for bot in bots:
+ if "pid" in bot:
+ try:
+ pid = bot["pid"]
+ if psutil.pid_exists(pid):
+ os.kill(pid, signal.SIGTERM)
+ db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})
+
+ bot_config[bot["name"]] = {"status": "off"}
+ save_bot_config(bot_config)
+
+ add_log(f"{session['username']} turned off {bot['name']}")
+ else:
+ add_log(f"Process for bot {bot['name']} does not exist")
+ db.dc_bots.update_one({"name": bot["name"]}, {"$unset": {"pid": ""}})
+ except Exception as e:
+ add_log(f"Error stopping bot {bot['name']}: {str(e)}")
+
+ return redirect(url_for("dc_bots"))
+
+
+@app.route("/bot_logs/", methods=["GET"])
+def bot_logs(bot_name):
+ if "username" not in session or "discord_bots" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if bot and "log_file" in bot:
+ log_file_path = bot["log_file"]
+
+ try:
+ with open(log_file_path, "r") as log_file:
+ logs = log_file.readlines()
+ except FileNotFoundError:
+ logs = ["Keine Logs gefunden."]
+
+ return render_template("bot_logs.html", bot_name=bot_name, logs=logs)
+ else:
+ return f"Logs für Bot {bot_name} nicht gefunden", 404
+
+
+def start_bots_on_server_start():
+ for bot_name, config in bot_config.items():
+ if config["status"] == "on":
+ bot = db.dc_bots.find_one({"name": bot_name})
+ if bot:
+ if "pid" in bot and psutil.pid_exists(bot["pid"]):
+ add_log(f"Bot {bot_name} is already running with PID {bot['pid']}")
+ continue
+
+ bot_directory = bot["directory"]
+ try:
+ command = ["python", bot_directory]
+ process = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ log_file_path = f"/tmp/{bot_name}_log.txt"
+ db.dc_bots.update_one({"name": bot_name}, {
+ "$set": {
+ "pid": process.pid,
+ "log_file": log_file_path
+ }
+ })
+ threading.Thread(target=save_logs_to_file, args=(process, log_file_path)).start()
+
+ bot_config[bot_name] = {"status": "on", "pid": process.pid}
+ save_bot_config(bot_config)
+
+ add_log(f"Bot {bot_name} automatically started on server start")
+ except Exception as e:
+ add_log(f"Error starting bot {bot_name} on server start: {str(e)}")
+
+
+start_bots_on_server_start()
+
+
+@app.route("/verify_age", methods=["GET", "POST"])
+@csrf.exempt
+def verify_age():
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ age = None
+ birthdate_dt = None
+ today = datetime.today()
+ if request.method == "POST":
+ birthdate = request.form["birthdate"]
+ steam_id = request.form["steam_id"]
+ ingame_name = request.form["ingame_name"]
+
+ birthdate_dt = datetime.strptime(birthdate, "%Y-%m-%d")
+ age = today.year - birthdate_dt.year - ((today.month, today.day) < (birthdate_dt.month, birthdate_dt.day))
+
+ user_ages_collection.insert_one({
+ "username": session["username"],
+ "birthdate": birthdate,
+ "age": age,
+ "steam_id": steam_id,
+ "ingame_name": ingame_name,
+ "timestamp": datetime.now()
+ })
+
+ search_query = request.args.get('search')
+ if search_query:
+ users = user_ages_collection.find({
+ "$or": [
+ {"ingame_name": {"$regex": search_query, "$options": "i"}},
+ {"steam_id": {"$regex": search_query, "$options": "i"}}
+ ]
+ })
+ else:
+ users = user_ages_collection.find().limit(10)
+
+ return render_template("verify_age.html", age=age, birthdate_dt=birthdate_dt, today=today, users=users)
+
+
+@app.route("/verify_age/", methods=["GET"])
+def view_user_age(user_id):
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ user = user_ages_collection.find_one({"_id": ObjectId(user_id)})
+ if user:
+ user['_id'] = str(user['_id'])
+ return render_template("view_user_age.html", user=user)
+ else:
+ return "Benutzer nicht gefunden", 404
+
+
+@app.route('/logout')
+def logout():
+ add_log(f"{session['username']} hat sich ausgeloggt")
+ session.pop("username", None)
+ session.pop("permissions", None)
+ session.pop("user_id", None)
+ return redirect(url_for("login"))
+
+
+@app.route('/toggle-theme', methods=['POST'])
+@csrf.exempt
+def toggle_theme():
+ if "username" not in session:
+ return "Unauthorized", 401
+
+ user = accounts_collection.find_one({"_id": ObjectId(session["user_id"])})
+ if not user:
+ return "User not found", 404
+
+ current_theme = user.get("Style", "lightmode")
+ new_theme = "darkmode" if current_theme == "lightmode" else "lightmode"
+
+ accounts_collection.update_one({"_id": ObjectId(session["user_id"])}, {"$set": {"Style": new_theme}})
+ session["theme"] = new_theme
+
+ return "", 204
+
+
+@app.route("/ip_checker", methods=["GET", "POST"])
+def ip_checker():
+ if "username" not in session or "ip_checker" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ import ipaddress
+
+ ip_data = None
+ error = None
+ similar_ips = []
+ is_cheater_network = False
+ similar_cheater_ips = []
+
+ if request.method == "POST":
+ ip_address = request.form.get("ip_address")
+ is_cheater = request.form.get("is_cheater") == "yes"
+ steam_id = request.form.get("steam_id") if is_cheater else None
+
+ if not ip_address:
+ error = "Bitte eine IP-Adresse eingeben."
+ elif is_cheater and not steam_id:
+ error = "SteamID ist erforderlich, wenn die IP als Cheater markiert wird."
+ else:
+ try:
+ api_key = "015ada9472664329affb2f26d7036d47"
+ response = requests.get(f"https://vpnapi.io/api/{ip_address}?key={api_key}")
+ if response.status_code == 200:
+ ip_data = response.json()
+
+ if "security" not in ip_data or not isinstance(ip_data["security"], dict):
+ ip_data["security"] = {}
+ if "location" not in ip_data or not isinstance(ip_data["location"], dict):
+ ip_data["location"] = {}
+ if "network" not in ip_data or not isinstance(ip_data["network"], dict):
+ ip_data["network"] = {}
+
+ checked_network = None
+ if "network" in ip_data and "network" in ip_data["network"]:
+ cidr = str(ip_data["network"]["network"]).replace(" ", "")
+ try:
+ checked_network = ipaddress.ip_network(cidr, strict=False)
+ except Exception:
+ checked_network = None
+
+ cheater_ips = list(db.ip_infos.find({"is_cheater": True}))
+ for cheater in cheater_ips:
+ cheater_data = cheater.get("data", {})
+ cheater_network = None
+ if "network" in cheater_data and "network" in cheater_data["network"]:
+ cheater_cidr = str(cheater_data["network"]["network"]).replace(" ", "")
+ try:
+ cheater_network = ipaddress.ip_network(cheater_cidr, strict=False)
+ except Exception:
+ cheater_network = None
+ cheater_ip = cheater.get("ip")
+ if cheater_network and checked_network:
+ if checked_network.overlaps(cheater_network):
+ is_cheater_network = True
+ similar_cheater_ips.append(cheater)
+ if cheater_ip and checked_network:
+ try:
+ if ipaddress.ip_address(cheater_ip) in checked_network:
+ is_cheater_network = True
+ similar_cheater_ips.append(cheater)
+ except Exception:
+ pass
+
+ similar_ips = list(db.ip_infos.find({
+ "$or": [
+ {"ip": ip_address},
+ {"data.location.city": ip_data["location"].get("city")},
+ {"data.network.autonomous_system_number": ip_data["network"].get(
+ "autonomous_system_number")}
+ ]
+ }))
+
+ db.ip_infos.insert_one({
+ "ip": ip_address,
+ "is_cheater": is_cheater,
+ "steam_id": steam_id,
+ "data": ip_data,
+ "added_by": session["username"],
+ "timestamp": datetime.now()
+ })
+ else:
+ error = "Fehler beim Abrufen der IP-Daten."
+ except Exception as e:
+ error = f"Fehler: {str(e)}"
+
+ return render_template(
+ "ip_checker.html",
+ ip_data=ip_data,
+ error=error,
+ similar_ips=similar_ips,
+ is_cheater_network=is_cheater_network,
+ similar_cheater_ips=similar_cheater_ips
+ )
+
+
+@app.route('/add-changelog', methods=['GET', 'POST'])
+def add_changelog():
+ if 'changelogs' not in session.get('permissions', []):
+ return "Unauthorized", 403
+
+ if request.method == 'POST':
+ changelog_text = request.form.get('changelog')
+ if changelog_text:
+ changelogs_collection.insert_one({
+ 'text': changelog_text,
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ })
+ return redirect(url_for('dashboard'))
+
+ return render_template('add_changelog.html')
+
+
+@app.route("/appointments", methods=["GET", "POST"])
+def appointments():
+ if "username" not in session or "appointments" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ if request.method == "POST":
+ title = request.form["title"]
+ start_date = request.form["start_date"]
+ end_date = request.form["end_date"]
+ description = request.form.get("description", "")
+
+ appointments_collection.insert_one({
+ "username": session["username"],
+ "title": title,
+ "start_date": start_date,
+ "end_date": end_date,
+ "description": description,
+ "created_at": datetime.now()
+ })
+
+ add_log(f"{session['username']} hat einen neuen Termin erstellt: {title}")
+
+ return redirect(url_for("appointments"))
+
+ user_appointments = list(appointments_collection.find({"username": session["username"]}).sort("start_date", 1))
+
+ return render_template("appointments.html", appointments=user_appointments)
+
+
+@app.route("/delete_appointment/", methods=["POST"])
+def delete_appointment(appointment_id):
+ if "username" not in session or "appointments" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
+ if appointment:
+ appointments_collection.delete_one({"_id": ObjectId(appointment_id)})
+ add_log(f"{session['username']} hat den Termin '{appointment['title']}' gelöscht")
+
+ return redirect(url_for("appointments"))
+
+
+@app.route("/edit_appointment/", methods=["GET", "POST"])
+def edit_appointment(appointment_id):
+ if "username" not in session or "appointments" not in session["permissions"]:
+ return redirect(url_for("login"))
+
+ appointment = appointments_collection.find_one({"_id": ObjectId(appointment_id), "username": session["username"]})
+ if not appointment:
+ return "Termin nicht gefunden", 404
+
+ if request.method == "POST":
+ title = request.form["title"]
+ start_date = request.form["start_date"]
+ end_date = request.form["end_date"]
+ description = request.form.get("description", "")
+
+ appointments_collection.update_one(
+ {"_id": ObjectId(appointment_id)},
+ {"$set": {
+ "title": title,
+ "start_date": start_date,
+ "end_date": end_date,
+ "description": description
+ }}
+ )
+
+ add_log(f"{session['username']} hat den Termin '{title}' bearbeitet")
+
+ return redirect(url_for("appointments"))
+
+ return render_template("edit_appointment.html", appointment=appointment)
+
+
+@app.route("/pcmachwas", methods=["GET", "POST"])
+def pcmachwas():
+ return render_template("stinker.html")
+
+
+@app.route("/settings", methods=["GET"])
+def settings():
+ if "username" not in session:
+ return redirect(url_for("login"))
+
+ return redirect(url_for("edit_user", user_id=session["user_id"]))
+
+
+@app.route("/ets2_map_cracked")
+def ets2_map_cracked():
+ scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
+ if not os.path.isdir(scs_dir):
+ return f"Verzeichnis nicht gefunden oder keine Rechte: {scs_dir}", 404
+ try:
+ files = [
+ f for f in os.listdir(scs_dir)
+ if f.endswith(".scs") and os.path.isfile(os.path.join(scs_dir, f))
+ ]
+ except Exception as e:
+ return f"Fehler beim Lesen des Verzeichnisses: {e}", 500
+ if not files:
+ return "Keine .scs Dateien gefunden.", 404
+ links = "".join(
+ f''
+ for i, fname in enumerate(files)
+ )
+ js = f"""
+
+ """
+ return f"ETS2 Maps Cracked Downloads
{links}{js}Downloads werden automatisch gestartet...
"
+
+
+@app.route("/download_ets2_map_cracked/")
+def download_ets2_map_cracked(filename):
+ scs_dir = "/var/www/admin_website/static/ets2_map_cracked"
+ file_path = os.path.join(scs_dir, filename)
+ if os.path.isfile(file_path) and filename.endswith(".scs"):
+ return send_file(file_path, as_attachment=True)
+ return "Datei nicht gefunden.", 404
+
+
+# wallpaper
+@app.route("/wallpaper")
+def wallpaper():
+ return render_template("wallpaper.html")
+
+
+# NEUE PROXY-ROUTE für die SCP:SL API
+@app.route('/api/scpsl_proxy')
+def scpsl_proxy():
+ try:
+ response = requests.get(SCP_SL_API_URL, timeout=5)
+ response.raise_for_status()
+ return jsonify(response.json())
+ except requests.exceptions.Timeout:
+ return jsonify({"error": "Request timed out"}), 504
+ except requests.exceptions.ConnectionError:
+ return jsonify({"error": "Connection error"}), 503
+ except requests.exceptions.RequestException as e:
+ return jsonify({"error": f"Failed to fetch data: {e}"}), 500
+
+
+def fetch_and_store_player_count():
+ with app.app_context():
+ try:
+ response = requests.get(SCP_SL_API_URL, timeout=5)
+ response.raise_for_status()
+ data = response.json()
+ players_str_full = data.get('players', '0/0')
+ current_players = 0
+ if '/' in players_str_full:
+ try:
+ current_players = int(players_str_full.split('/')[0])
+ except ValueError:
+ current_players = 0
+ else:
+ try:
+ current_players = int(players_str_full)
+ except ValueError:
+ current_players = 0
+ scpsl_playerdata_collection.insert_one({
+ "timestamp": datetime.utcnow(),
+ "players": current_players
+ })
+ scpsl_playerdata_collection.create_index("timestamp")
+ except requests.exceptions.RequestException as e:
+ print(f"Error fetching player count: {e}")
+ except Exception as e:
+ print(f"Unexpected error fetching/storing to MongoDB: {e}")
+
+
+def background_task():
+ while True:
+ fetch_and_store_player_count()
+ time.sleep(60)
+
+
+threading.Thread(target=background_task, daemon=True).start()
+
+
+@app.route('/api/player_history')
+def player_history_api():
+ graph_data = []
+ one_day_ago = datetime.utcnow() - timedelta(hours=24)
+ history_cursor = scpsl_playerdata_collection.find(
+ {"timestamp": {"$gte": one_day_ago}},
+ {"_id": 0, "timestamp": 1, "players": 1}
+ ).sort("timestamp", 1)
+
+ zero_start = None
+ for doc in history_cursor:
+ t = doc["timestamp"]
+ p = doc["players"]
+ if p == 0:
+ if zero_start is None:
+ zero_start = t
+ continue
+ else:
+ if zero_start is not None:
+ duration = (t - zero_start).total_seconds()
+ if duration > 20:
+ graph_data.append({
+ "time": zero_start.isoformat() + "Z",
+ "players": 0
+ })
+ graph_data.append({
+ "time": t.isoformat() + "Z",
+ "players": p
+ })
+ zero_start = None
+ graph_data.append({
+ "time": t.isoformat() + "Z",
+ "players": p
+ })
+ if zero_start is not None:
+ last_time = datetime.utcnow()
+ duration = (last_time - zero_start).total_seconds()
+ if duration > 20:
+ graph_data.append({
+ "time": zero_start.isoformat() + "Z",
+ "players": 0
+ })
+ graph_data.append({
+ "time": last_time.isoformat() + "Z",
+ "players": 0
+ })
+ return jsonify(graph_data)
+
+
+@app.route('/player_history_graph')
+def player_history_graph():
+ html = """
+
+ SCP:SL Player Count (Letzte 24h)
+
+
+
+
+
+ """
+ return html
+
+
+@app.route('/player_history_graph_img')
+def player_history_graph_img():
+ one_day_ago = datetime.utcnow() - timedelta(hours=24)
+ history_cursor = scpsl_playerdata_collection.find(
+ {"timestamp": {"$gte": one_day_ago}},
+ {"_id": 0, "timestamp": 1, "players": 1}
+ ).sort("timestamp", 1)
+ zero_start = None
+ filtered_times = []
+ filtered_players = []
+ for doc in history_cursor:
+ t = doc["timestamp"]
+ p = doc["players"]
+ if p == 0:
+ if zero_start is None:
+ zero_start = t
+ continue
+ else:
+ if zero_start is not None:
+ duration = (t - zero_start).total_seconds()
+ if duration > 20:
+ filtered_times.append(zero_start)
+ filtered_players.append(0)
+ filtered_times.append(t)
+ filtered_players.append(p)
+ zero_start = None
+ filtered_times.append(t)
+ filtered_players.append(p)
+ if zero_start is not None:
+ last_time = datetime.utcnow()
+ duration = (last_time - zero_start).total_seconds()
+ if duration > 20:
+ filtered_times.append(zero_start)
+ filtered_players.append(0)
+ filtered_times.append(last_time)
+ filtered_players.append(0)
+ fig, ax = plt.subplots(figsize=(10, 6))
+ if not filtered_times:
+ ax.text(0.5, 0.5, 'No player data available for the last 24 hours.',
+ horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=12, color='red')
+ ax.set_ylim(bottom=0, top=36)
+ else:
+ ax.plot(filtered_times, filtered_players, color='lime', linewidth=2)
+ max_players_in_history = max(filtered_players) if filtered_players else 0
+ if max_players_in_history > 0:
+ ax.set_ylim(bottom=0, top=max_players_in_history + 5)
+ else:
+ ax.set_ylim(bottom=0, top=36)
+ ax.set_title('SCP:SL Player Count (Letzte 24h)', color='white')
+ ax.set_xlabel('Zeit', color='white')
+ ax.set_ylabel('Spieleranzahl', color='white')
+ ax.set_facecolor('#1a1a1a')
+ fig.patch.set_facecolor('#1a1a1a')
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
+ ax.xaxis.set_major_locator(mdates.HourLocator(interval=3))
+ fig.autofmt_xdate()
+ ax.tick_params(axis='x', colors='white')
+ ax.tick_params(axis='y', colors='white')
+ ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True))
+ ax.grid(True, linestyle='--', alpha=0.5, color='#444444')
+ ax.spines['bottom'].set_color('white')
+ ax.spines['top'].set_color('white')
+ ax.spines['left'].set_color('white')
+ ax.spines['right'].set_color('white')
+ buffer = BytesIO()
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=100)
+ buffer.seek(0)
+ plt.close(fig)
+ return send_file(buffer, mimetype='image/png')
+
+
+# Voice-Channel Übersicht
+@app.route("/vcs")
+def vcs_overview():
+ # Beispiel: Channels können dynamisch sein, hier statisch
+ channels = [
+ {"id": "talk1", "name": "Teamtalk"},
+ {"id": "talk2", "name": "Privat"},
+ {"id": "talk3", "name": "Privat 2"}
+ ]
+ return render_template("schisscord.html", channels=channels)
+
+
+# Voice-Channel Join
+@app.route("/join/")
+def join_vc(channel_id):
+ if "username" not in session:
+ return redirect(url_for("login"))
+ return render_template("vc_room.html", channel_id=channel_id, username=session["username"])
+
+
+# SocketIO: Nutzer tritt Channel bei
+@socketio.on("join_channel")
+def handle_join_channel(data):
+ channel = data.get("channel")
+ username = data.get("username")
+ join_room(channel)
+ emit("user_joined", {"username": username}, room=channel)
+
+
+@socketio.on("leave_channel")
+def handle_leave_channel(data):
+ channel = data.get("channel")
+ username = data.get("username")
+ leave_room(channel)
+ emit("user_left", {"username": username}, room=channel)
+
+
+@socketio.on("signal")
+def handle_signal(data):
+ channel = data.get("channel")
+ signal_data = data.get("signal")
+ sender = data.get("username")
+ emit("signal", {"signal": signal_data, "username": sender}, room=channel, include_self=False)
+
+
+MONGO_URI = "mongodb+srv://MrSniff:5169Galaxy@chainsaw.d0shx.mongodb.net/"
+client = MongoClient(MONGO_URI)
+ETS2_DB_NAME = "ets2_license_db"
+ETS2_COLLECTION_NAME = "licenses"
+ets2_db = client[ETS2_DB_NAME]
+ets2_licenses = ets2_db[ETS2_COLLECTION_NAME]
+print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")
+
+# --- DLC Folder ---
+DLC_FOLDER = "/var/www/admin_website/static/ets2_dlcs"
+
+# --- License Check Endpoint ---
+@app.route("/checklicense", methods=["GET"])
+def check_license():
+ key = request.args.get("key")
+ user = request.args.get("user")
+ print(f"[DEBUG] /checklicense called with key='{key}' user='{user}'")
+
+ if not key or not user:
+ print("[DEBUG] Missing key or user")
+ return "invalid", 400
+
+ entry = ets2_licenses.find_one({"license_key": key})
+ print(f"[DEBUG] MongoDB entry found: {entry}")
+
+ if entry:
+ if not entry.get("windows_user"):
+ ets2_licenses.update_one({"license_key": key}, {"$set": {"windows_user": user}})
+ print(f"[DEBUG] Key '{key}' registered to user '{user}'")
+ return "valid"
+ elif entry["windows_user"] == user:
+ print(f"[DEBUG] Key '{key}' valid for user '{user}'")
+ return "valid"
+ else:
+ print(f"[DEBUG] Key '{key}' already used by another user '{entry['windows_user']}'")
+ return "invalid_user"
+ else:
+ print(f"[DEBUG] Key '{key}' not found in database")
+ return "invalid"
+
+# --- DLC Download Endpoint ---
+@app.route("/download", methods=["GET"])
+def download_dlc():
+ key = request.headers.get("License-Key")
+ user = request.headers.get("Windows-User")
+ dlc_type = request.args.get("type", "all") # "all_map_dlcs" oder "all_dlcs"
+ print(f"[DEBUG] /download called with key='{key}', user='{user}', type='{dlc_type}'")
+
+ if not key or not user:
+ print("[DEBUG] Missing headers")
+ return "missing_headers", 400
+
+ entry = ets2_licenses.find_one({"license_key": key})
+ print(f"[DEBUG] MongoDB entry for download: {entry}")
+
+ if not entry or entry.get("windows_user") != user:
+ print(f"[DEBUG] Unauthorized access for key '{key}' user '{user}'")
+ return "unauthorized", 403
+
+ # Bestimme die Datei basierend auf DLC Typ
+ if dlc_type == "all_map_dlcs":
+ file_name = "all_map_dlcs.zip"
+ else:
+ file_name = "all_dlcs.zip"
+
+ file_path = os.path.join(DLC_FOLDER, file_name)
+ if not os.path.exists(file_path):
+ print(f"[DEBUG] File not found at {file_path}")
+ return "not_found", 404
+
+ print(f"[DEBUG] Sending file {file_path} to user '{user}'")
+ return send_file(file_path, as_attachment=True)
+
+
+if __name__ == "__main__":
+ print("[DEBUG] Starting ETS2 DLC Server")
+ print(f"[DEBUG] Using DB: {ETS2_DB_NAME}, Collection: {ETS2_COLLECTION_NAME}")
+ socketio.run(app, debug=True, host='0.0.0.0', port=5000)