From 4bca204defa6735c530f43984436ae4eb4ee728f Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 5 Oct 2025 23:25:21 +0200 Subject: [PATCH] + Not Working in Development --- Uptime-Stats/.gitignore | 14 + Uptime-Stats/main.py | 302 ++++++++++++++++++++ Uptime-Stats/readme.md | 47 +++ Uptime-Stats/requirements.txt | 9 + Uptime-Stats/templates/admin/index.html | 20 ++ Uptime-Stats/templates/change_password.html | 43 +++ Uptime-Stats/templates/index.html | 78 +++++ Uptime-Stats/templates/layout.html | 94 ++++++ Uptime-Stats/templates/login.html | 36 +++ Uptime-Stats/uptime.db | Bin 0 -> 12288 bytes 10 files changed, 643 insertions(+) create mode 100644 Uptime-Stats/.gitignore create mode 100644 Uptime-Stats/main.py create mode 100644 Uptime-Stats/readme.md create mode 100644 Uptime-Stats/requirements.txt create mode 100644 Uptime-Stats/templates/admin/index.html create mode 100644 Uptime-Stats/templates/change_password.html create mode 100644 Uptime-Stats/templates/index.html create mode 100644 Uptime-Stats/templates/layout.html create mode 100644 Uptime-Stats/templates/login.html create mode 100644 Uptime-Stats/uptime.db diff --git a/Uptime-Stats/.gitignore b/Uptime-Stats/.gitignore new file mode 100644 index 0000000..973e1fb --- /dev/null +++ b/Uptime-Stats/.gitignore @@ -0,0 +1,14 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Environment +.env +venv/ +env/ + +# IDEs +.idea/ +.vscode/ diff --git a/Uptime-Stats/main.py b/Uptime-Stats/main.py new file mode 100644 index 0000000..8c8356d --- /dev/null +++ b/Uptime-Stats/main.py @@ -0,0 +1,302 @@ + +import warnings +warnings.filterwarnings("ignore", category=UserWarning, module='flask_admin.contrib') + +import os +import requests +import threading +import time +import socket +from datetime import datetime +from flask import Flask, render_template, flash, redirect, url_for, request +from flask_sqlalchemy import SQLAlchemy +from flask_admin import Admin, AdminIndexView +from flask_admin.contrib.sqla import ModelView +from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required +from flask_migrate import Migrate, upgrade as upgrade_database +from werkzeug.security import generate_password_hash, check_password_hash +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField, SelectField, TextAreaField +from wtforms.validators import DataRequired, EqualTo + +# --- Initialisierung --- + +basedir = os.path.abspath(os.path.dirname(__file__)) + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'uptime.db') +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['SECRET_KEY'] = 'eine-viel-sicherere-geheime-zeichenkette' # In Produktion ändern! + +db = SQLAlchemy(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message = 'Bitte loggen Sie sich ein, um auf diese Seite zuzugreifen.' + +migrate = Migrate(app, db) + +# --- Datenbankmodelle --- + +class User(UserMixin, db.Model): + """Modell für Benutzer.""" + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password_hash = db.Column(db.String(200), nullable=False) + + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + + def __repr__(self): + return f'' + +class Monitor(db.Model): + """Modell für einen zu überwachenden Dienst.""" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False, unique=True) + monitor_type = db.Column(db.String(20), nullable=False, default='HTTP') + url = db.Column(db.String(200), nullable=False) + keyword = db.Column(db.String(100), nullable=True) + port = db.Column(db.Integer, nullable=True) + status_override = db.Column(db.String(50), nullable=True) + status_override_message = db.Column(db.Text, nullable=True) + + logs = db.relationship('UptimeLog', backref='monitor', lazy=True, cascade="all, delete-orphan") + + def __repr__(self): + return f'' + +class UptimeLog(db.Model): + """Modell zum Protokollieren des Uptime-Status.""" + id = db.Column(db.Integer, primary_key=True) + monitor_id = db.Column(db.Integer, db.ForeignKey('monitor.id'), nullable=False) + timestamp = db.Column(db.DateTime, default=datetime.utcnow) + status_code = db.Column(db.Integer, nullable=True) + is_up = db.Column(db.Boolean, nullable=False) + + def __repr__(self): + return f'' + +# --- Login-Management --- + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + +# --- Formulare --- + +class LoginForm(FlaskForm): + username = StringField('Benutzername', validators=[DataRequired()]) + password = PasswordField('Passwort', validators=[DataRequired()]) + submit = SubmitField('Einloggen') + +class ChangePasswordForm(FlaskForm): + current_password = PasswordField('Aktuelles Passwort', validators=[DataRequired()]) + new_password = PasswordField('Neues Passwort', validators=[DataRequired()]) + confirm_password = PasswordField('Neues Passwort bestätigen', validators=[DataRequired(), EqualTo('new_password', 'Die Passwörter müssen übereinstimmen.')]) + submit = SubmitField('Passwort ändern') + +# --- Gesicherter Admin-Bereich --- + +class SecureModelView(ModelView): + def is_accessible(self): + return current_user.is_authenticated + + def inaccessible_callback(self, name, **kwargs): + return redirect(url_for('login', next=request.url)) + +class SecureAdminIndexView(AdminIndexView): + def is_accessible(self): + return current_user.is_authenticated + + def inaccessible_callback(self, name, **kwargs): + return redirect(url_for('login', next=request.url)) + +class MonitorModelView(SecureModelView): + # Dropdown für den Monitor-Typ und manuellen Status + form_overrides = { + 'monitor_type': SelectField, + 'status_override': SelectField, + 'status_override_message': TextAreaField + } + form_args = { + 'monitor_type': { + 'label': 'Monitor-Typ', + 'choices': [ + ('HTTP', 'HTTP(s)'), + ('KEYWORD', 'HTTP(s) mit Keyword'), + ('TCP', 'TCP-Port') + ] + }, + 'status_override': { + 'label': 'Manueller Status', + 'choices': [ + ('', 'Automatisch'), # Leerer String für None + ('MAINTENANCE', 'Wartungsarbeiten'), + ('DEGRADED', 'Leistungsprobleme'), + ('OPERATIONAL', 'Funktionsfähig (Manuell)') + ] + } + } + column_list = ('name', 'monitor_type', 'status_override', 'url', 'port') + form_columns = ('name', 'monitor_type', 'url', 'port', 'keyword', 'status_override', 'status_override_message') + column_labels = dict(name='Name', monitor_type='Typ', url='URL/Host', port='Port', keyword='Keyword', status_override='Manueller Status', status_override_message='Status-Nachricht') + + def on_model_change(self, form, model, is_created): + if is_created: + print(f"Neuer Monitor erstellt: {model.name}. Starte initiale Prüfung.") + checker_thread = threading.Thread(target=check_monitors, kwargs={'monitor_id': model.id}) + checker_thread.start() + super().on_model_change(form, model, is_created) + + +admin = Admin(app, name='Uptime Stats Admin', template_mode='bootstrap4', index_view=SecureAdminIndexView()) +admin.add_view(MonitorModelView(Monitor, db.session, name="Monitore verwalten")) +admin.add_view(SecureModelView(User, db.session, name="Administratoren", endpoint='user_admin')) + + +# --- Web-Routen --- + +@app.route('/') +def index(): + monitors_with_status = [] + monitors = Monitor.query.order_by(Monitor.name).all() + for monitor in monitors: + last_log = UptimeLog.query.filter_by(monitor_id=monitor.id).order_by(UptimeLog.timestamp.desc()).first() + monitors_with_status.append({ + 'name': monitor.name, + 'url': monitor.url, + 'monitor_type': monitor.monitor_type, + 'keyword': monitor.keyword, + 'port': monitor.port, + 'status_override': monitor.status_override, + 'status_override_message': monitor.status_override_message, + 'is_up': last_log.is_up if last_log else None, + 'last_checked': last_log.timestamp if last_log else 'Nie' + }) + return render_template('index.html', monitors=monitors_with_status) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(username=form.username.data).first() + if user is None or not user.check_password(form.password.data): + flash('Ungültiger Benutzername oder Passwort.', 'danger') + return redirect(url_for('login')) + login_user(user) + flash('Erfolgreich eingeloggt.', 'success') + return redirect(url_for('admin.index')) + return render_template('login.html', form=form) + +@app.route('/logout') +@login_required +def logout(): + logout_user() + flash('Erfolgreich ausgeloggt.', 'info') + return redirect(url_for('index')) + +@app.route('/change-password', methods=['GET', 'POST']) +@login_required +def change_password(): + form = ChangePasswordForm() + if form.validate_on_submit(): + if not current_user.check_password(form.current_password.data): + flash('Das aktuelle Passwort ist nicht korrekt.', 'danger') + else: + current_user.set_password(form.new_password.data) + db.session.commit() + flash('Ihr Passwort wurde erfolgreich geändert.', 'success') + return redirect(url_for('index')) + return render_template('change_password.html', form=form) + +# --- Uptime-Checker (Hintergrundprozess) --- + +def check_monitors(monitor_id=None): + """Überprüft den Status. Wenn monitor_id angegeben ist, nur diesen, sonst alle.""" + with app.app_context(): + if monitor_id: + monitors = Monitor.query.filter_by(id=monitor_id).all() + print(f"[{datetime.now()}] Starte initiale Prüfung für Monitor ID {monitor_id}...") + else: + monitors = Monitor.query.all() + if not monitors: + return + print(f"[{datetime.now()}] Starte periodische Überprüfung für {len(monitors)} Monitor(en)...") + + if not monitors: + return + for monitor in monitors: + # Wenn ein manueller Status gesetzt ist, überspringe die automatische Prüfung. + if monitor.status_override: + print(f" - Monitor '{monitor.name}' hat manuellen Status '{monitor.status_override}'. Überspringe Prüfung.") + continue + is_up, status_code = False, None + + if monitor.monitor_type in ['HTTP', 'KEYWORD']: + try: + response = requests.get(monitor.url, timeout=10) + status_code = response.status_code + if 200 <= status_code < 400: + if monitor.monitor_type == 'KEYWORD' and monitor.keyword: + if monitor.keyword in response.text: + is_up = True + else: # Standard HTTP + is_up = True + except requests.RequestException: + pass + + elif monitor.monitor_type == 'TCP': + try: + # For TCP, the 'url' field holds the host + host = monitor.url + port = monitor.port + if host and port: + with socket.create_connection((host, port), timeout=10) as sock: + is_up = True + # status_code is not applicable for TCP checks + except (socket.timeout, socket.error, OSError): + pass + + log_entry = UptimeLog(monitor_id=monitor.id, status_code=status_code, is_up=is_up) + db.session.add(log_entry) + + db.session.commit() + print(f"[{datetime.now()}] Überprüfung abgeschlossen.") + + +def run_checks_periodically(): + """Führt die Überprüfungen in regelmäßigen Abständen aus.""" + while True: + check_monitors() + # Warte 5 Minuten (300 Sekunden) bis zur nächsten Überprüfung + time.sleep(300) + +# --- Initialisierung der App --- + +def create_initial_user(): + with app.app_context(): + # db.create_all() is now handled by migrations + if User.query.first() is None: + print("Erstelle initialen Admin-Benutzer...") + initial_user = User(username='admin') + initial_user.set_password('admin123') + db.session.add(initial_user) + db.session.commit() + print("Benutzer 'admin' mit Passwort 'admin123' erstellt.") + +if __name__ == '__main__': + # Apply database migrations automatically + with app.app_context(): + upgrade_database() + + create_initial_user() + + checker_thread = threading.Thread(target=run_checks_periodically, daemon=True) + checker_thread.start() + + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/Uptime-Stats/readme.md b/Uptime-Stats/readme.md new file mode 100644 index 0000000..1534acf --- /dev/null +++ b/Uptime-Stats/readme.md @@ -0,0 +1,47 @@ +# Uptime Stats + +Ein einfaches, in Python geschriebenes Tool zur Überwachung der Uptime von Websites, inspiriert von Uptime Kuma. + +Dieses Projekt verwendet Flask, um eine Weboberfläche und einen Admin-Bereich bereitzustellen, und speichert alle Daten in einer SQLite-Datenbank. + +## Features + +- **Web-Dashboard:** Eine einfache Seite zur Anzeige des aktuellen Status aller überwachten Websites. +- **Admin-Bereich:** Ein passwortgeschützter Bereich (`/admin`) zum Hinzufügen, Bearbeiten und Löschen von zu überwachenden Websites. +- **SQLite-Datenbank:** Alle Konfigurationen und Uptime-Protokolle werden in einer einzigen `uptime.db`-Datei gespeichert. Es ist keine externe Datenbank erforderlich. +- **Periodische Überprüfungen:** Ein Hintergrundprozess überprüft alle 5 Minuten automatisch den Status der Websites. + +## Installation + +1. **Klonen Sie das Repository (oder laden Sie die Dateien herunter):** + ```bash + git clone + cd Uptime-Stats/Uptime-Stats + ``` + +2. **Installieren Sie die Abhängigkeiten:** + Stellen Sie sicher, dass Sie Python 3 installiert haben. Erstellen Sie optional eine virtuelle Umgebung. + ```bash + pip install -r requirements.txt + ``` + +## Verwendung + +1. **Starten Sie die Anwendung:** + ```bash + python main.py + ``` + Die Anwendung wird standardmäßig auf `http://localhost:5000` ausgeführt. + +2. **Einloggen und Websites hinzufügen:** + - Öffnen Sie die Login-Seite, die automatisch erscheint, wenn Sie auf den Admin-Bereich zugreifen wollen: [http://localhost:5000/admin](http://localhost:5000/admin) + - Loggen Sie sich mit den Standard-Anmeldedaten ein: + - **Benutzername:** `admin` + - **Passwort:** `admin123` + - Nach dem Login können Sie im Admin-Bereich Websites hinzufügen, bearbeiten oder löschen. + - Über den Menüpunkt "Passwort ändern" können Sie Ihr Passwort aktualisieren. + +3. **Überprüfen Sie den Status:** + - Öffnen Sie die Hauptseite: [http://localhost:5000](http://localhost:5000) + - Die Seite zeigt den aktuellen Status der von Ihnen hinzugefügten Websites an. + - Die Statusüberprüfung findet alle 5 Minuten statt. Die Seite aktualisiert sich nicht automatisch; Sie müssen sie neu laden, um den neuesten Status zu sehen. \ No newline at end of file diff --git a/Uptime-Stats/requirements.txt b/Uptime-Stats/requirements.txt new file mode 100644 index 0000000..ba074af --- /dev/null +++ b/Uptime-Stats/requirements.txt @@ -0,0 +1,9 @@ +requests +Flask +Flask-SQLAlchemy +Flask-Admin==1.6.1 +Flask-Login +Flask-WTF +werkzeug +WTForms==3.0.1 +Flask-Migrate \ No newline at end of file diff --git a/Uptime-Stats/templates/admin/index.html b/Uptime-Stats/templates/admin/index.html new file mode 100644 index 0000000..e0827e9 --- /dev/null +++ b/Uptime-Stats/templates/admin/index.html @@ -0,0 +1,20 @@ +{% extends 'admin/master.html' %} + +{% block body %} +
+

