+ 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 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):
upgrade()
ran_migrations = True
app.logger.info("Migrationen abgeschlossen.")
else:
app.logger.warning("Kein migrations/-Ordner gefunden überspringe Alembic upgrade.")
# Wendet ausstehende Datenbank-Migrationen an.
# Dies aktualisiert das Schema auf den neuesten Stand.
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)