+ update emblemed support in iframe and list view
This commit is contained in:
117
main.py
117
main.py
@@ -3,16 +3,17 @@ import requests
|
||||
import socket
|
||||
import threading
|
||||
import logging
|
||||
import time
|
||||
import time, math
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from flask import Flask, render_template, flash, redirect, url_for, request
|
||||
from flask_admin import Admin, AdminIndexView
|
||||
from flask import Flask, render_template, flash, redirect, url_for, request, make_response
|
||||
from flask_admin import Admin, AdminIndexView, BaseView, expose
|
||||
from flask_admin.contrib.sqla import ModelView
|
||||
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_sqlalchemy import SQLAlchemy
|
||||
from flask_wtf import FlaskForm
|
||||
@@ -91,6 +92,7 @@ class Monitor(db.Model):
|
||||
url = db.Column(db.String(200), nullable=False)
|
||||
keyword = db.Column(db.String(100), 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_message = db.Column(db.Text, nullable=True)
|
||||
|
||||
@@ -104,7 +106,7 @@ 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, index=True)
|
||||
timestamp = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), index=True)
|
||||
status_code = db.Column(db.Integer, nullable=True)
|
||||
is_up = db.Column(db.Boolean, nullable=False)
|
||||
|
||||
@@ -156,6 +158,14 @@ class SecureAdminIndexView(SecureView, AdminIndexView):
|
||||
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):
|
||||
"""Angepasste Admin-Ansicht für Monitore."""
|
||||
form_overrides = {
|
||||
@@ -188,11 +198,11 @@ class MonitorModelView(SecureModelView):
|
||||
'validators': [VOptional()],
|
||||
},
|
||||
}
|
||||
column_list = ('name', 'monitor_type', 'status_override', 'url', 'port')
|
||||
form_columns = ('name', 'monitor_type', 'url', 'port', 'keyword', 'status_override', 'status_override_message')
|
||||
column_list = ('name', 'monitor_type', 'position', 'status_override', 'url', 'port')
|
||||
form_columns = ('name', 'monitor_type', 'position', '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'
|
||||
position='Position', status_override='Manueller Status', status_override_message='Status-Nachricht'
|
||||
)
|
||||
|
||||
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.add_view(MonitorModelView(Monitor, db.session, name="Monitore"))
|
||||
admin.add_view(SecureModelView(User, db.session, name="Administratoren", endpoint='user_admin'))
|
||||
admin.add_view(EmbedInfoView(name='Einbettungs-Info', endpoint='embed-info'))
|
||||
|
||||
|
||||
# --- 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('/')
|
||||
def index():
|
||||
"""Öffentliche Statusseite."""
|
||||
@@ -237,11 +254,72 @@ def index():
|
||||
UptimeLog,
|
||||
(UptimeLog.monitor_id == latest_log_subquery.c.monitor_id) &
|
||||
(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)
|
||||
|
||||
|
||||
@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'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
@@ -328,14 +406,14 @@ def check_monitors(monitor_id: Optional[int] = None):
|
||||
query = db.select(Monitor)
|
||||
if 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:
|
||||
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()
|
||||
if not monitors:
|
||||
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
|
||||
|
||||
for monitor in monitors:
|
||||
@@ -348,7 +426,7 @@ def check_monitors(monitor_id: Optional[int] = None):
|
||||
db.session.add(log_entry)
|
||||
|
||||
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):
|
||||
@@ -374,17 +452,14 @@ def init_app():
|
||||
|
||||
with app.app_context():
|
||||
app.logger.info("Führe Datenbank-Migrationen aus...")
|
||||
migrations_dir = os.path.join(basedir, 'migrations')
|
||||
ran_migrations = False
|
||||
try:
|
||||
if os.path.isdir(migrations_dir):
|
||||
# Wendet ausstehende Datenbank-Migrationen an.
|
||||
# Dies aktualisiert das Schema auf den neuesten Stand.
|
||||
upgrade()
|
||||
ran_migrations = True
|
||||
app.logger.info("Migrationen abgeschlossen.")
|
||||
else:
|
||||
app.logger.warning("Kein migrations/-Ordner gefunden – überspringe Alembic upgrade.")
|
||||
app.logger.info("Datenbank-Migrationen erfolgreich angewendet.")
|
||||
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
|
||||
insp = inspect(db.engine)
|
||||
|
@@ -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
11
templates/_footer.html
Normal 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">
|
||||
© {{ 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>
|
13
templates/_status_badge.html
Normal file
13
templates/_status_badge.html
Normal 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>
|
16
templates/admin/embed_info.html
Normal file
16
templates/admin/embed_info.html
Normal 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><iframe src="{{ url_for('status', _external=True) }}" style="width: 100%; height: 400px; border: none;"></iframe></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 %}
|
@@ -1,78 +1,86 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Uptime Status</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>
|
||||
.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 class="d-flex flex-column vh-100">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<i class="bi bi-bar-chart-line-fill"></i> Uptime Status
|
||||
</a>
|
||||
<div class="ms-auto">
|
||||
<a href="{{ url_for('admin.index') }}" class="btn btn-outline-light">
|
||||
<i class="bi bi-person-circle"></i> Admin-Bereich
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% extends 'layout.html' %}
|
||||
|
||||
{% block title %}Statusübersicht - Uptime Stats{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<main class="container mt-4 flex-grow-1">
|
||||
<h1 class="mb-4">Statusübersicht</h1>
|
||||
|
||||
{% if not monitors %}
|
||||
{% if not monitors_with_status %}
|
||||
<div class="alert alert-info text-center">
|
||||
<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>
|
||||
<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="row">
|
||||
{% for monitor in monitors %}
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card h-100 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title d-flex justify-content-between">
|
||||
{{ monitor.name }}
|
||||
|
||||
{# Manuelle Statusanzeige priorisieren #}
|
||||
{% if monitor.status_override == 'MAINTENANCE' %}
|
||||
<span class="badge bg-primary status-badge">Wartung <i class="bi bi-tools"></i></span>
|
||||
{% elif monitor.status_override == 'DEGRADED' %}
|
||||
<span class="badge bg-warning text-dark status-badge">Probleme <i class="bi bi-exclamation-triangle"></i></span>
|
||||
{% elif monitor.status_override == 'OPERATIONAL' %}
|
||||
<span class="badge bg-success status-badge">Funktionsfähig <i class="bi bi-check-circle"></i></span>
|
||||
|
||||
{# Automatische Statusanzeige, wenn kein manueller Status gesetzt ist #}
|
||||
{% elif monitor.is_up == True %}
|
||||
<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 #}
|
||||
<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="alert alert-info mt-2 p-2">
|
||||
<small>{{ monitor.status_override_message }}</small>
|
||||
<div class="text-muted fst-italic small">
|
||||
<i class="bi bi-info-circle"></i> {{ monitor.status_override_message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="card-text">
|
||||
{% if monitor.monitor_type == 'TCP' %}
|
||||
<span class="text-muted text-break">{{ monitor.url }}:{{ monitor.port }}</span>
|
||||
</td>
|
||||
{% include '_status_badge.html' %}
|
||||
<td>
|
||||
{% if last_log %}
|
||||
{{ last_log.timestamp.strftime('%d.%m.%Y %H:%M:%S UTC') }}
|
||||
{% else %}
|
||||
<a href="{{ monitor.url }}" target="_blank" class="text-muted text-break">{{ monitor.url }}</a>
|
||||
<span class="text-muted">Nie</span>
|
||||
{% 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>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
{% include '_footer.html' %}
|
||||
|
||||
<script src="ht
|
||||
</html>
|
78
templates/status.html
Normal file
78
templates/status.html
Normal 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
85
update.py
Normal 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()
|
Reference in New Issue
Block a user