Translate repository documentation and configuration added for ldap logging
This commit is contained in:
+20
-20
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user