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 + + + + + + + + + + + +
+ +
+
+ + + + + + + + +
+ +

AstraOS

+
+ +
+ +
+ +

+

+ +
+ +
+ + +
+ +
+ +
+ + +
+
+ + + +
+ +
+ +
+ + + + + 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 %} +
+
+

Termin Planer

+
+ + + + + + + + + + + + + + {% for week in calendar %} + + {% for day in week %} + + {% endfor %} + + {% endfor %} + +
MontagDienstagMittwochDonnerstagFreitagSamstagSonntag
+ {{ day.date }} +
    + {% for event in day.events %} +
  • {{ event.time }} - {{ event.player }}: {{ event.description }} (Für: {{ event.assigned_to }})
  • + {% endfor %} +
+ +
+
+
+
+ + + + +{% 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

+
+ + + +
+
+
+
+ 🔙 Zurück zum Dashboard +
+
+ +
+

📋 Anstehende Termine

+ {% if appointments %} + + {% 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 %} +
+ + +
+
+ Check + 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 }}

+ {{ 'Online' if 'pid' in bot else 'Offline' }} +
+ + +
+
+ + +
+
+ +
+
+ {% endfor %} +
+
+ Zurück +
+
+
+ + +{% else %} + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+ + +
+
+

Discord Bots

+ +
+ {% for bot in bots %} +
+

{{ bot.name }}

+ {{ 'Online' if 'pid' in bot else 'Offline' }} +
+ + +
+
+ + +
+
+ +
+
+ {% endfor %} +
+
+ Zurück +
+
+
+ + +{% 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 %} + + + + + + + {% for request in delete_requests %} + + + + + + {% endfor %} +
LizenzschlüsselBenutzernameAktionen
{{ request['license_key'] }}{{ request['username'] }} +
+ + +
+
+ + +
+
+ {% endif %} +
+ Zurück +
+
+
+{% else %} + +
+
+

Lösch Anfragen

+ {% if delete_requests|length == 0 %} +

Zurzeit keine aktive Lösch-Anfragen

+ {% else %} + + + + + + + {% for request in delete_requests %} + + + + + + {% endfor %} +
LizenzschlüsselBenutzernameAktionen
{{ request['license_key'] }}{{ request['username'] }} +
+ + +
+
+ + +
+
+ {% endif %} +
+ Zurück +
+
+
+{% 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 %} + +
+
+ +
+ + + +

Benutzer bearbeiten

+ + + + +

Berechtigungen: {{ user['permissions'] | join(', ') }}

