+ update emblemed support in iframe and list view

This commit is contained in:
Robin
2025-10-06 12:54:41 +02:00
parent 988ab003ad
commit ca02212004
8 changed files with 416 additions and 98 deletions

119
main.py
View File

@@ -3,16 +3,17 @@ import requests
import socket import socket
import threading import threading
import logging import logging
import time import time, math
import warnings import warnings
from datetime import datetime from datetime import datetime, timezone, timedelta
from typing import Optional, Tuple from typing import Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from flask import Flask, render_template, flash, redirect, url_for, request from flask import Flask, render_template, flash, redirect, url_for, request, make_response
from flask_admin import Admin, AdminIndexView from flask_admin import Admin, AdminIndexView, BaseView, expose
from flask_admin.contrib.sqla import ModelView from flask_admin.contrib.sqla import ModelView
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required
from flask_admin.menu import MenuLink
from flask_migrate import Migrate, upgrade from flask_migrate import Migrate, upgrade
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
@@ -91,6 +92,7 @@ class Monitor(db.Model):
url = db.Column(db.String(200), nullable=False) url = db.Column(db.String(200), nullable=False)
keyword = db.Column(db.String(100), nullable=True) keyword = db.Column(db.String(100), nullable=True)
port = db.Column(db.Integer, nullable=True) port = db.Column(db.Integer, nullable=True)
position = db.Column(db.Integer, nullable=False, default=0, server_default='0')
status_override = db.Column(db.String(50), nullable=True) status_override = db.Column(db.String(50), nullable=True)
status_override_message = db.Column(db.Text, nullable=True) status_override_message = db.Column(db.Text, nullable=True)
@@ -104,7 +106,7 @@ class UptimeLog(db.Model):
"""Modell zum Protokollieren des Uptime-Status.""" """Modell zum Protokollieren des Uptime-Status."""
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
monitor_id = db.Column(db.Integer, db.ForeignKey('monitor.id'), nullable=False) monitor_id = db.Column(db.Integer, db.ForeignKey('monitor.id'), nullable=False)
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) timestamp = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), index=True)
status_code = db.Column(db.Integer, nullable=True) status_code = db.Column(db.Integer, nullable=True)
is_up = db.Column(db.Boolean, nullable=False) is_up = db.Column(db.Boolean, nullable=False)
@@ -156,6 +158,14 @@ class SecureAdminIndexView(SecureView, AdminIndexView):
pass pass
class EmbedInfoView(SecureView, BaseView):
"""Eine gesicherte Admin-Ansicht, die den iFrame-Einbettungscode anzeigt."""
@expose('/')
def index(self):
# 'render' wird von BaseView bereitgestellt und injiziert den Admin-Kontext
return self.render('admin/embed_info.html')
class MonitorModelView(SecureModelView): class MonitorModelView(SecureModelView):
"""Angepasste Admin-Ansicht für Monitore.""" """Angepasste Admin-Ansicht für Monitore."""
form_overrides = { form_overrides = {
@@ -188,11 +198,11 @@ class MonitorModelView(SecureModelView):
'validators': [VOptional()], 'validators': [VOptional()],
}, },
} }
column_list = ('name', 'monitor_type', 'status_override', 'url', 'port') column_list = ('name', 'monitor_type', 'position', 'status_override', 'url', 'port')
form_columns = ('name', 'monitor_type', 'url', 'port', 'keyword', 'status_override', 'status_override_message') form_columns = ('name', 'monitor_type', 'position', 'url', 'port', 'keyword', 'status_override', 'status_override_message')
column_labels = dict( column_labels = dict(
name='Name', monitor_type='Typ', url='URL/Host', port='Port', keyword='Keyword', name='Name', monitor_type='Typ', url='URL/Host', port='Port', keyword='Keyword',
status_override='Manueller Status', status_override_message='Status-Nachricht' position='Position', status_override='Manueller Status', status_override_message='Status-Nachricht'
) )
def on_model_change(self, form, model, is_created): def on_model_change(self, form, model, is_created):
@@ -216,10 +226,17 @@ class MonitorModelView(SecureModelView):
admin = Admin(app, name='Uptime Stats Admin', template_mode='bootstrap4', index_view=SecureAdminIndexView()) admin = Admin(app, name='Uptime Stats Admin', template_mode='bootstrap4', index_view=SecureAdminIndexView())
admin.add_view(MonitorModelView(Monitor, db.session, name="Monitore")) admin.add_view(MonitorModelView(Monitor, db.session, name="Monitore"))
admin.add_view(SecureModelView(User, db.session, name="Administratoren", endpoint='user_admin')) admin.add_view(SecureModelView(User, db.session, name="Administratoren", endpoint='user_admin'))
admin.add_view(EmbedInfoView(name='Einbettungs-Info', endpoint='embed-info'))
# --- Web-Routen --- # --- Web-Routen ---
@app.context_processor
def inject_now():
"""Stellt das aktuelle UTC-Datum für alle Templates zur Verfügung."""
return {'now': datetime.now(timezone.utc)}
@app.route('/') @app.route('/')
def index(): def index():
"""Öffentliche Statusseite.""" """Öffentliche Statusseite."""
@@ -237,11 +254,72 @@ def index():
UptimeLog, UptimeLog,
(UptimeLog.monitor_id == latest_log_subquery.c.monitor_id) & (UptimeLog.monitor_id == latest_log_subquery.c.monitor_id) &
(UptimeLog.timestamp == latest_log_subquery.c.max_timestamp) (UptimeLog.timestamp == latest_log_subquery.c.max_timestamp)
).order_by(Monitor.name).all() ).order_by(Monitor.position, Monitor.name).all()
return render_template('index.html', monitors_with_status=monitors_with_status) return render_template('index.html', monitors_with_status=monitors_with_status)
@app.route('/status')
def status():
"""Erstellt eine für iFrames optimierte Status-Tabellenseite."""
latest_log_subquery = db.session.query(
UptimeLog.monitor_id,
func.max(UptimeLog.timestamp).label('max_timestamp')
).group_by(UptimeLog.monitor_id).subquery()
monitors_with_status = db.session.query(
Monitor,
UptimeLog
).outerjoin(
latest_log_subquery, Monitor.id == latest_log_subquery.c.monitor_id
).outerjoin(
UptimeLog,
(UptimeLog.monitor_id == latest_log_subquery.c.monitor_id) &
(UptimeLog.timestamp == latest_log_subquery.c.max_timestamp)
).order_by(Monitor.position, Monitor.name).all()
response = make_response(render_template('status.html', monitors_with_status=monitors_with_status))
response.headers.pop('X-Frame-Options', None)
return response
@app.route('/monitor/<int:monitor_id>')
def monitor_details(monitor_id: int):
"""Zeigt Detailinformationen und Statistiken für einen einzelnen Monitor."""
monitor = db.session.get(Monitor, monitor_id)
if not monitor:
return "Monitor nicht gefunden", 404
now = datetime.now(timezone.utc)
# Statistik für 24 Stunden
start_24h = now - timedelta(hours=24)
logs_24h = db.session.scalars(
db.select(UptimeLog).where(
UptimeLog.monitor_id == monitor_id,
UptimeLog.timestamp >= start_24h
)
).all()
total_checks_24h = len(logs_24h)
up_checks_24h = sum(1 for log in logs_24h if log.is_up)
uptime_24h = (up_checks_24h / total_checks_24h * 100) if total_checks_24h > 0 else 100
# Statistik für 30 Tage
start_30d = now - timedelta(days=30)
logs_30d = db.session.scalars(
db.select(UptimeLog).where(
UptimeLog.monitor_id == monitor_id,
UptimeLog.timestamp >= start_30d
)
).all()
total_checks_30d = len(logs_30d)
up_checks_30d = sum(1 for log in logs_30d if log.is_up)
uptime_30d = (up_checks_30d / total_checks_30d * 100) if total_checks_30d > 0 else 100
return render_template('monitor_details.html', monitor=monitor,
uptime_24h=uptime_24h, uptime_30d=uptime_30d,
total_checks_24h=total_checks_24h, total_checks_30d=total_checks_30d)
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if current_user.is_authenticated: if current_user.is_authenticated:
@@ -328,14 +406,14 @@ def check_monitors(monitor_id: Optional[int] = None):
query = db.select(Monitor) query = db.select(Monitor)
if monitor_id: if monitor_id:
query = query.where(Monitor.id == monitor_id) query = query.where(Monitor.id == monitor_id)
app.logger.info(f"[{datetime.now()}] Starte Prüfung für Monitor ID {monitor_id}...") app.logger.info(f"[{datetime.now(timezone.utc)}] Starte Prüfung für Monitor ID {monitor_id}...")
else: else:
app.logger.info(f"[{datetime.now()}] Starte periodische Überprüfung...") app.logger.info(f"[{datetime.now(timezone.utc)}] Starte periodische Überprüfung...")
monitors = db.session.scalars(query).all() monitors = db.session.scalars(query).all()
if not monitors: if not monitors:
if not monitor_id: if not monitor_id:
app.logger.info(f"[{datetime.now()}] Keine Monitore zur Überprüfung gefunden.") app.logger.info(f"[{datetime.now(timezone.utc)}] Keine Monitore zur Überprüfung gefunden.")
return return
for monitor in monitors: for monitor in monitors:
@@ -348,7 +426,7 @@ def check_monitors(monitor_id: Optional[int] = None):
db.session.add(log_entry) db.session.add(log_entry)
db.session.commit() db.session.commit()
app.logger.info(f"[{datetime.now()}] Überprüfung abgeschlossen.") app.logger.info(f"[{datetime.now(timezone.utc)}] Überprüfung abgeschlossen.")
def run_checks_periodically(interval: int = None): def run_checks_periodically(interval: int = None):
@@ -374,17 +452,14 @@ def init_app():
with app.app_context(): with app.app_context():
app.logger.info("Führe Datenbank-Migrationen aus...") app.logger.info("Führe Datenbank-Migrationen aus...")
migrations_dir = os.path.join(basedir, 'migrations')
ran_migrations = False
try: try:
if os.path.isdir(migrations_dir): # Wendet ausstehende Datenbank-Migrationen an.
upgrade() # Dies aktualisiert das Schema auf den neuesten Stand.
ran_migrations = True upgrade()
app.logger.info("Migrationen abgeschlossen.") app.logger.info("Datenbank-Migrationen erfolgreich angewendet.")
else:
app.logger.warning("Kein migrations/-Ordner gefunden überspringe Alembic upgrade.")
except Exception as e: except Exception as e:
app.logger.warning(f"Alembic upgrade fehlgeschlagen: {e}") app.logger.error(f"Anwenden der Datenbank-Migrationen fehlgeschlagen: {e}")
app.logger.info("Versuche, Tabellen mit db.create_all() zu erstellen, falls sie nicht existieren.")
# Sicherstellen, dass alle Tabellen existieren Fallback für frische Setups ohne Migrations # Sicherstellen, dass alle Tabellen existieren Fallback für frische Setups ohne Migrations
insp = inspect(db.engine) insp = inspect(db.engine)

