Compare commits
14
Commits
prerestart
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4850bc067 | ||
|
|
fd37656586 | ||
|
|
6f4fda8993 | ||
|
|
cce95f2c38 | ||
|
|
90e2df955d | ||
|
|
15c59df140 | ||
|
|
8b741a5728 | ||
|
|
293c3b05c0 | ||
|
|
6ea15021ec | ||
|
|
5e2f3868be | ||
|
|
5709f32fcd | ||
|
|
b1d1c07846 | ||
|
|
15ab66fef2 | ||
|
|
4b12975107 |
+64
-3
@@ -1,10 +1,10 @@
|
|||||||
#u!/usr/bin/env python3
|
#u!/usr/bin/env python3
|
||||||
from flask import Flask, render_template, request, redirect, url_for, flash
|
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
|
||||||
from flask_login import LoginManager, login_user, login_required, logout_user, UserMixin, current_user
|
from flask_login import LoginManager, login_user, login_required, logout_user, UserMixin, current_user
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import sqlite3, glob, os, re
|
import sqlite3, glob, os, re, subprocess
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = "309cc4d5ce1fe7486ae25cbd232bbdfe6a72539c03f0127d372186dbdc0fc928"
|
app.secret_key = "309cc4d5ce1fe7486ae25cbd232bbdfe6a72539c03f0127d372186dbdc0fc928"
|
||||||
@@ -15,6 +15,7 @@ login_manager.login_view = "login"
|
|||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
|
|
||||||
DB_PATH = "/srv/poe_manager/sqlite.db"
|
DB_PATH = "/srv/poe_manager/sqlite.db"
|
||||||
|
POE_SCRIPT = "/usr/local/bin/custom/poe.sh"
|
||||||
|
|
||||||
class User(UserMixin):
|
class User(UserMixin):
|
||||||
def __init__(self, id_, username, is_admin):
|
def __init__(self, id_, username, is_admin):
|
||||||
@@ -115,7 +116,7 @@ def index():
|
|||||||
# Geräte aus DB laden
|
# Geräte aus DB laden
|
||||||
conn = sqlite3.connect("sqlite.db")
|
conn = sqlite3.connect("sqlite.db")
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute("SELECT mac, name, is_active FROM devices ORDER BY name ASC")
|
c.execute("SELECT mac, name, rpi_ip, switch_hostname, port, is_active FROM devices ORDER BY name ASC")
|
||||||
devices = c.fetchall()
|
devices = c.fetchall()
|
||||||
|
|
||||||
# Intervall aus DB laden
|
# Intervall aus DB laden
|
||||||
@@ -458,6 +459,66 @@ def get_log():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Fehler beim Lesen des Logs: {e}"
|
return f"Fehler beim Lesen des Logs: {e}"
|
||||||
|
|
||||||
|
@app.route("/restart/<mac>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def restart_device(mac):
|
||||||
|
|
||||||
|
conn = sqlite3.connect("sqlite.db")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
c.execute("""
|
||||||
|
SELECT
|
||||||
|
mac,
|
||||||
|
name,
|
||||||
|
is_active
|
||||||
|
FROM devices
|
||||||
|
WHERE mac=?
|
||||||
|
""", (mac,))
|
||||||
|
|
||||||
|
device = c.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not device:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": "Gerät wurde nicht gefunden."
|
||||||
|
}), 404
|
||||||
|
|
||||||
|
if device["is_active"] == 0:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": f"{device['name']} ist deaktiviert."
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
"/bin/bash",
|
||||||
|
POE_SCRIPT,
|
||||||
|
"restart",
|
||||||
|
mac
|
||||||
|
],
|
||||||
|
env={
|
||||||
|
"PATH": "/srv/poe_manager/venv/bin:" + os.environ["PATH"]
|
||||||
|
},
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"device": device["name"]
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
@app.route('/logs')
|
@app.route('/logs')
|
||||||
@login_required
|
@login_required
|
||||||
def logs():
|
def logs():
|
||||||
|
|||||||
@@ -29,7 +29,16 @@ def generate_ips_list():
|
|||||||
switch_pass = ""
|
switch_pass = ""
|
||||||
|
|
||||||
port = dev['port'] or ""
|
port = dev['port'] or ""
|
||||||
print(f"{dev['rpi_ip']}:{dev['name']}:{switch_ip}:{dev['switch_hostname'] or 'kein Switch'}:{port}:{switch_user}:{switch_pass}")
|
print(
|
||||||
|
f"{dev['rpi_ip']}|"
|
||||||
|
f"{dev['name']}|"
|
||||||
|
f"{switch_ip}|"
|
||||||
|
f"{dev['switch_hostname'] or 'kein Switch'}|"
|
||||||
|
f"{port}|"
|
||||||
|
f"{switch_user}|"
|
||||||
|
f"{switch_pass}|"
|
||||||
|
f"{dev['mac']}"
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
generate_ips_list()
|
generate_ips_list()
|
||||||
|
|||||||
@@ -10,7 +10,14 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
{% for d in devices %}
|
{% for d in devices %}
|
||||||
<div class="col-6 col-md-4 col-lg-3 col-xl-2">
|
<div class="col-6 col-md-4 col-lg-3 col-xl-2">
|
||||||
<div class="card text-center p-2"
|
<div class="card text-center p-2 device-card"
|
||||||
|
style="cursor:pointer;"
|
||||||
|
data-mac="{{ d[0] }}"
|
||||||
|
data-name="{{ d[1] }}"
|
||||||
|
data-ip="{{ d[2] }}"
|
||||||
|
data-switch="{{ d[3] }}"
|
||||||
|
data-port="{{ d[4] }}"
|
||||||
|
data-active="{{ d[5] }}"
|
||||||
{% if last_seen.get(d[0]) %}
|
{% if last_seen.get(d[0]) %}
|
||||||
title="{{ last_seen[d[0]] }}"
|
title="{{ last_seen[d[0]] }}"
|
||||||
{% elif status[d[0]] == 'offline' %}
|
{% elif status[d[0]] == 'offline' %}
|
||||||
@@ -19,11 +26,11 @@
|
|||||||
<div class="card-header">{{ d[1] }}</div>
|
<div class="card-header">{{ d[1] }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span class="fw-bold" style="color:
|
<span class="fw-bold" style="color:
|
||||||
{% if d[2] == 0 %}gray
|
{% if d[5] == 0 %}gray
|
||||||
{% elif status[d[0]] == 'online' %}green
|
{% elif status[d[0]] == 'online' %}green
|
||||||
{% else %}red
|
{% else %}red
|
||||||
{% endif %};">
|
{% endif %};">
|
||||||
{% if d[2] == 0 %}
|
{% if d[5] == 0 %}
|
||||||
Deaktiviert
|
Deaktiviert
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if status[d[0]] %}{{ status[d[0]]|capitalize }}{% else %}Unbekannt{% endif %}
|
{% if status[d[0]] %}{{ status[d[0]]|capitalize }}{% else %}Unbekannt{% endif %}
|
||||||
@@ -39,6 +46,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const intervalMinutes = {{ interval | int }}; // aus DB
|
const intervalMinutes = {{ interval | int }}; // aus DB
|
||||||
const intervalMilliseconds = intervalMinutes * 60 * 1000;
|
const intervalMilliseconds = intervalMinutes * 60 * 1000;
|
||||||
let lastUpdateTime = Date.now();
|
let lastUpdateTime = Date.now();
|
||||||
|
let selectedCard = null;
|
||||||
|
let selectedMac = null;
|
||||||
|
let selectedName = null;
|
||||||
|
|
||||||
|
const deviceModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("deviceModal")
|
||||||
|
);
|
||||||
|
|
||||||
|
const restartModal = new bootstrap.Modal(
|
||||||
|
document.getElementById("restartModal")
|
||||||
|
);
|
||||||
|
|
||||||
function parseLogTimestamp(ts) {
|
function parseLogTimestamp(ts) {
|
||||||
const parts = ts.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
const parts = ts.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
|
||||||
@@ -82,6 +100,90 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
.catch(err => console.error("Fehler beim Laden der Logs:", err));
|
.catch(err => console.error("Fehler beim Laden der Logs:", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll(".device-card").forEach(card => {
|
||||||
|
const mac = card.dataset.mac;
|
||||||
|
const restartKey = "restart_" + mac;
|
||||||
|
if (sessionStorage.getItem(restartKey)) {
|
||||||
|
const span = card.querySelector(".fw-bold");
|
||||||
|
if (span.innerText.trim().toLowerCase() === "online") {
|
||||||
|
sessionStorage.removeItem(restartKey);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
span.innerHTML =
|
||||||
|
'<span class="spinner-border spinner-border-sm me-2"></span>Restarting...';
|
||||||
|
span.style.color = "#ffc107";
|
||||||
|
document.getElementById("deviceStatus").innerHTML =
|
||||||
|
'<span class="spinner-border spinner-border-sm me-2"></span>Restarting...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
card.addEventListener("click", function () {
|
||||||
|
if (this.dataset.active == "0") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem("restart_" + this.dataset.mac)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedCard = this;
|
||||||
|
selectedMac = this.dataset.mac;
|
||||||
|
selectedName = this.dataset.name;
|
||||||
|
document.getElementById("deviceModalTitle").innerText =
|
||||||
|
this.dataset.name;
|
||||||
|
document.getElementById("deviceIp").innerText =
|
||||||
|
this.dataset.ip || "-";
|
||||||
|
document.getElementById("deviceSwitch").innerText =
|
||||||
|
this.dataset.switch || "-";
|
||||||
|
document.getElementById("devicePort").innerText =
|
||||||
|
this.dataset.port || "-";
|
||||||
|
document.getElementById("deviceStatus").innerText =
|
||||||
|
this.querySelector(".fw-bold").innerText;
|
||||||
|
deviceModal.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("restartButton")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
deviceModal.hide();
|
||||||
|
document.getElementById("restartTitle").innerText =
|
||||||
|
selectedName + " neu starten?";
|
||||||
|
restartModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("confirmRestart")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
const button = this;
|
||||||
|
button.disabled = true;
|
||||||
|
fetch("/restart/" + encodeURIComponent(selectedMac), {
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
button.disabled = false;
|
||||||
|
if (!data.success) {
|
||||||
|
alert(data.message || "Neustart konnte nicht gestartet werden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restartModal.hide();
|
||||||
|
deviceModal.hide();
|
||||||
|
sessionStorage.setItem(
|
||||||
|
"restart_" + selectedMac,
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
const span = selectedCard.querySelector(".fw-bold");
|
||||||
|
span.innerHTML =
|
||||||
|
'<span class="spinner-border spinner-border-sm me-2"></span>Restarting...';
|
||||||
|
span.style.color = "#ffc107";
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
button.disabled = false;
|
||||||
|
alert(
|
||||||
|
"Fehler beim Starten des Neustarts.\n\n" +
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Timer alle 1 Sekunde aktualisieren
|
// Timer alle 1 Sekunde aktualisieren
|
||||||
setInterval(updateTimer, 1000);
|
setInterval(updateTimer, 1000);
|
||||||
|
|
||||||
@@ -90,4 +192,104 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- ========================================================= -->
|
||||||
|
<!-- Geräteinformationen -->
|
||||||
|
<!-- ========================================================= -->
|
||||||
|
|
||||||
|
<div class="modal fade"
|
||||||
|
id="deviceModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5
|
||||||
|
id="deviceModalTitle"
|
||||||
|
class="modal-title">
|
||||||
|
</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Schließen">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table table-sm align-middle mb-0">
|
||||||
|
<tr>
|
||||||
|
<th style="width:120px;">IP-Adresse</th>
|
||||||
|
<td id="deviceIp"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Switch</th>
|
||||||
|
<td id="deviceSwitch"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Port</th>
|
||||||
|
<td id="devicePort"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td id="deviceStatus"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
id="restartButton"
|
||||||
|
class="btn btn-warning">
|
||||||
|
🔄 Neustarten
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-bs-dismiss="modal">
|
||||||
|
Schließen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ========================================================= -->
|
||||||
|
<!-- Neustart bestätigen -->
|
||||||
|
<!-- ========================================================= -->
|
||||||
|
|
||||||
|
<div class="modal fade"
|
||||||
|
id="restartModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5
|
||||||
|
id="restartTitle"
|
||||||
|
class="modal-title">
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-2">
|
||||||
|
Der Neustart erfolgt durch einen kurzen
|
||||||
|
PoE-Reset.
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Der Pi wird für wenige Sekunden
|
||||||
|
vom Netzwerk getrennt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-bs-dismiss="modal">
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="confirmRestart"
|
||||||
|
class="btn btn-danger">
|
||||||
|
Neustarten
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -85,11 +85,41 @@ expect eof
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function manual_restart() {
|
||||||
|
local target_mac="$1"
|
||||||
|
python3 /srv/poe_manager/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_hostname switch_port switch_user switch_pass mac; do
|
||||||
|
if [[ "$mac" != "$target_mac" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') Manueller Neustart von $dev_name gestartet." >> "$LOGFILE"
|
||||||
|
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 deaktiviert." >> "$LOGFILE"
|
||||||
|
sleep 2
|
||||||
|
enable_poe "$switch_ip" "$switch_port" "$switch_user" "$switch_pass"
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name PoE aktiviert." >> "$LOGFILE"
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') Manueller Neustart von $dev_name abgeschlossen." >> "$LOGFILE"
|
||||||
|
else
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name besitzt keinen PoE-Port." >> "$LOGFILE"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$1" == "restart" ]]; then
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "MAC-Adresse fehlt."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
manual_restart "$2"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
echo "" > "$LOGFILE"
|
echo "" > "$LOGFILE"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
echo "--------------------------------------------------------------------" >> "$LOGFILE"
|
echo "--------------------------------------------------------------------" >> "$LOGFILE"
|
||||||
python3 /srv/poe_manager/generate_ips.py | while IFS=: read -r rpi_ip dev_name switch_ip switch_hostname switch_port switch_user switch_pass; do
|
python3 /srv/poe_manager/generate_ips.py | while IFS='|' read -r rpi_ip dev_name switch_ip switch_hostname switch_port switch_user switch_pass; do
|
||||||
if ping -c 1 -W 2 "$rpi_ip" &> /dev/null; then
|
if ping -c 1 -W 2 "$rpi_ip" &> /dev/null; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist erreichbar!" >> "$LOGFILE"
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $dev_name ist erreichbar!" >> "$LOGFILE"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user