Kritischen poe.sh-Pfadbug beheben + verschachteltes <form> in Wartung fixen
- KRITISCH: usr/local/bin/custom/poe.sh wurde bei der TESM-Umbenennung
übersehen und zeigte weiterhin komplett auf die alten Pfade
(/srv/poe_manager/sqlite.db, /srv/poe_manager/generate_ips.py,
/var/log/poe-manager) -- der Ping-Check/PoE-Neustart-Loop
(tesm-check.service) lief dadurch seit dem Umzug nur noch auf Fehler
("No such file or directory"), das Live-Log stand seit gestern 19 Uhr
still und automatische PoE-Neustarts bei Geräteausfall liefen die
ganze Zeit ins Leere. Auf POETEST live behoben und verifiziert: Log
schreibt wieder frische Einträge, Checks laufen wieder durch.
- Wartung: "Update starten" nahm live IMMER zusätzlich das erste Gerät
mit, auch ganz ohne angehakte Checkbox. Ursache: der "Neustart"-Button
jeder Zeile saß in einem eigenen <form>, das INNERHALB des großen
#updateForm verschachtelt war -- ungültiges HTML, wodurch Browser das
äußere Formular vorzeitig schließen/aufspalten. Behoben, indem jedes
Neustart-<form> jetzt außerhalb von #updateForm steht und der Button
es nur per form="reboot-<mac>" referenziert. Live verifiziert: ein
gezielt einzeln ausgewähltes Gerät startet jetzt ausschließlich sich
selbst, keine leere Auswahl mehr möglich.
- Zusätzlich defensiv: Checkboxen werden beim Laden der Wartungsseite
jetzt explizit zurückgesetzt (manche Browser stellen den
Checked-Zustand nach einem Redirect/Reload sonst aus dem Verlauf
wieder her).
- Restliche "poe"-Relikte aus der TESM-Umbenennung bereinigt: Export-
Dateiname (poe_manager_export_… → tesm_export_…), Kommentare
(poe_web.service → tesm.service) sowie ein für den Nutzer sichtbarer
Hinweistext in Systemeinstellungen (rpi-check.service → tesm-check.service).
This commit is contained in:
+4
-4
@@ -140,7 +140,7 @@ os.makedirs(AVATAR_DIR, exist_ok=True)
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# Eigene, rotierte Datei für App-seitige Fehler/Warnungen ("Syslog"-Äquivalent
|
# Eigene, rotierte Datei für App-seitige Fehler/Warnungen ("Syslog"-Äquivalent
|
||||||
# der App) — vorher landete das nur im journalctl-Output von poe_web.service,
|
# der App) — vorher landete das nur im journalctl-Output von tesm.service,
|
||||||
# ohne eigene, von der App aus kontrollierbare Aufbewahrung. Bewusst nur
|
# ohne eigene, von der App aus kontrollierbare Aufbewahrung. Bewusst nur
|
||||||
# WARNING und höher (kein INFO/Request-Rauschen) — reine Fehlerdiagnose,
|
# WARNING und höher (kein INFO/Request-Rauschen) — reine Fehlerdiagnose,
|
||||||
# kein Access-Log.
|
# kein Access-Log.
|
||||||
@@ -2788,7 +2788,7 @@ def settings():
|
|||||||
else:
|
else:
|
||||||
ok, out = _dhcp_run_privileged(["timedatectl", "set-timezone", new_tz], timeout=10)
|
ok, out = _dhcp_run_privileged(["timedatectl", "set-timezone", new_tz], timeout=10)
|
||||||
if ok:
|
if ok:
|
||||||
# Ohne tzset() würde diese bereits laufende poe_web.service-
|
# Ohne tzset() würde diese bereits laufende tesm.service-
|
||||||
# Instanz die neue Zeitzone für datetime.now() (Änderungslog,
|
# Instanz die neue Zeitzone für datetime.now() (Änderungslog,
|
||||||
# App-Log) erst nach einem Neustart übernehmen -- glibc
|
# App-Log) erst nach einem Neustart übernehmen -- glibc
|
||||||
# cached die Zeitzone sonst ab dem ersten Zugriff im Prozess.
|
# cached die Zeitzone sonst ab dem ersten Zugriff im Prozess.
|
||||||
@@ -4399,7 +4399,7 @@ def export_data():
|
|||||||
summary = ", ".join(f"{EXPORT_SECTION_LABELS[k]}: {counts[k]}" for k in payload)
|
summary = ", ".join(f"{EXPORT_SECTION_LABELS[k]}: {counts[k]}" for k in payload)
|
||||||
log_action("data.export", ", ".join(EXPORT_SECTION_LABELS[k] for k in payload), summary)
|
log_action("data.export", ", ".join(EXPORT_SECTION_LABELS[k] for k in payload), summary)
|
||||||
|
|
||||||
filename = f"poe_manager_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
filename = f"tesm_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||||
response = jsonify(envelope)
|
response = jsonify(envelope)
|
||||||
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
|
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
|
||||||
return response
|
return response
|
||||||
@@ -5010,7 +5010,7 @@ def credentials():
|
|||||||
# auf dem Gerät selbst -- unabhängig davon, ob/wie es an einen Switch
|
# auf dem Gerät selbst -- unabhängig davon, ob/wie es an einen Switch
|
||||||
# angeschlossen ist. Job-Status lebt bewusst nur im Prozessspeicher (wie
|
# angeschlossen ist. Job-Status lebt bewusst nur im Prozessspeicher (wie
|
||||||
# das SSH-Terminal auch keine Sitzung persistiert) -- ein Neustart des
|
# das SSH-Terminal auch keine Sitzung persistiert) -- ein Neustart des
|
||||||
# poe_web.service verwirft laufende Job-Anzeigen, nicht aber die
|
# tesm.service verwirft laufende Job-Anzeigen, nicht aber die
|
||||||
# zugrundeliegende SSH-Aktion selbst (die läuft serverseitig auf dem
|
# zugrundeliegende SSH-Aktion selbst (die läuft serverseitig auf dem
|
||||||
# Zielgerät unabhängig vom Anzeigezustand hier weiter).
|
# Zielgerät unabhängig vom Anzeigezustand hier weiter).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -445,7 +445,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* "Jetzt prüfen"-Button neben dem globalen Timer (nur Admins): startet
|
/* "Jetzt prüfen"-Button neben dem globalen Timer (nur Admins): startet
|
||||||
rpi-check.service neu (frisches Logfile + sofortiger Durchlauf) und
|
tesm-check.service neu (frisches Logfile + sofortiger Durchlauf) und
|
||||||
meldet das per Custom Event, damit Dashboard/Live-Log sich selbst
|
meldet das per Custom Event, damit Dashboard/Live-Log sich selbst
|
||||||
aktualisieren können, ohne dass app.js die Seiteninhalte kennen muss. */
|
aktualisieren können, ohne dass app.js die Seiteninhalte kennen muss. */
|
||||||
function initRunCheckNow() {
|
function initRunCheckNow() {
|
||||||
|
|||||||
@@ -68,10 +68,16 @@
|
|||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
</button>
|
</button>
|
||||||
{% if can_run %}
|
{% if can_run %}
|
||||||
<form method="post" action="{{ url_for('maintenance_reboot') }}" data-confirm="{{ d['name'] }} ({{ d['ip'] }}) jetzt per SSH neu starten?">
|
<!-- Bewusst KEIN <form> hier: das säße dann innerhalb des
|
||||||
<input type="hidden" name="macs" value="{{ d['mac'] }}">
|
äußeren #updateForm (verschachtelte <form>-Elemente sind
|
||||||
<button type="submit" class="btn btn-sm btn-secondary">Neustart</button>
|
ungültiges HTML) -- der Browser hätte das äußere Formular
|
||||||
</form>
|
an dieser Stelle vorzeitig geschlossen bzw. dessen Inhalt
|
||||||
|
zerrissen. "Update starten" hat dadurch live IMMER
|
||||||
|
zusätzlich das erste/oberste Gerät mitgeschickt, auch ganz
|
||||||
|
ohne angehakte Checkbox. Das eigentliche <form> für den
|
||||||
|
Neustart steht daher unten AUSSERHALB von #updateForm; der
|
||||||
|
Button hier referenziert es nur über "form=". -->
|
||||||
|
<button type="submit" form="reboot-{{ d['mac'] }}" class="btn btn-sm btn-secondary">Neustart</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -97,12 +103,36 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Ein eigenständiges, NICHT verschachteltes <form> je Gerät für den
|
||||||
|
einzelnen "Neustart"-Button (siehe Kommentar oben in der Tabelle) --
|
||||||
|
jedes referenziert nur genau eine MAC, unabhängig vom Checkbox-Zustand
|
||||||
|
im großen #updateForm. -->
|
||||||
|
{% if can_run %}
|
||||||
|
{% for d in devices %}
|
||||||
|
<form method="post" action="{{ url_for('maintenance_reboot') }}" id="reboot-{{ d['mac'] }}" data-confirm="{{ d['name'] }} ({{ d['ip'] }}) jetzt per SSH neu starten?">
|
||||||
|
<input type="hidden" name="macs" value="{{ d['mac'] }}">
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
// Checkboxen beim Laden IMMER zurücksetzen: Browser stellen den
|
||||||
|
// Checked-Zustand von Formularen nach einem Reload/Redirect (z.B. genau der
|
||||||
|
// Redirect nach "Update starten") gerne aus dem eigenen Verlauf wieder her,
|
||||||
|
// unabhängig davon, dass das frisch gerenderte HTML gar kein "checked"
|
||||||
|
// enthält -- ein einmal angehaktes Gerät (z.B. beim ersten Ausprobieren)
|
||||||
|
// blieb dadurch bei jedem weiteren Aufruf der Seite unsichtbar mitausgewählt
|
||||||
|
// und lief bei "Update starten" immer zusätzlich mit, ganz gleich was man
|
||||||
|
// gerade neu ankreuzt (live genau so reproduziert).
|
||||||
|
document.querySelectorAll(".maint-check").forEach(function (cb) { cb.checked = false; });
|
||||||
|
const selectAllCb = document.getElementById("selectAll");
|
||||||
|
if (selectAllCb) selectAllCb.checked = false;
|
||||||
|
|
||||||
document.getElementById("selectAll")?.addEventListener("change", function () {
|
document.getElementById("selectAll")?.addEventListener("change", function () {
|
||||||
document.querySelectorAll(".maint-check").forEach(function (cb) {
|
document.querySelectorAll(".maint-check").forEach(function (cb) {
|
||||||
if (!cb.disabled) cb.checked = this.checked;
|
if (!cb.disabled) cb.checked = this.checked;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="interval">Prüfintervall (Minuten)</label>
|
<label for="interval">Prüfintervall (Minuten)</label>
|
||||||
<input type="number" name="interval" id="interval" value="{{ interval }}" min="1" required>
|
<input type="number" name="interval" id="interval" value="{{ interval }}" min="1" required>
|
||||||
<div class="field-hint">Wie oft Geräte auf Erreichbarkeit geprüft werden. Der Hintergrund-Dienst (rpi-check.service) wird nach dem Speichern automatisch neu gestartet.</div>
|
<div class="field-hint">Wie oft Geräte auf Erreichbarkeit geprüft werden. Der Hintergrund-Dienst (tesm-check.service) wird nach dem Speichern automatisch neu gestartet.</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">
|
<button type="submit" class="btn btn-primary btn-block">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Eine einzige, durchgehend fortgeschriebene Datei statt vorher bei jedem
|
# Eine einzige, durchgehend fortgeschriebene Datei statt vorher bei jedem
|
||||||
# (auch täglich per rpi-check-restart.timer automatisch ausgelösten)
|
# (auch täglich per tesm-check-restart.timer automatisch ausgelösten)
|
||||||
# Dienst-Neustart ein neues rpi-<timestamp>.log -- Rotation/Aufbewahrung
|
# Dienst-Neustart ein neues rpi-<timestamp>.log -- Rotation/Aufbewahrung
|
||||||
# übernimmt jetzt zentral logrotate (siehe app.py: _write_logrotate_config,
|
# übernimmt jetzt zentral logrotate (siehe app.py: _write_logrotate_config,
|
||||||
# einstellbar unter Systemeinstellungen → Logs), nicht mehr dieses Skript
|
# einstellbar unter Systemeinstellungen → Logs), nicht mehr dieses Skript
|
||||||
# selbst. Pfad muss mit POE_LOG_DIR/live.log in app.py übereinstimmen.
|
# selbst. Pfad muss mit TESM_LOG_DIR/live.log in app.py übereinstimmen.
|
||||||
LOG_DIR="/var/log/poe-manager"
|
LOG_DIR="/var/log/tesm"
|
||||||
LOGFILE="$LOG_DIR/live.log"
|
LOGFILE="$LOG_DIR/live.log"
|
||||||
mkdir -p "$LOG_DIR"
|
mkdir -p "$LOG_DIR"
|
||||||
touch "$LOGFILE"
|
touch "$LOGFILE"
|
||||||
@@ -20,7 +20,7 @@ touch "$LOGFILE"
|
|||||||
# Intervall aus DB abrufen
|
# Intervall aus DB abrufen
|
||||||
SLEEP=$(python3 - <<'END'
|
SLEEP=$(python3 - <<'END'
|
||||||
import sqlite3
|
import sqlite3
|
||||||
conn = sqlite3.connect("/srv/poe_manager/sqlite.db")
|
conn = sqlite3.connect("/srv/tesm/sqlite.db")
|
||||||
row = conn.execute("SELECT value FROM settings WHERE key='check_interval'").fetchone()
|
row = conn.execute("SELECT value FROM settings WHERE key='check_interval'").fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
print(row[0] if row else 300)
|
print(row[0] if row else 300)
|
||||||
@@ -164,7 +164,7 @@ function check_device() {
|
|||||||
|
|
||||||
function manual_restart() {
|
function manual_restart() {
|
||||||
local target_mac="$1"
|
local target_mac="$1"
|
||||||
python3 /srv/poe_manager/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do
|
python3 /srv/tesm/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do
|
||||||
if [[ "$mac" != "$target_mac" ]]; then
|
if [[ "$mac" != "$target_mac" ]]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -213,7 +213,7 @@ while true; do
|
|||||||
# Subshell, deren Hintergrund-Jobs von außen nicht mehr sichtbar wären).
|
# Subshell, deren Hintergrund-Jobs von außen nicht mehr sichtbar wären).
|
||||||
while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do
|
while IFS='|' read -r rpi_ip dev_name switch_ip switch_ssh_port switch_hostname switch_port switch_user switch_pass mac; do
|
||||||
check_device "$rpi_ip" "$dev_name" "$switch_ip" "$switch_ssh_port" "$switch_hostname" "$switch_port" "$switch_user" "$switch_pass" &
|
check_device "$rpi_ip" "$dev_name" "$switch_ip" "$switch_ssh_port" "$switch_hostname" "$switch_port" "$switch_user" "$switch_pass" &
|
||||||
done < <(python3 /srv/poe_manager/generate_ips.py)
|
done < <(python3 /srv/tesm/generate_ips.py)
|
||||||
|
|
||||||
# Auf alle in diesem Durchlauf gestarteten Geräte-Checks warten, bevor
|
# Auf alle in diesem Durchlauf gestarteten Geräte-Checks warten, bevor
|
||||||
# das Intervall abgewartet wird -- sonst könnten sich Durchläufe
|
# das Intervall abgewartet wird -- sonst könnten sich Durchläufe
|
||||||
|
|||||||
Reference in New Issue
Block a user