Willkommen im Admin-Bereich

+

+ Dies ist der zentrale Verwaltungsbereich für Ihre Uptime-Statistiken. +

+

+ Klicken Sie auf den Button unten, um direkt einen neuen Monitor zur Überwachung hinzuzufügen. +

+ + Neuen Monitor hinzufügen + +
+

+ Alternativ können Sie die Menüpunkte in der Navigationsleiste oben verwenden, um alle Monitore oder Benutzer anzuzeigen und zu bearbeiten. +

+
+{% endblock %} diff --git a/Uptime-Stats/templates/change_password.html b/Uptime-Stats/templates/change_password.html new file mode 100644 index 0000000..250d7ea --- /dev/null +++ b/Uptime-Stats/templates/change_password.html @@ -0,0 +1,43 @@ + +{% extends 'layout.html' %} + +{% block title %}Passwort ändern - Uptime Stats{% endblock %} + +{% block content %} +
+
+
+
+

Passwort ändern

+
+ {{ form.hidden_tag() }} +
+ {{ form.current_password.label(class="form-label") }} + {{ form.current_password(class="form-control is-invalid" if form.current_password.errors else "form-control") }} + {% for error in form.current_password.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.new_password.label(class="form-label") }} + {{ form.new_password(class="form-control is-invalid" if form.new_password.errors else "form-control") }} + {% for error in form.new_password.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.confirm_password.label(class="form-label") }} + {{ form.confirm_password(class="form-control is-invalid" if form.confirm_password.errors else "form-control") }} + {% for error in form.confirm_password.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.submit(class_="btn btn-primary") }} +
+
+
+
+
+
+{% endblock %} diff --git a/Uptime-Stats/templates/index.html b/Uptime-Stats/templates/index.html new file mode 100644 index 0000000..bba47e5 --- /dev/null +++ b/Uptime-Stats/templates/index.html @@ -0,0 +1,78 @@ + +{% extends 'layout.html' %} + +{% block title %}Statusübersicht - Uptime Stats{% endblock %} + +{% block content %} +
+

