+ update emblemed support in iframe and list view
This commit is contained in:
119
main.py
119
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):
|
||||
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)
|
||||
|
Reference in New Issue
Block a user