From fee69e65b3d795baad439f0e43607c997fd6fc22 Mon Sep 17 00:00:00 2001 From: host Date: Mon, 3 Aug 2026 00:22:09 +0300 Subject: [PATCH] =?UTF-8?q?v2.1.2:=20list=5Fapp=5Fcatalog=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D1=82=20GET=20=D0=B8?= =?UTF-8?q?=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20=D0=BF=D1=83?= =?UTF-8?q?=D1=82=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- truenas_tools.py | 118 ++++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/truenas_tools.py b/truenas_tools.py index c1eb7de..1f167f1 100644 --- a/truenas_tools.py +++ b/truenas_tools.py @@ -1,48 +1,70 @@ -def _rest_request(self, method: str, endpoint: str, payload: Optional[dict] = None, timeout: Optional[int] = None) -> Optional[Any]: - method = method.upper() - if method not in ("POST", "PUT", "DELETE", "PATCH", "GET"): - self._last_error = f"_rest_request: неподдерживаемый метод {method}" - return None - url = f"{self._base}/api/v2.0/{endpoint}" - t = timeout if timeout is not None else self.valves.request_timeout - try: - r = requests.request(method, url, headers=self._headers, json=payload if payload is not None else {}, timeout=t, verify=self.valves.verify_ssl) - # v2.1.2: автоматический fallback POST → GET при 405 (Method Not Allowed). - # Это часто встречается в TrueNAS 25.10: некоторые эндпоинты каталога - # исторически были POST, но в новых версиях стали GET. - if r.status_code == 405 and method == "POST": - try: - r2 = requests.get(url, headers=self._headers, params=payload or {}, timeout=t, verify=self.valves.verify_ssl) - if r2.status_code in (200, 201, 202, 204): - if not r2.text: - return {"status": "ok", "http_code": r2.status_code, "via": "GET-fallback"} - try: - data = r2.json() - if isinstance(data, dict): - data["_via"] = "GET-fallback" - return data - except Exception: - return {"status": "ok", "text": r2.text, "http_code": r2.status_code, "_via": "GET-fallback"} - except Exception: - pass # вернёмся к исходной ошибке ниже - if r.status_code in (200, 201, 202, 204): - if not r.text: - return {"status": "ok", "http_code": r.status_code} - try: - return r.json() - except Exception: - return {"status": "ok", "text": r.text, "http_code": r.status_code} - self._last_error = f"{method} {endpoint} → HTTP {r.status_code}: {r.text[:400]}" - return None - except requests.exceptions.SSLError as e: - self._last_error = f"SSL-ошибка {method} {endpoint}: {e}" - return None - except requests.exceptions.ConnectionError as e: - self._last_error = f"Соединение {method} {endpoint}: {e}" - return None - except requests.exceptions.Timeout: - self._last_error = f"Таймаут {method} {endpoint} (> {t}с)" - return None - except Exception as e: - self._last_error = f"{method} {endpoint}: {type(e).__name__}: {e}" - return None \ No newline at end of file + payload: Dict[str, Any] = {"catalog_name": catalog, "retrieve_all_trains": True} + if category: + payload["categories"] = [category] + # v2.1.2: TrueNAS 25.10 исторически делал POST /app/available, но в новых билдах + # многие эндпоинты переехали на GET. _rest_request сам делает POST→GET fallback + # при 405, поэтому здесь используем _post — он сам разберётся. + # Дополнительно пробуем GET напрямую для надёжности. + endpoints = [ + "app/available", # новый (Electric Eel+) + "app.available", + "chart/release/names", + "chart.release.names", + "chart_release/names", + "catalog/items", # fallback + "catalog.items", + "catalog_items", + "app/available_apps", + "app/list_apps", + ] + apps: List[Dict[str, Any]] = [] + used_ep = None + used_method = None + for ep in endpoints: + # Сначала GET (часто работает на 25.10) + try: + r = requests.get( + f"{self._base}/api/v2.0/{ep}", + headers=self._headers, + params=payload if payload else None, + timeout=self.valves.request_timeout, + verify=self.valves.verify_ssl, + ) + if r.status_code == 200: + try: + data = r.json() + if isinstance(data, list): + apps = data + used_ep = ep + used_method = "GET" + break + if isinstance(data, dict): + for key in ("items", "applications", "releases", "apps"): + if key in data and isinstance(data[key], list): + apps = data[key] + used_ep = ep + used_method = "GET" + break + if apps: + break + except Exception: + pass + except Exception: + pass + # Затем POST (через _rest_request — у него есть fallback на GET при 405) + res = self._post(ep, payload, timeout=self.valves.request_timeout) + if res is not None: + if isinstance(res, list): + apps = res + used_ep = ep + used_method = "POST" + break + if isinstance(res, dict): + for key in ("items", "applications", "releases", "apps"): + if key in res and isinstance(res[key], list): + apps = res[key] + used_ep = ep + used_method = res.get("_via", "POST") + break + if apps: + break \ No newline at end of file