Statusübersicht

+ + {% if not monitors %} +
+

Noch keine Monitore konfiguriert.

+

Bitte fügen Sie im Admin-Bereich einen Monitor hinzu, um mit der Überwachung zu beginnen.

+
+ {% else %} +
+ {% for monitor in monitors %} +
+
+
+
+ {{ monitor.name }} + + {# Manuelle Statusanzeige priorisieren #} + {% if monitor.status_override == 'MAINTENANCE' %} + Wartung + {% elif monitor.status_override == 'DEGRADED' %} + Probleme + {% elif monitor.status_override == 'OPERATIONAL' %} + Funktionsfähig + + {# Automatische Statusanzeige, wenn kein manueller Status gesetzt ist #} + {% elif monitor.is_up == True %} + Up + {% elif monitor.is_up == False %} + Down + {% else %} + Unbekannt + {% endif %} +
+ + {# Status-Nachricht anzeigen, wenn vorhanden #} + {% if monitor.status_override_message %} +
+ {{ monitor.status_override_message }} +
+ {% endif %} + +

+ {% if monitor.monitor_type == 'TCP' %} + {{ monitor.url }}:{{ monitor.port }} + {% else %} + {{ monitor.url }} + {% endif %} +

+
+ {{ monitor.monitor_type }} + {% if monitor.monitor_type == 'KEYWORD' and monitor.keyword %} + Keyword: {{ monitor.keyword }} + {% endif %} +
+
+ +
+
+ {% endfor %} +
+ {% endif %} +
+{% endblock %} diff --git a/Uptime-Stats/templates/layout.html b/Uptime-Stats/templates/layout.html new file mode 100644 index 0000000..dccf5da --- /dev/null +++ b/Uptime-Stats/templates/layout.html @@ -0,0 +1,94 @@ + + + + + + + {% block title %}Uptime Stats{% endblock %} + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + + + + diff --git a/Uptime-Stats/templates/login.html b/Uptime-Stats/templates/login.html new file mode 100644 index 0000000..2d404fc --- /dev/null +++ b/Uptime-Stats/templates/login.html @@ -0,0 +1,36 @@ + +{% extends 'layout.html' %} + +{% block title %}Login - Uptime Stats{% endblock %} + +{% block content %} +
+
+
+
+

Admin Login

+
+ {{ form.hidden_tag() }} +
+ {{ form.username.label(class="form-label") }} + {{ form.username(class="form-control is-invalid" if form.username.errors else "form-control") }} + {% for error in form.username.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.password.label(class="form-label") }} + {{ form.password(class="form-control is-invalid" if form.password.errors else "form-control") }} + {% for error in form.password.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.submit(class_="btn btn-primary") }} +
+
+
+
+
+
+{% endblock %} diff --git a/Uptime-Stats/uptime.db b/Uptime-Stats/uptime.db new file mode 100644 index 0000000000000000000000000000000000000000..89e47641dadb1076aebde697368f697decd07b14 GIT binary patch literal 12288 zcmeI#KTE?v7zXgWC~5*B-MYSGLPXHcf@cU=jH$gwq0^Dp2*Lc(HWgPL{7!x|M{~u3 zWa;Emo)_-IljDxuFVj02M|qRcW4$QTh6b$1IA<3`j4|Ghy>@I$=lxA+pE_?V&Q3l( z+xcdOV>8EgzP4?^Is_m90SG_<0uX=z1Rwwb2teT91>QY=*!O+@VK(Wk$kHM!XZc*c zW{YKBSDk1CG89QjiMWhH>MW9LnZMgqwJPae$lyvycW~xWJW43Oi=tz)%wQBx5-Emp z(mg>vKh5b@4sV2<(sei`_s@Br<((S`{jtwkUOi-Q%a@|<;#In8>UCC~&rzqH7rL)M z(w?@SdU`@Z00Izz00bZa0SG_<0uX=z1R$`h0+zlQ`u|=1y|@