Translate repository documentation and configuration added for ldap logging

This commit is contained in:
2026-08-03 20:57:48 +00:00
parent 8a9115af83
commit b54b180115
14 changed files with 170 additions and 118 deletions
+20 -20
View File
@@ -77,7 +77,7 @@ def _normalize_mac(value: Any) -> str | None:
def _extract_devices(payload: Any) -> list[dict[str, Any]]:
if isinstance(payload, list):
direct = [item for item in payload if isinstance(item, dict)]
# MeshCtrl kann mehrere vollständige JSON-Blöcke nacheinander ausgeben.
# MeshCtrl can emit multiple complete JSON blocks in sequence.
nested: list[dict[str, Any]] = []
for item in payload:
if isinstance(item, (list, dict)):
@@ -106,14 +106,14 @@ def _extract_devices(payload: Any) -> list[dict[str, Any]]:
def _strip_terminal_noise(output: str) -> str:
"""Entfernt BOM, ANSI-Steuersequenzen und NUL-Zeichen aus MeshCtrl-Ausgaben."""
"""Remove BOM markers, ANSI control sequences, and NUL bytes from MeshCtrl output."""
output = output.lstrip("\ufeff")
output = output.replace("\x00", "")
return re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", output)
def _parse_json_output(output: str) -> Any:
"""Liest auch JSON mit vor-/nachgelagerten Hinweisen oder mehreren JSON-Blöcken."""
"""Parse JSON even when surrounded by notices or when multiple JSON blocks are present."""
cleaned = _strip_terminal_noise(output).strip()
if not cleaned:
raise ValueError("MeshCtrl lieferte eine leere Ausgabe.")
@@ -137,7 +137,7 @@ def _parse_json_output(output: str) -> Any:
values.append(value)
position = end
except json.JSONDecodeError as exc:
# Ein nicht valider Detailblock darf nicht zu einer irreführenden Standardmeldung führen.
# An invalid detail block must not produce a misleading default message.
context_start = max(0, exc.pos - 120)
context_end = min(len(cleaned), exc.pos + 120)
context = cleaned[context_start:context_end].replace("\n", "\\n")
@@ -175,7 +175,7 @@ def _run_meshctrl(
command.append("--noverify")
# stdout/stderr bewusst direkt in Dateien schreiben. Dadurch umgehen wir
# mögliche Pipe-/Puffergrenzen bei sehr großen MeshCtrl-JSON-Ausgaben.
# possible pipe or buffer limits with very large MeshCtrl JSON output.
diagnostic_dir = Path(os.getenv("DIAGNOSTIC_DIR", "/app/data/logs/diagnostics"))
diagnostic_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
@@ -198,9 +198,9 @@ def _run_meshctrl(
stdout_text = stdout_bytes.decode("utf-8", errors="replace")
stderr_text = stderr_bytes.decode("utf-8", errors="replace")
# Leere stderr-Dateien entfernen. Erfolgreiche, häufige Abfragen wie die
# Präsenzprüfung können ihre stdout-Rohdatei nach dem Einlesen verwerfen.
# Bei MeshCtrl-Fehlern bleibt die Datei unabhängig von retain_stdout erhalten.
# Remove empty stderr files. Successful high-frequency queries such as the
# presence check may discard their raw stdout file after it has been parsed.
# For MeshCtrl errors, the file is retained regardless of retain_stdout.
if not stderr_bytes:
stderr_path.unlink(missing_ok=True)
if not retain_stdout and completed.returncode == 0:
@@ -220,7 +220,7 @@ def _looks_truncated(output: str) -> bool:
cleaned = _strip_terminal_noise(output).rstrip()
if not cleaned:
return False
# Der beobachtete Fehler endet nahe 64 KiB mitten in einem JSON-Objekt.
# The observed failure ends near 64 KiB in the middle of a JSON object.
return len(cleaned) >= 65000 and cleaned[-1] not in "]}"
@@ -424,8 +424,8 @@ def fetch_devices(log: Callable[[str, str], None] | None = None) -> list[dict[st
except (ValueError, json.JSONDecodeError, RuntimeError) as exc:
attempts.append(("mit Details: " if include_details else "ohne Details: ") + str(exc))
log("warning", f"JSON-Auswertung {mode} fehlgeschlagen: {exc}")
# Manche MeshCentral-/MeshCtrl-Versionen liefern bei --details einen
# beschädigten sehr großen JSON-Block. Dann automatisch Basisdaten abrufen.
# Some MeshCentral or MeshCtrl versions return a
# corrupted, very large JSON block with --details. Fall back to basic data automatically.
continue
raise RuntimeError("MeshCentral-Import fehlgeschlagen. " + " | ".join(attempts))
@@ -443,7 +443,7 @@ def _bytes_to_gb(value: Any, binary: bool = False) -> str | None:
def _path_values(data: Any, path: str) -> list[Any]:
"""Liest Punktpfade; mehrere Alternativen können mit | getrennt werden."""
"""Read dotted paths; multiple alternatives may be separated with |."""
for alternative in [item.strip() for item in str(path or "").split("|") if item.strip()]:
values = [data]
for part in alternative.split('.'):
@@ -639,8 +639,8 @@ def _custom_value(row: AssetFieldValue | None, definition: FieldDefinition) -> A
def _coerce_system_value(target: str, value: Any) -> Any:
# Die meisten Asset-Spalten sind Textfelder. Integer-/Fremdschlüsselfelder
# werden typgerecht geschrieben, damit dynamische Mappings auch dort sicher sind.
# Most asset columns are text fields. Integer and foreign-key fields
# are written with the appropriate type so dynamic mappings remain safe there as well.
if target in {"mesh_mtype", "parent_asset_id", "category_id"}:
try:
return int(value) if value not in (None, "") else None
@@ -652,7 +652,7 @@ def _coerce_system_value(target: str, value: Any) -> Any:
def map_device(raw: dict[str, Any], field_mappings: list[dict[str, Any]] | None = None) -> dict[str, Any]:
"""Erzeugt Assetwerte ausschließlich aus den aktivierten Datenbank-Mappings."""
"""Build asset values exclusively from enabled database mappings."""
mapped: dict[str, Any] = {}
for mapping in field_mappings or []:
if not isinstance(mapping, dict) or not mapping.get('enabled', True):
@@ -662,7 +662,7 @@ def map_device(raw: dict[str, Any], field_mappings: list[dict[str, Any]] | None
continue
value = _mapped_value(raw, mapping)
if value not in (None, '', [], {}):
# Niedrigere Priorität wird zuerst verarbeitet; spätere Regeln dürfen überschreiben.
# Lower priorities are processed first; later rules may override earlier values.
mapped[target] = value
return mapped
@@ -949,8 +949,8 @@ def synchronize(db: Session, log: Callable[[str, str], None] | None = None) -> S
history_changes: dict[str, dict[str, str]] = {}
changed = False
# Ausschließlich die aktiven Mapping-Regeln bestimmen, welche
# System- und Custom-Felder synchronisiert werden. Es gibt keine
# Only enabled mapping rules determine which
# system and custom fields are synchronized. There is no
# feste Feld-Whitelist mehr.
for target, incoming in mapped.items():
if target == "mesh_device_type" or incoming in (None, "", [], {}):
@@ -958,8 +958,8 @@ def synchronize(db: Session, log: Callable[[str, str], None] | None = None) -> S
definition = field_definitions.get(target)
if definition is None:
# Das Zielfeld muss im zentralen Feldkatalog existieren.
# Die Prüfung wird zusätzlich vor der Geräteschleife protokolliert.
# The target field must exist in the central field catalog.
# This validation is also logged before the device loop starts.
continue
rule = mapping_rules.get(target, "fill_empty")