From 310b36317e1fcc80f9fc4d7454c8a26ba6dc3a3a Mon Sep 17 00:00:00 2001 From: nessi Date: Sun, 16 Aug 2026 20:07:19 +0200 Subject: [PATCH] feat: add drive temperature monitoring with async updates and multi-pattern SMART parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add smart_temperature() function with smartctl -A command execution, fallback to -d sat mode, and regex patterns for Temperature_Celsius/Airflow_Temperature_Cel/Drive_Temperature/Current Drive Temperature attributes plus Temperature: field parsing with 1-3 digit capture and °C formatting. Add temperature_futures dict with service_check_executor.submit() calls for parallel temperature reads alongside smart_futures. Add disk[" --- app.py | 23 ++++++++++++++++++++++- templates/index.html | 3 +++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 81e7a01..d42b759 100644 --- a/app.py +++ b/app.py @@ -352,6 +352,24 @@ def smart_status(device): health = re.search(r"(?:SMART overall-health self-assessment test result|SMART Health Status):\s*(.+)", smart_output, re.IGNORECASE) return health.group(1).strip() if health else ("Fehler: " + smart_error.strip()[:80] if smart_error.strip() else "Nicht unterstützt") +def smart_temperature(device): + """Read the drive temperature in a controller-friendly way.""" + if not SMART_ENABLED or not shutil.which("smartctl"): + return "Nicht verfügbar" + smart_device = device.replace("/dev/", f"{HOST_DEVICE_PATH.rstrip('/')}/", 1) + output, error = _read_command(["smartctl", "-A", smart_device], timeout=6, warn_on_nonzero=False) + if not output: + output, error = _read_command(["smartctl", "-A", "-d", "sat", smart_device], timeout=6, warn_on_nonzero=False) + patterns = ( + r"(?:Temperature_Celsius|Airflow_Temperature_Cel|Drive_Temperature|Current Drive Temperature)\s+.*?\s(\d{1,3})(?:\s*°?C)?\s*$", + r"^\s*Temperature:\s*(\d{1,3})\s*(?:C|°C|Celsius)?\s*$", + ) + for pattern in patterns: + match = re.search(pattern, output, re.IGNORECASE | re.MULTILINE) + if match: + return f"{int(match.group(1))} °C" + return "Nicht verfügbar" + def raid_status(): global raid_log_state mdstat, mdstat_error = "", "" @@ -406,8 +424,10 @@ def raid_status(): except (ValueError, AttributeError): block_devices = {} smart_futures = {} - if SMART_ENABLED and shutil.which("smartctl"): + temperature_futures = {} + if shutil.which("smartctl"): smart_futures = {disk["device"]: service_check_executor.submit(smart_status, disk["device"]) for disk in devices} + temperature_futures = {disk["device"]: service_check_executor.submit(smart_temperature, disk["device"]) for disk in devices} for disk in devices: info = block_devices.get(disk["device"], {}) parent = re.sub(r"(p)?\d+$", "", disk["device"]) @@ -435,6 +455,7 @@ def raid_status(): elif disk["smart"] != "PASSED" and smart_log_key not in hardware_log_once: log.warning("SMART-Status für %s nicht verfügbar: %s", disk["device"], disk["smart"]) hardware_log_once.add(smart_log_key) + disk["temperature"] = temperature_futures[disk["device"]].result() if disk["device"] in temperature_futures else smart_temperature(disk["device"]) missing = members_match and members_match.group(1) != members_match.group(2) failed = detail.get("failed_devices", 0) > 0 or any(disk["status"] == "failed" for disk in devices) rebuilding = bool(progress_match) diff --git a/templates/index.html b/templates/index.html index 724776e..f328f0d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,5 +1,8 @@ +