diff --git a/srv/poe_manager/app.py b/srv/poe_manager/app.py index 7a8b50e..f29a430 100644 --- a/srv/poe_manager/app.py +++ b/srv/poe_manager/app.py @@ -3,8 +3,8 @@ from flask import Flask, render_template, request, redirect, url_for, flash from flask_login import LoginManager, login_user, login_required, logout_user, UserMixin, current_user from flask_bcrypt import Bcrypt from cryptography.fernet import Fernet -import sqlite3 -import glob, os, re +from datetime import datetime +import sqlite3, glob, os, re app = Flask(__name__) app.secret_key = "309cc4d5ce1fe7486ae25cbd232bbdfe6a72539c03f0127d372186dbdc0fc928" @@ -81,6 +81,35 @@ def logout(): logout_user() return redirect(url_for('login')) +def get_last_seen(dev_name: str): + """Letztes Mal, dass ein Gerät erreichbar war.""" + log_files = glob.glob("/var/log/rpi-*.log") + if not log_files: + return None + + latest_time = None + + # alle Logs durchgehen + for logfile in sorted(log_files): + with open(logfile, "r") as f: + for line in f: + line = line.strip() + if f"{dev_name} ist erreichbar!" in line: + try: + ts_str = line.split(" ")[0] + " " + line.split(" ")[1] # "YYYY-MM-DD HH:MM:SS" + ts = datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S") + if latest_time is None or ts > latest_time: + latest_time = ts + except Exception: + continue + + if latest_time: + date_str = latest_time.strftime("Zuletzt Online am %d.%m.%Y") + time_str = latest_time.strftime("um %H:%M Uhr") + return f"{date_str}\n{time_str}" + + return None + @app.route("/") @login_required def index(): @@ -90,26 +119,40 @@ def index(): c.execute("SELECT mac, name, is_active FROM devices ORDER BY name ASC") devices = c.fetchall() - # Intervall aus DB (Minuten) laden + # Intervall aus DB laden c.execute("SELECT value FROM settings WHERE key='interval'") row = c.fetchone() - interval = int(row[0]) if row else 5 # Default 5 Minuten + interval = int(row[0]) if row else 5 conn.close() - # Status aus letztem Log ermitteln - import glob, os + # Status aus Logdateien ermitteln log_files = glob.glob("/var/log/rpi-*.log") status_dict = {} + last_seen_dict = {} + if log_files: latest_log = max(log_files, key=os.path.getctime) with open(latest_log, "r") as f: - for line in f: - for dev in devices: - if dev[1] in line: - status_dict[dev[0]] = "online" if "erreichbar" in line else "offline" + lines = f.readlines() - # Template rendern mit Devices, Status und Intervall - return render_template("index.html", devices=devices, status=status_dict, interval=interval) + for dev in devices: + last_status = None + for line in reversed(lines): + if f"{dev[1]} ist erreichbar!" in line: + last_status = "online" + break + elif f"{dev[1]} ist nicht erreichbar!" in line: + last_status = "offline" + break + + if last_status: + status_dict[dev[0]] = last_status + if last_status == "offline": + last_seen_dict[dev[0]] = get_last_seen(dev[1]) + else: + status_dict[dev[0]] = "unbekannt" + + return render_template("index.html", devices=devices, status=status_dict, last_seen=last_seen_dict, interval=interval) @app.route("/settings", methods=["GET", "POST"]) @login_required @@ -179,7 +222,7 @@ def devices(): switch_hostname = request.form.get('switch_hostname') is_active = 1 if 'is_active' in request.form else 0 - if not all([mac, rpi_ip, port, name, switch_hostname]): + if not all([mac, rpi_ip, name]): flash("Alle Felder müssen ausgefüllt sein!") return redirect(url_for('devices')) @@ -216,23 +259,23 @@ def devices(): old_mac = request.form.get('old_mac') mac = request.form.get('mac') rpi_ip = request.form.get('rpi_ip') - port = request.form.get('port') + port = request.form.get('port') or None name = request.form.get('name') switch_hostname = request.form.get('switch_hostname') or None is_active = 1 if 'is_active' in request.form else 0 - # --- Prüfen, ob es sich um eine Switch-Änderung handelt --- - if mac is None and rpi_ip is None and port is None and name is None and switch_hostname: - # Nur den Switch ändern + # --- Nur Switch ändern --- + # Prüfen, ob nur das Switch-Feld gesendet wurde und die anderen Felder leer sind + if 'switch_hostname' in request.form and all(not f for f in [mac, rpi_ip, name]): device = conn.execute("SELECT name, switch_hostname FROM devices WHERE mac=?", (old_mac,)).fetchone() - if not device: flash("Gerät nicht gefunden!") return redirect(url_for('devices')) old_switch = device['switch_hostname'] or "unbekannt" device_name = device['name'] - + switch_hostname = request.form.get('switch_hostname') or "" + try: conn.execute(""" UPDATE devices @@ -240,27 +283,36 @@ def devices(): WHERE mac=? """, (switch_hostname, old_mac)) conn.commit() - flash(f"Switch von {device_name} geändert: {old_switch} → {switch_hostname}") + flash(f"Switch von {device_name} geändert: {old_switch} → {switch_hostname or 'Kein Switch'}") except sqlite3.IntegrityError: flash("Fehler beim Ändern des Switch!") return redirect(url_for('devices')) - if not all([old_mac, mac, rpi_ip, port, name]): - flash("Alle Felder müssen ausgefüllt sein!") - return redirect(url_for('devices')) + # --- Normales Gerät bearbeiten --- + # Pflichtfelder prüfen + if not all([old_mac, mac, rpi_ip, name]): + flash("Felder 'MAC', 'IP' und 'Name' müssen ausgefüllt sein!") + return redirect(url_for('devices')) # Prüfen auf doppelte IP außer das aktuelle Gerät - ip_device = conn.execute("SELECT name FROM devices WHERE rpi_ip=? AND mac<>?", (rpi_ip, old_mac)).fetchone() + ip_device = conn.execute( + "SELECT name FROM devices WHERE rpi_ip=? AND mac<>?", + (rpi_ip, old_mac) + ).fetchone() if ip_device: flash(f"IP-Adresse existiert bereits für Gerät '{ip_device['name']}'!") return redirect(url_for('devices')) # Prüfen auf doppelte MAC außer das aktuelle Gerät - mac_device = conn.execute("SELECT name FROM devices WHERE mac=? AND mac<>?", (mac, old_mac)).fetchone() + mac_device = conn.execute( + "SELECT name FROM devices WHERE mac=? AND mac<>?", + (mac, old_mac) + ).fetchone() if mac_device: flash(f"MAC-Adresse existiert bereits für Gerät '{mac_device['name']}'!") return redirect(url_for('devices')) + # Update durchführen try: conn.execute(""" UPDATE devices @@ -272,6 +324,7 @@ def devices(): except sqlite3.IntegrityError: flash("Fehler beim Aktualisieren des Geräts!") + # ----------------------- # Gerät löschen # ----------------------- @@ -300,7 +353,7 @@ def devices(): switches.hostname AS switch_hostname FROM devices LEFT JOIN switches ON devices.switch_hostname = switches.hostname - ORDER BY switches.hostname ASC + ORDER BY switches.hostname ASC, devices.name ASC """).fetchall() conn.close() @@ -434,36 +487,43 @@ def logs(): return render_template('logs.html', log_content=log_content, log_name=os.path.basename(latest_log), interval=interval) def load_device_status(): - """ - Liest das aktuellste rpi-Logfile und extrahiert den letzten Status jedes Devices. - Gibt ein Dictionary zurück: {Device-Name: 'online'/'offline'} - """ status = {} + + # Devices aus DB laden (Name → MAC) + conn = sqlite3.connect("sqlite.db") + conn.row_factory = sqlite3.Row + devices = conn.execute("SELECT mac, name FROM devices").fetchall() + name_to_mac = {d['name']: d['mac'] for d in devices} + conn.close() + + # Logfile log_files = glob.glob("/var/log/rpi-*.log") if not log_files: return status - latest_log = max(log_files, key=os.path.getctime) - # Jede Zeile des Logs lesen - with open(latest_log, "r") as f: - lines = f.readlines() - - # Regex für Ping-Ergebnisse online_re = re.compile(r"(\S+) ist erreichbar!") offline_re = re.compile(r"(\S+) ist nicht erreichbar!") - for line in lines: - line = line.strip() - m_online = online_re.search(line) - m_offline = offline_re.search(line) - if m_online: - status[m_online.group(1)] = 'online' - elif m_offline: - status[m_offline.group(1)] = 'offline' + with open(latest_log, "r") as f: + for line in f: + line = line.strip() + m_online = online_re.search(line) + m_offline = offline_re.search(line) + if m_online: + name = m_online.group(1) + mac = name_to_mac.get(name) + if mac: + status[mac] = 'online' + elif m_offline: + name = m_offline.group(1) + mac = name_to_mac.get(name) + if mac: + status[mac] = 'offline' return status + @app.route("/users", methods=["GET", "POST"]) @login_required def users(): diff --git a/srv/poe_manager/generate_ips.py b/srv/poe_manager/generate_ips.py index a299564..0a46f66 100644 --- a/srv/poe_manager/generate_ips.py +++ b/srv/poe_manager/generate_ips.py @@ -6,7 +6,10 @@ def generate_ips_list(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row - switches = {row['hostname']: row for row in conn.execute("SELECT hostname, ip, username, password FROM switches")} + switches = {row['hostname']: row for row in conn.execute( + "SELECT hostname, ip, username, password FROM switches" + )} + devices = conn.execute(""" SELECT mac, rpi_ip, port, name, switch_hostname FROM devices @@ -16,11 +19,17 @@ def generate_ips_list(): for dev in devices: switch = switches.get(dev['switch_hostname']) - if not switch: - continue - password = decrypt_password(switch['password']) + if switch: + switch_ip = switch['ip'] + switch_user = switch['username'] + switch_pass = decrypt_password(switch['password']) + else: + switch_ip = "" + switch_user = "" + switch_pass = "" + port = dev['port'] or "" - print(f"{dev['rpi_ip']}:{dev['name']}:{switch['ip']}:{switch['hostname']}:{port}:{switch['username']}:{password}") + print(f"{dev['rpi_ip']}:{dev['name']}:{switch_ip}:{dev['switch_hostname'] or 'kein Switch'}:{port}:{switch_user}:{switch_pass}") if __name__ == "__main__": generate_ips_list() diff --git a/srv/poe_manager/sqlite.db b/srv/poe_manager/sqlite.db index 33d7fba..5644d9b 100644 Binary files a/srv/poe_manager/sqlite.db and b/srv/poe_manager/sqlite.db differ diff --git a/srv/poe_manager/templates/devices.html b/srv/poe_manager/templates/devices.html index 4172023..613120d 100644 --- a/srv/poe_manager/templates/devices.html +++ b/srv/poe_manager/templates/devices.html @@ -89,11 +89,12 @@