+
+ {% if session['theme'] == 'lightmode' %} + + Zurück + {% else %} + + Zurück + {% endif %} +
+
+
+
+ +
+ + +{% 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' %} +
+
+
+

Event hinzufügen

+
+ + + + + + + + + + + + +
+
+
+

Bestes Event berechnen

+
+ + + + + + + +
+ {% if best_events %} +

Top 5 Events

+
+
+ + {% elif best_event %} +

Bestes Event

+

{{ best_event.name }} - Rating: {{ best_event.rating }} (Top: {{ best_event.top }}, Flop: {{ best_event.flop }})

+ {% endif %} +
+
+

Protokollierte Events

+
    + {% set event_names = [] %} + {% for event in events %} + {% if event.name not in event_names %} +
  • {{ event.name }}
  • + {% set event_names = event_names.append(event.name) %} + {% endif %} + {% endfor %} +
+
+
+
+ Zurück +
+
+{% else %} +
+
+
+

Event hinzufügen

+
+ + + + + + + + + + + + +
+
+
+

Bestes Event berechnen

+
+ + + + + + + +
+ {% if best_events %} +

Top 5 Events

+
+
+ + {% elif best_event %} +

Bestes Event

+

{{ best_event.name }} - Rating: {{ best_event.rating }} (Top: {{ best_event.top }}, Flop: {{ best_event.flop }})

+ {% endif %} +
+
+

Protokollierte Events

+
    + {% set event_names = [] %} + {% for event in events %} + {% if event.name not in event_names %} +
  • {{ event.name }}
  • + {% set event_names = event_names.append(event.name) %} + {% endif %} + {% endfor %} +
+
+
+
+ Zurück +
+
+{% 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

+ +
+ Cross + 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 %} +
+
+

IP Checker

+
+ + +
+ +
+ +
+
+ +
+
+ {% if session['theme'] == 'darkmode' %} + + Zurück + {% else %} + + Zurück + {% endif %} +
+
+ + {% if error %} +

{{ error }}

+ {% endif %} + {% if ip_data %} +

IP-Daten

+ + + + + + + + + + + + + +
FeldWert
IP-Adresse{{ ip_data.get('ip', 'N/A') }}
VPN{{ 'Ja' if ip_data['security'].get('vpn', False) else 'Nein' }}
Proxy{{ 'Ja' if ip_data['security'].get('proxy', False) else 'Nein' }}
Tor{{ 'Ja' if ip_data['security'].get('tor', False) else 'Nein' }}
Land{{ ip_data['location'].get('country', 'N/A') }}
Stadt{{ ip_data['location'].get('city', 'N/A') }}
ASN{{ ip_data['network'].get('autonomous_system_number', 'N/A') }}
Netzwerk{{ ip_data['network'].get('network', 'N/A') }}
+ {% endif %} + {% if similar_cheater_ips %} +

Ähnliche Cheater-IPs

+ + + + + + + + {% for ip in similar_cheater_ips %} + + + + + + + {% endfor %} +
IP-AdresseStadtASNSteamID
{{ ip['ip'] }}{{ ip['data']['location'].get('city', 'N/A') }}{{ ip['data']['network'].get('autonomous_system_number', 'N/A') }}{{ ip.get('steam_id', 'N/A') }}
+ {% endif %} + {% if similar_ips|selectattr('is_cheater', 'equalto', True)|list|length > 0 %} +

Ähnliche Cheater-IPs

+ + + + + + + + {% for ip in similar_ips %} + {% if ip['is_cheater'] %} + + + + + + + {% endif %} + {% endfor %} +
IP-AdresseStadtASNSteamID
{{ ip['ip'] }}{{ ip['data']['location'].get('city', 'N/A') }}{{ ip['data']['network'].get('autonomous_system_number', 'N/A') }}{{ ip.get('steam_id', 'N/A') }}
+ {% endif %} +
+
+{% 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 %} +
+ 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' %} + +
+
+ Zurück +
+
+

Logs

+ {% for log in logs %} +
+

Datum: {{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}

+

Nachricht: {{ log.message }}

+
+ {% endfor %} +
+
+{% else %} + +
+
+ Zurück +
+
+

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'] }} +
+
+ Profilbild +
+
+
+ + \ 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 %} +
+

User Management

+ + + + + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} + +
PFPs UsernameDisplay NamePermsAktionen
+ Profilbild + {{ user['username'] }}{{ user['display_name'] }} +
+ {% for permission in user['permissions'] %} +
+ {{ permission }} + {% if user['username'] != session['username'] and permission in session.get('permissions', []) %} +
+ + + Remove +
+ {% endif %} +
+ {% endfor %} + + {% if user['username'] != session['username'] %} + + {% endif %} +
+
+ {% if user['username'] != session['username'] %} + {% set current_user_permissions = session.get('permissions', []) %} + {% if 'permission_management' in current_user_permissions and 'permission_management' in user['permissions'] and session['username'] != 'MrSniff' %} + Keine Aktionen verfügbar + {% else %} +
+ {% if session['theme'] == 'darkmode' %} + + {% else %} + + {% endif %} +
+
+ {% if session['theme'] == 'darkmode' %} + + + {% else %} + + + {% endif %} +
+ {% endif %} + {% else %} + Keine Aktionen verfügbar + {% endif %} +
+ +
+ + + + + + + + {% if session['theme'] == 'darkmode' %} + + {% else %} + + {% endif %} +
+ + {% if session['theme'] == 'darkmode' %} + Zurück + {% else %} + Zurück + {% endif %} +
+ {% 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' %} + +
+
+
+

Altersüberprüfung

+
+ + +
+ + +
+ + +
+ +
+ {% if age is not none %} +

Die Person ist {{ age }} Jahre alt.

+ {% if age < 13 %} +

Diese Person muss für {{ (birthdate_dt.replace(year=birthdate_dt.year + 13) - today).days }} Tage gebannt werden.

+ {% else %} +

Diese Person muss nicht gebannt werden.

+ {% endif %} + {% endif %} +
+
+

Benutzer suchen

+
+ + +
+
+ {% for user in users %} +
+ +
+ {% endfor %} +
+
+
+
+ Zurück +
+
+{% else %} + + + + + Altersüberprüfung + + + + + + {% include "top_bar.html" %} +
+
+

Altersüberprüfung

+
+ + +
+ + +
+ + +
+ +
+ {% if age is not none %} +

Die Person ist {{ age }} Jahre alt.

+ {% if age < 13 %} +

Diese Person muss für {{ (birthdate_dt.replace(year=birthdate_dt.year + 13) - today).days }} Tage gebannt werden.

+ {% else %} +

Diese Person muss nicht gebannt werden.

+ {% endif %} + {% endif %} +
+
+

Benutzer suchen

+
+ + +
+
+ {% for user in users %} +
+ +
+ {% endfor %} +
+
+
+
+ Zurück +
+ + +{% 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' %} + +
+
+ Zurück +
+
+

Website Changelogs

+ {% for changelog in changelogs %} +
+ {{ changelog.timestamp }} +
{{ changelog.text }}
+
+ {% endfor %} +
+
+{% else %} + +
+
+ Zurück +
+
+

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:

+ + {% 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...
+ + +
+ Spielerstatistik der letzten 24h +
+
+ + + + \ 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)