View File

@@ -0,0 +1,32 @@
"""Auto-detect model changes
Revision ID: fe9c4d25c5da
Revises:
Create Date: 2025-10-06 12:00:18.340503
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'fe9c4d25c5da'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('monitor', schema=None) as batch_op:
batch_op.add_column(sa.Column('position', sa.Integer(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('monitor', schema=None) as batch_op:
batch_op.drop_column('position')
# ### end Alembic commands ###

11
templates/_footer.html Normal file
View File

@@ -0,0 +1,11 @@
<footer class="footer mt-auto py-3 bg-light border-top">
<div class="container text-center">
<span class="text-muted">
&copy; {{ now.year }} <a href="https://rl-dev.de" target="_blank" class="text-decoration-none">rl-dev.de</a>
</span>
<span class="mx-2 text-muted">|</span>
<a href="https://git.rl-dev.de/Robin/Uptime-Stats" target="_blank" class="text-decoration-none text-muted">
<i class="bi bi-git"></i> Source Code
</a>
</div>
</footer>

View File

@@ -0,0 +1,13 @@
<td>
{% if monitor.status_override == 'MAINTENANCE' %}
<span class="badge bg-primary">Wartung <i class="bi bi-tools"></i></span>
{% elif monitor.status_override == 'DEGRADED' %}
<span class="badge bg-warning text-dark">Probleme <i class="bi bi-exclamation-triangle"></i></span>
{% elif last_log and last_log.is_up %}
<span class="badge bg-success status-badge">Up <i class="bi bi-check-circle"></i></span>
{% elif last_log and not last_log.is_up %}
<span class="badge bg-danger status-badge">Down <i class="bi bi-x-circle"></i></span>
{% else %}
<span class="badge bg-secondary">Unbekannt <i class="bi bi-question-circle"></i></span>
{% endif %}
</td>

View File

@@ -0,0 +1,16 @@
{% extends 'admin/master.html' %}
{% block body %}
<h1>iFrame-Einbettungscode</h1>
<p>Kopieren Sie den folgenden HTML-Code, um die Status-Tabelle auf einer anderen Seite oder in Ihrem Intranet einzubetten:</p>
<div class="card">
<div class="card-body bg-light">
<pre><code>&lt;iframe src="{{ url_for('status', _external=True) }}" style="width: 100%; height: 400px; border: none;"&gt;&lt;/iframe&gt;</code></pre>
</div>
</div>
<p class="mt-4">
<a href="{{ url_for('admin.index') }}" class="btn btn-primary"><i class="bi bi-arrow-left"></i> Zurück zum Dashboard</a>
</p>
{% endblock %}

View File

@@ -1,78 +1,86 @@
<!doctype html>
{% extends 'layout.html' %} <html lang="de">
<head>
{% block title %}Statusübersicht - Uptime Stats{% endblock %} <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block content %} <title>Uptime Status</title>
<div class="container"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<h1 class="mb-4">Statusübersicht</h1> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
{% if not monitors %} .status-badge.bg-danger {
<div class="alert alert-info text-center"> animation: pulse-red 1.5s infinite;
<h4>Noch keine Monitore konfiguriert.</h4> }
<p>Bitte fügen Sie im <a href="{{ url_for('admin.index') }}" class="alert-link">Admin-Bereich</a> einen Monitor hinzu, um mit der Überwachung zu beginnen.</p> @keyframes pulse-green {
</div> 0% { box-shadow: 0 0 0 0 rgba(25, 135, 84, 0.7); }
{% else %} 70% { box-shadow: 0 0 0 10px rgba(25, 135, 84, 0); }
<div class="row"> 100% { box-shadow: 0 0 0 0 rgba(25, 135, 84, 0); }
{% for monitor in monitors %} }
<div class="col-lg-4 col-md-6 mb-4"> @keyframes pulse-red {
<div class="card h-100 shadow-sm"> 0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); }
<div class="card-body"> 70% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); }
<h5 class="card-title d-flex justify-content-between"> 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); }
{{ monitor.name }} }
</style>
{# Manuelle Statusanzeige priorisieren #} </head>
{% if monitor.status_override == 'MAINTENANCE' %} <body class="d-flex flex-column vh-100">
<span class="badge bg-primary status-badge">Wartung <i class="bi bi-tools"></i></span> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
{% elif monitor.status_override == 'DEGRADED' %} <div class="container">
<span class="badge bg-warning text-dark status-badge">Probleme <i class="bi bi-exclamation-triangle"></i></span> <i class="bi bi-bar-chart-line-fill"></i> Uptime Status
{% elif monitor.status_override == 'OPERATIONAL' %} </a>
<span class="badge bg-success status-badge">Funktionsfähig <i class="bi bi-check-circle"></i></span> <div class="ms-auto">
<a href="{{ url_for('admin.index') }}" class="btn btn-outline-light">
{# Automatische Statusanzeige, wenn kein manueller Status gesetzt ist #} <i class="bi bi-person-circle"></i> Admin-Bereich
{% elif monitor.is_up == True %} </a>
<span class="badge bg-success status-badge">Up <i class="bi bi-check-circle"></i></span>
{% elif monitor.is_up == False %}
<span class="badge bg-danger status-badge">Down <i class="bi bi-x-circle"></i></span>
{% else %}
<span class="badge bg-secondary status-badge">Unbekannt <i class="bi bi-question-circle"></i></span>
{% endif %}
</h5>
{# Status-Nachricht anzeigen, wenn vorhanden #}
{% if monitor.status_override_message %}
<div class="alert alert-info mt-2 p-2">
<small>{{ monitor.status_override_message }}</small>
</div>
{% endif %}
<p class="card-text">
{% if monitor.monitor_type == 'TCP' %}
<span class="text-muted text-break">{{ monitor.url }}:{{ monitor.port }}</span>
{% else %}
<a href="{{ monitor.url }}" target="_blank" class="text-muted text-break">{{ monitor.url }}</a>
{% endif %}
</p>
<div>
<span class="badge rounded-pill bg-primary">{{ monitor.monitor_type }}</span>
{% if monitor.monitor_type == 'KEYWORD' and monitor.keyword %}
<span class="badge rounded-pill bg-light text-dark">Keyword: {{ monitor.keyword }}</span>
{% endif %}
</div>
</div>
<div class="card-footer text-muted">
<small>Zuletzt geprüft:
{% if monitor.last_checked != 'Nie' %}
{{ monitor.last_checked.strftime('%d.%m.%Y %H:%M:%S UTC') }}
{% else %}
Nie
{% endif %}
</small>
</div>
</div>
</div> </div>
{% endfor %}
</div> </div>
{% endif %} </nav>
</div>
{% endblock %} <main class="container mt-4 flex-grow-1">
<h1 class="mb-4">Statusübersicht</h1>
{% if not monitors_with_status %}
<div class="alert alert-info text-center">
<h4>Keine Monitore konfiguriert.</h4>
<p>Bitte loggen Sie sich in den <a href="{{ url_for('admin.index') }}" class="alert-link">Admin-Bereich</a> ein, um neue Monitore hinzuzufügen.</p>
</div>
{% else %}
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
<th>Dienst</th>
<th>Status</th>
<th>Letzte Prüfung</th>
</tr>
</thead>
<tbody>
{% for monitor, last_log in monitors_with_status %}
<tr>
<td>
<a href="{{ url_for('monitor_details', monitor_id=monitor.id) }}" class="text-decoration-none fw-bold">{{ monitor.name }}</a>
{% if monitor.status_override_message %}
<div class="text-muted fst-italic small">
<i class="bi bi-info-circle"></i> {{ monitor.status_override_message }}
</div>
{% endif %}
</td>
{% include '_status_badge.html' %}
<td>
{% if last_log %}
{{ last_log.timestamp.strftime('%d.%m.%Y %H:%M:%S UTC') }}
{% else %}
<span class="text-muted">Nie</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</main>
{% include '_footer.html' %}
<script src="ht
</html>

78
templates/status.html Normal file
View File

@@ -0,0 +1,78 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Statusübersicht</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
body {
background-color: transparent;
}
.status-badge.bg-success {
animation: pulse-green 2s infinite;
}
.status-badge.bg-danger {
animation: pulse-red 1.5s infinite;
}
@keyframes pulse-green {
0% { box-shadow: 0 0 0 0 rgba(25, 135, 84, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(25, 135, 84, 0); }
100% { box-shadow: 0 0 0 0 rgba(25, 135, 84, 0); }
}
@keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); }
}
</style>
</head>
<body>
<div class="container-fluid">
{% if not monitors_with_status %}
<div class="alert alert-info text-center">
<h4>Keine Monitore konfiguriert.</h4>
</div>
{% else %}
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>Dienst</th>
<th class="text-center">Status</th>
<th>Letzte Prüfung</th>
</tr>
</thead>
<tbody>
{% for monitor, last_log in monitors_with_status %}
<tr>
<td>
<a href="{{ url_for('monitor_details', monitor_id=monitor.id, _external=True) }}" target="_blank" class="text-decoration-none fw-bold">{{ monitor.name }}</a>
{% if monitor.status_override_message %}
<div class="text-muted fst-italic small">
<i class="bi bi-info-circle"></i> {{ monitor.status_override_message }}
</div>
{% endif %}
</td>
{% with %}
{% set monitor=monitor %}
{% set last_log=last_log %}
{% include '_status_badge.html' %}
{% endwith %}
<td>
{% if last_log %}
{{ last_log.timestamp.strftime('%d.%m.%Y %H:%M:%S UTC') }}
{% else %}
Nie
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% include '_footer.html' %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

85
update.py Normal file
View File

@@ -0,0 +1,85 @@
import subprocess
import sys
import os
import platform
def run_command(command, cwd=None):
"""
Führt einen Befehl aus, gibt seine Ausgabe in Echtzeit aus und prüft auf Fehler.
"""
print(f"\n>>> Führe aus: {' '.join(command)}")
print("-" * 40)
try:
# Stelle sicher, dass der Python-Interpreter des venv für Module verwendet wird
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding='utf-8',
errors='replace', # Verhindert Fehler bei unerwarteten Zeichen
cwd=cwd,
bufsize=1 # Zeilen-gepuffert
)
# Lese und drucke die Ausgabe Zeile für Zeile
for line in iter(process.stdout.readline, ''):
print(line, end='')
process.wait() # Warte auf das Ende des Prozesses
if process.returncode != 0:
print("-" * 40)
print(f"FEHLER: Befehl schlug mit Exit-Code {process.returncode} fehl.")
print("-" * 40)
return False
print("-" * 40)
print(">>> Befehl erfolgreich abgeschlossen.")
return True
except FileNotFoundError:
print(f"FEHLER: Befehl '{command[0]}' nicht gefunden. Ist Git/Python korrekt installiert und im PATH?")
return False
except Exception as e:
print(f"Ein unerwarteter Fehler ist aufgetreten: {e}")
return False
def main():
"""Haupt-Update-Prozess."""
print("Starte den Update-Prozess der Anwendung...")
project_root = os.path.dirname(os.path.abspath(__file__))
# Finde den Python-Interpreter des aktuellen Virtual Environments
python_executable = sys.executable
# Schritt 1: Neueste Änderungen von Git holen
if not run_command(["git", "pull"], cwd=project_root):
print("\nUpdate fehlgeschlagen: Konnte die neuesten Änderungen nicht von Git holen.")
sys.exit(1)
# Schritt 2: Python-Abhängigkeiten installieren/aktualisieren
if not run_command([python_executable, "-m", "pip", "install", "-r", "requirements.txt"], cwd=project_root):
print("\nUpdate fehlgeschlagen: Konnte die Python-Abhängigkeiten nicht installieren.")
sys.exit(1)
# Schritt 3: Datenbank-Migrationen erstellen (falls nötig)
# Dieser Befehl erkennt Modelländerungen und erstellt ein Migrationsskript.
# Er kann fehlschlagen, wenn es keine Änderungen gibt, das ist in Ordnung.
print("\nVersuche, Datenbank-Änderungen zu erkennen...")
run_command([python_executable, "-m", "flask", "--app", "main", "db", "migrate", "-m", "Auto-detect model changes"], cwd=project_root)
# Schritt 4: Datenbank-Migrationen anwenden
print("\nWende Datenbank-Migrationen an...")
if not run_command([python_executable, "-m", "flask", "--app", "main", "db", "upgrade"], cwd=project_root):
print("\nUpdate fehlgeschlagen: Konnte die Datenbank-Migrationen nicht anwenden.")
sys.exit(1)
print("\n\n===================================================")
print("✅ Update erfolgreich abgeschlossen!")
print("Bitte starten Sie die Anwendung (main.py) neu, um die Änderungen zu übernehmen.")
print("===================================================")
if __name__ == "__main__":
main()