Compare commits

...

49 Commits

Author SHA1 Message Date
cfbec22590 Merge pull request 'srv/poe_manager/templates/index.html aktualisiert' (#5) from dev into main
Reviewed-on: #5
2025-10-12 17:53:28 +02:00
3eb78b46a7 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:51:21 +02:00
609e116911 Merge pull request 'dev' (#4) from dev into main
Reviewed-on: #4
2025-10-12 17:45:58 +02:00
681888a36a DB 2025-10-12 17:45:24 +02:00
e8df4f937b srv/poe_manager/templates/users.html aktualisiert 2025-10-12 17:43:58 +02:00
29823bb4ef srv/poe_manager/templates/switche.html aktualisiert 2025-10-12 17:43:46 +02:00
a2920a98bd srv/poe_manager/templates/devices.html aktualisiert 2025-10-12 17:43:12 +02:00
579f3f70b9 revert 9344690a31
revert srv/poe_manager/templates/devices.html aktualisiert
2025-10-12 17:42:58 +02:00
9344690a31 srv/poe_manager/templates/devices.html aktualisiert 2025-10-12 17:41:09 +02:00
1b74609d1b Merge pull request 'dev' (#3) from dev into main
Reviewed-on: #3
2025-10-12 17:38:39 +02:00
7f0871fa64 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:33:30 +02:00
5ae38a9e20 srv/poe_manager/app.py aktualisiert 2025-10-12 17:33:05 +02:00
de23b132ed srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:31:41 +02:00
746972e321 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:31:08 +02:00
dfba8789a7 srv/poe_manager/app.py aktualisiert 2025-10-12 17:28:05 +02:00
194e3cfbf9 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:22:31 +02:00
5b33e2d656 Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2025-10-12 17:19:37 +02:00
4e10fe8ac0 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:16:23 +02:00
7ae131b5f4 srv/poe_manager/app.py aktualisiert 2025-10-12 17:12:40 +02:00
09ee8c245e srv/poe_manager/app.py aktualisiert 2025-10-12 17:09:43 +02:00
bb07a598d7 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:06:38 +02:00
4ef10fc39d srv/poe_manager/app.py aktualisiert 2025-10-12 17:06:04 +02:00
d73fda1935 revert aa2895ccf5
revert srv/poe_manager/templates/index.html aktualisiert
2025-10-12 17:03:52 +02:00
ad9dfcee7a revert 4ae255b67f
revert srv/poe_manager/templates/index.html aktualisiert
2025-10-12 17:03:00 +02:00
4ae255b67f srv/poe_manager/templates/index.html aktualisiert 2025-10-12 17:00:36 +02:00
aa2895ccf5 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 16:53:23 +02:00
7c81f3a299 srv/poe_manager/app.py aktualisiert 2025-10-12 16:52:24 +02:00
cef96cd7cd Devices DB 2025-10-12 16:39:42 +02:00
b4cc41c272 v1.2 2025-10-12 13:35:39 +00:00
0d784a0530 srv/poe_manager/app.py aktualisiert 2025-10-12 15:22:08 +02:00
34ef2c4e2a srv/poe_manager/app.py aktualisiert 2025-10-12 15:20:21 +02:00
a26a91ec5a srv/poe_manager/templates/index.html aktualisiert 2025-10-12 15:17:21 +02:00
ef548b9532 srv/poe_manager/templates/index.html aktualisiert 2025-10-12 15:14:10 +02:00
466f3615ed usr/local/bin/custom/poe.sh aktualisiert 2025-10-12 15:09:22 +02:00
3dc554e165 srv/poe_manager/app.py aktualisiert 2025-10-12 14:58:56 +02:00
78ed058d69 srv/poe_manager/generate_ips.py aktualisiert 2025-10-12 14:53:59 +02:00
d3f209941b srv/poe_manager/generate_ips.py aktualisiert 2025-10-12 14:51:08 +02:00
9f98d240ca srv/poe_manager/app.py aktualisiert 2025-10-12 14:43:58 +02:00
d88135fc12 srv/poe_manager/app.py aktualisiert 2025-10-12 14:37:34 +02:00
34eae3ca9d srv/poe_manager/app.py aktualisiert 2025-10-12 14:35:18 +02:00
f05936226d srv/poe_manager/app.py aktualisiert 2025-10-12 14:33:49 +02:00
d6c15d3d49 srv/poe_manager/app.py aktualisiert 2025-10-12 14:32:37 +02:00
7a1e9d88e5 srv/poe_manager/app.py aktualisiert 2025-10-12 14:29:02 +02:00
2f1c7b5653 srv/poe_manager/app.py aktualisiert 2025-10-12 14:10:10 +02:00
d1e43a1220 srv/poe_manager/app.py aktualisiert 2025-10-12 14:03:06 +02:00
fe921254c8 srv/poe_manager/app.py aktualisiert 2025-10-12 13:58:25 +02:00
f27e73ae72 srv/poe_manager/app.py aktualisiert 2025-10-12 13:55:40 +02:00
63b8e33434 srv/poe_manager/app.py aktualisiert 2025-10-12 13:50:22 +02:00
ad09b4f6a2 srv/poe_manager/templates/devices.html aktualisiert 2025-10-12 13:48:59 +02:00
8 changed files with 137 additions and 63 deletions

View File

@@ -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,34 @@ 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:
datetime_str = latest_time.strftime("Zuletzt Online am %d.%m.%Y um %H:%M Uhr")
return f"{datetime_str}"
return None
@app.route("/")
@login_required
def index():
@@ -90,26 +118,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 +221,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 +258,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 Switchnderung 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 +282,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 +323,7 @@ def devices():
except sqlite3.IntegrityError:
flash("Fehler beim Aktualisieren des Geräts!")
# -----------------------
# Gerät löschen
# -----------------------
@@ -300,7 +352,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 +486,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():

View File

@@ -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()

Binary file not shown.

View File

@@ -8,7 +8,7 @@
<button class="btn btn-success mb-3" data-bs-toggle="modal" data-bs-target="#deviceModal" onclick="openDeviceModal()">Neues Gerät hinzufügen</button>
{% endif %}
<table class="table table-bordered">
<table class="table table-striped">
<thead>
<tr>
<th>Hostname</th>
@@ -89,11 +89,12 @@
</div>
<div class="mb-3">
<label>Port</label>
<input type="text" name="port" class="form-control" required placeholder="z.B. 3">
<input type="text" name="port" class="form-control" placeholder="z.B. 3">
</div>
<div class="mb-3">
<label>Switch</label>
<select name="switch_hostname" class="form-select" required>
<label>Switch (optional)</label>
<select name="switch_hostname" class="form-select">
<option value="">Kein Switch</option>
{% for sw in switches %}
<option value="{{ sw['hostname'] }}">{{ sw['hostname'] }}</option>
{% endfor %}
@@ -137,7 +138,7 @@
</div>
<div class="mb-3">
<label>Port</label>
<input type="text" name="port" id="edit_port" class="form-control" required placeholder="z.B. 3">
<input type="text" name="port" id="edit_port" class="form-control" placeholder="z.B. 3">
</div>
</div>
<div class="modal-footer">
@@ -161,7 +162,8 @@
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<select name="switch_hostname" id="switch_select" class="form-select" required>
<select name="switch_hostname" id="switch_select" class="form-select">
<option value="">Kein Switch</option>
{% for sw in switches %}
<option value="{{ sw['hostname'] }}">{{ sw['hostname'] }}</option>
{% endfor %}

View File

@@ -7,15 +7,20 @@
Nächste Prüfung in -- Sekunden
</span>
</h2>
<div class="row row-cols-1 row-cols-md-6 g-3">
<div class="row g-3">
{% for d in devices %}
<div class="col">
<div class="card text-center p-2">
<div class="col-6 col-md-4 col-lg-3 col-xl-2">
<div class="card text-center p-2"
{% if last_seen.get(d[0]) %}
title="{{ last_seen[d[0]] }}"
{% elif status[d[0]] == 'offline' %}
title="Noch nie online"
{% endif %}>
<div class="card-header">{{ d[1] }}</div>
<div class="card-body">
<span class="fw-bold" style="color:
{% if d[2] == 0 %}gray
{% elif status[d[0]]=='online' %}green
{% elif status[d[0]] == 'online' %}green
{% else %}red
{% endif %};">
{% if d[2] == 0 %}
@@ -29,7 +34,6 @@
</div>
{% endfor %}
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const intervalMinutes = {{ interval | int }}; // aus DB

View File

@@ -5,7 +5,7 @@
<!-- Button zum Hinzufügen -->
<button class="btn btn-success mb-3" data-bs-toggle="modal" data-bs-target="#addSwitchModal">Neuen Switch hinzufügen</button>
<table class="table table-bordered">
<table class="table table-striped">
<thead>
<tr>
<th>Hostname</th>

View File

@@ -8,7 +8,7 @@
<button class="btn btn-success mb-3" data-bs-toggle="modal" data-bs-target="#userModal" onclick="openUserModal()">Neuen Benutzer</button>
{% endif %}
<table class="table table-bordered">
<table class="table table-striped">
<thead>
<tr>
<th class="col-ip">Username</th>

View File

@@ -96,7 +96,7 @@ while true; do
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist nicht erreichbar!" >> "$LOGFILE"
# Nur PoE neu starten, wenn Port vorhanden ist
if [ -n "$switch_port" ]; then
if [ -n "$switch_port" ] && [ "$switch_port" != "None" ]; then
disable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass"
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE auf Port $switch_port am Switch $switch_hostname deaktiviert." >> "$LOGFILE"
sleep 2