From d7e435f18ed3d18155f8c58180f69556413365af Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 5 Oct 2025 23:28:05 +0200 Subject: [PATCH] uploade --- .gitignore | 14 ++ data.md | 281 ++++++++++++++++++++++++++++++ main.py | 302 +++++++++++++++++++++++++++++++++ migrations/README | 1 + migrations/alembic.ini | 50 ++++++ migrations/env.py | 113 ++++++++++++ migrations/script.py.mako | 24 +++ readme.md | 47 +++++ requirements.txt | 9 + templates/admin/index.html | 20 +++ templates/change_password.html | 43 +++++ templates/index.html | 78 +++++++++ templates/layout.html | 94 ++++++++++ templates/login.html | 36 ++++ 14 files changed, 1112 insertions(+) create mode 100644 .gitignore create mode 100644 data.md create mode 100644 main.py create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 readme.md create mode 100644 requirements.txt create mode 100644 templates/admin/index.html create mode 100644 templates/change_password.html create mode 100644 templates/index.html create mode 100644 templates/layout.html create mode 100644 templates/login.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..973e1fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Environment +.env +venv/ +env/ + +# IDEs +.idea/ +.vscode/ diff --git a/data.md b/data.md new file mode 100644 index 0000000..ce722a6 --- /dev/null +++ b/data.md @@ -0,0 +1,281 @@ +Selbst gehostetes Monitoring-Tool +Du betreibst Uptime Kuma auf deiner eigenen Infrastruktur +Libre Self-hosted ++3 +uptime.kuma.pet ++3 +uptimekuma.org ++3 + +Verschiedene Monitor-Typen +Uptime Kuma unterstützt viele Arten von Überwachungen: + +HTTP / HTTPS (Standard-Webseiten) +uptimekuma.org ++6 +uptimekuma.org ++6 +uptimekuma.org ++6 + +HTTP(s) mit Keyword-Abfragen (z. B. “enthält dieses Wort auf der Seite”) +uptimekuma.org ++2 +mielke.de ++2 + +HTTP(s) JSON-Abfragen (z. B. API-Antworten) +uptimekuma.org ++3 +uptimekuma.org ++3 +GitHub ++3 + +TCP-Port-Checks (z. B. ein Dienst auf Port 22, 3306 etc.) +GitHub ++4 +uptimekuma.org ++4 +mielke.de ++4 + +Ping (ICMP) Überwachung (Latenz, ob Host erreichbar) +uptimekuma.org ++2 +mielke.de ++2 + +DNS-Eintragsüberwachung (z. B. überprüfe, ob DNS-A-Einträge sich ändern oder korrekt sind) +uptimekuma.org ++2 +mielke.de ++2 + +Push-Monitor: Für Dienste, die nicht „standardmäßig“ abgefragt werden können, sendest du selbst einen „Heartbeat“ an Uptime Kuma. +GitHub ++2 +uptimekuma.org ++2 + +Steam Game Server Überwachung +GitHub ++3 +uptimekuma.org ++3 +uptimekuma.org ++3 + +Überwachung von Docker-Containern +uptime.kuma.pet ++4 +uptimekuma.org ++4 +GitHub ++4 + +Datenbankmonitoring (z. B. MySQL / SQL) +uptimekuma.org ++4 +uptimekuma.org ++4 +GitHub ++4 + +Überwachungsintervalle & Wiederholungen + +Minimales Intervall: 20 Sekunden +uptimekuma.org ++4 +uptimekuma.org ++4 +mielke.de ++4 + +Standardintervall typischerweise 60 Sekunden (kann angepasst werden) +uptimekuma.org ++1 + +Einstellung, wie oft Wiederholungen erfolgen müssen, bevor ein Ausfall gemeldet wird (Retries) +mielke.de ++2 +uptimekuma.org ++2 + +„Reverse Mode“ (Umgekehrter Modus): Der Monitor ist aktiv, wenn das Ziel nicht erreichbar ist, z. B. um eine Backup-Leitung zu überwachen. +mielke.de + +Status-Dashboard & Visualisierung + +Echtzeit-Statusanzeige aller Monitore im Dashboard +blog.matt-vdv.me ++3 +uptimekuma.org ++3 +uptime.kuma.pet ++3 + +Graphen (z. B. Ping-Graph, Antwortzeiten) und historische Verlaufdaten +GitHub ++4 +uptimekuma.org ++4 +mielke.de ++4 + +Uptime / Ausfallstatistiken über Zeiträume +uptime.kuma.pet ++3 +uptimekuma.org ++3 +uptimekuma.org ++3 + +Statusseiten (Status Pages) + +Erstelle eine oder mehrere öffentlich zugängliche Statusseiten, um Nutzern oder Kunden den Status deiner Services anzuzeigen. +mielke.de ++4 +uptimekuma.org ++4 +GitHub ++4 + +Jede Statusseite kann individuell konfiguriert werden (Name, Slug, Monitore, Beschreibung) +GitHub ++2 +mielke.de ++2 + +Statusseiten werden im Cache 5 Minuten gespeichert, und die Seite refresht sich alle 5 Minuten. +GitHub + +Möglichkeit, Domains für Statusseiten zu definieren (z. B. status.deinedomain.tld) +GitHub + +Öffentlich (ohne Auth) oder eingeschränkt via Basic Auth (je nach Einstellungen) +GitHub ++1 + +Benachrichtigungen / Alerts + +Unterstützt über 90 Notification Channels (z. B. E-Mail (SMTP), Telegram, Discord, Slack, Signal, Webhook, etc.) +GitHub ++4 +betterstack.com ++4 +uptimekuma.org ++4 + +Testfunktion für Benachrichtigungskanäle in der Konfiguration +PikaPods Docs ++2 +uptimekuma.org ++2 + +Individuelle Auswahl je Monitor, über welchen Kanal benachrichtigt werden soll +mielke.de ++2 +uptimekuma.org ++2 + +Option (in Entwicklung / Diskussion), Benachrichtigungen nur bei Fehler („Only on Down“) statt auch bei Up oder „Recoveries“. +GitHub + +Sicherheit & Authentifizierung + +Zwei-Faktor-Authentifizierung (2FA) für das Uptime Kuma Dashboard +uptimekuma.org + +Proxy-Unterstützung (z. B. Cloudflare, Caddy, HAProxy, Traefik, Nginx) +uptimekuma.org ++1 + +API Keys: Für Zugriff auf Prometheus-Metriken etc. +uptimekuma.org ++1 + +REST-API & Socket.io Schnittstellen +GitHub + +Badge & öffentliche API-Daten + +Generiere Badges (z. B. für Monitor-Status) für Webseiten / Statusseiten. +GitHub ++2 +uptimekuma.org ++2 + +Öffentliche Statusseite-API-Endpunkte zur Abfrage von Statusdaten (bei veröffentlichten Statusseiten) +GitHub ++2 +GitHub ++2 + +Prometheus-Metrik-Endpunkt (mit Auth / API-Key Option) +GitHub ++1 + +Internationalisierung / Anpassung + +Mehr als 20 Sprachen unterstützt (u. a. Deutsch) +uptimekuma.org + +Dunkelmodus / Hellmodus / Automatik je nach Systemtheme +uptimekuma.org + +Anpassbare CSS / Layout-Anpassungen (über öffentliche Statusseite, Dashboard) (teilweise / mit Customizations) + +Backup & Wiederherstellung + +Möglichkeit, Daten und Konfiguration zu sichern / wiederherzustellen (Backup-Funktion) +blog.matt-vdv.me + +Einstellungen für Proxy, Sicherheit, Notifications, allgemeine Einstellungen etc. gruppiert in Menüs +blog.matt-vdv.me + +Reverse Proxy & Netzwerkintegration + +Unterstützung, Uptime Kuma hinter einem Reverse Proxy zu betreiben (weitergeleitete Header etc.) +blog.matt-vdv.me ++3 +uptimekuma.org ++3 +mielke.de ++3 + +Umgang mit HTTP Basic Auth (z. B. wenn eine Webanwendung Grund-Auth nutzt) +Stack Overflow + +Weitere Funktionen / kleine Zahnräder + +„Ping Charts“ mit Interaktivität +uptimekuma.org ++2 +blog.matt-vdv.me ++2 + +Filter, Suchfunktionen, Sortieren von Monitoren (Dashboard-Funktionalität) (teilweise vorhanden, aber mit Beschränkungen) +Reddit ++1 + +Status-Historie und Vorfälle (Incidents) in Statusseiten (wenn aktiviert) +GitHub ++2 +uptimekuma.org ++2 + +Möglichkeit, Monitore zu gruppieren oder in Ordnern / Abschnitten darzustellen (Dashboard-Organisation) + +Anbindung an Home Assistant (Integration) laut Erfahrungsberichten +mielke.de + +API-Dokumentation (intern / inoffiziell) + +Uptime Kuma hat eine API-Dokumentation im Wiki, inkl. Endpunkte für Push Monitore, Status Page, Metrics etc. +GitHub ++2 +uptimekuma.org ++2 + +Hinweis: Diese API ist primär für die eigene App gedacht und kann zwischen Versionen brechen. \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..8c8356d --- /dev/null +++ b/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/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1534acf --- /dev/null +++ b/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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ba074af --- /dev/null +++ b/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/templates/admin/index.html b/templates/admin/index.html new file mode 100644 index 0000000..e0827e9 --- /dev/null +++ b/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/templates/change_password.html b/templates/change_password.html new file mode 100644 index 0000000..250d7ea --- /dev/null +++ b/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/templates/index.html b/templates/index.html new file mode 100644 index 0000000..bba47e5 --- /dev/null +++ b/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/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..dccf5da --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,94 @@ + + + + + + + {% block title %}Uptime Stats{% endblock %} + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..2d404fc --- /dev/null +++ b/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 %}