ha(H4): исправления по ревью — маскировка ключа в дампе, reload-repair, reconfigure сохраняет поля
- major: diagnostics больше не содержит полный lanip_key (esp_home — маска, entry_data redacted); ключ для ESPHome — через reconfigure/CLI; план §3 синхронизирован; дамп при незагруженной записи не падает - reconfigure: сохраняет listen_port/keepalive_ms и текущие overrides/ temp_step; превью-дефолты подхватывают значения записи - repair fix-flow: подтверждение перезагружает запись (issue снимается только когда сессия вышла из key_error; иначе создаётся заново) - key_issue_id helper, использование async_remove_issue (мёртвый код), CONF_EMAIL/PASSWORD в TO_REDACT, placeholders в fix_flow, тест плейсхолдеров ru - тесты обновлены (47 components)
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
"""Диагностика fglair: параметры устройства для копирования в ESPHome.
|
||||
"""Диагностика fglair: параметры устройства для проверки и поддержки.
|
||||
|
||||
План §3: на странице устройства видны dsn/lanip_key/lanip_key_id/host —
|
||||
источник для secrets.yaml ESPHome; рядом маскированная копия ключа для
|
||||
пересылки дампов. Дамп доступен только администратору HA; полный ключ
|
||||
включён намеренно (это статический ключ модуля, не пароль облака).
|
||||
Дамп redacted: lanip_key маскируется (HA-конвенция; дампы прикладывают в
|
||||
issues). Полный ключ для secrets.yaml ESPHome берётся явным действием:
|
||||
«Настроить заново» (reconfigure) на карточке интеграции или CLI
|
||||
``python -m pyfglair discover`` на хосте HA.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -18,49 +18,31 @@ from pyfglair.templates import template_info
|
||||
from .const import (
|
||||
CONF_DEVICE_PORT,
|
||||
CONF_DSN,
|
||||
CONF_EMAIL,
|
||||
CONF_HOST,
|
||||
CONF_LANIP_KEY,
|
||||
CONF_LANIP_KEY_ID,
|
||||
CONF_LISTEN_PORT,
|
||||
CONF_MODEL,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_TEMP_STEP,
|
||||
CONF_TEMPLATE,
|
||||
)
|
||||
from .coordinator import FglairRuntime
|
||||
|
||||
TO_REDACT = {CONF_LANIP_KEY, "password", "email"}
|
||||
REDACTED = "**REDACTED**"
|
||||
TO_REDACT = {CONF_LANIP_KEY, CONF_PASSWORD, CONF_EMAIL}
|
||||
|
||||
|
||||
def _masked(value: Any) -> Any:
|
||||
if not isinstance(value, str) or not value:
|
||||
return REDACTED
|
||||
if len(value) <= 4:
|
||||
if not isinstance(value, str) or len(value) <= 4:
|
||||
return REDACTED
|
||||
return f"{value[:4]}…{REDACTED}"
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
runtime: FglairRuntime = entry.runtime_data
|
||||
client = runtime.client
|
||||
data = client.data
|
||||
key = entry.data.get(CONF_LANIP_KEY)
|
||||
|
||||
values: dict[str, Any] = {}
|
||||
for info in template_info(client.template):
|
||||
value = client.cached(info.prop)
|
||||
if value is None:
|
||||
continue
|
||||
values[info.name] = {
|
||||
"raw": value.int_value,
|
||||
"display": runtime.conversions.to_display(
|
||||
info.prop, value.int_value
|
||||
),
|
||||
}
|
||||
|
||||
def _base_diagnostics(entry: ConfigEntry) -> dict[str, Any]:
|
||||
data = entry.data
|
||||
return {
|
||||
"config": {
|
||||
CONF_HOST: data.get(CONF_HOST),
|
||||
@@ -74,22 +56,42 @@ async def async_get_config_entry_diagnostics(
|
||||
},
|
||||
"esp_home": {
|
||||
CONF_DSN: data.get(CONF_DSN),
|
||||
CONF_LANIP_KEY: key,
|
||||
CONF_LANIP_KEY: _masked(data.get(CONF_LANIP_KEY)),
|
||||
CONF_LANIP_KEY_ID: data.get(CONF_LANIP_KEY_ID),
|
||||
CONF_HOST: data.get(CONF_HOST),
|
||||
},
|
||||
"esp_home_masked": {
|
||||
CONF_DSN: data.get(CONF_DSN),
|
||||
CONF_LANIP_KEY: _masked(key),
|
||||
CONF_LANIP_KEY_ID: data.get(CONF_LANIP_KEY_ID),
|
||||
CONF_HOST: data.get(CONF_HOST),
|
||||
},
|
||||
"entry_data": async_redact_data(dict(entry.data), TO_REDACT),
|
||||
"overrides": entry.data.get("overrides", {}),
|
||||
"runtime": {
|
||||
"state": client.state.name.lower(),
|
||||
"last_error": client.last_error.name.lower(),
|
||||
"events_dropped": client.events_dropped,
|
||||
"values": values,
|
||||
},
|
||||
"entry_data": async_redact_data(dict(data), TO_REDACT),
|
||||
"overrides": data.get("overrides", {}),
|
||||
}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
diag = _base_diagnostics(entry)
|
||||
runtime: Optional[FglairRuntime] = getattr(entry, "runtime_data", None)
|
||||
if runtime is None:
|
||||
diag["runtime"] = {"loaded": False}
|
||||
return diag
|
||||
|
||||
client = runtime.client
|
||||
values: dict[str, Any] = {}
|
||||
for info in template_info(client.template):
|
||||
value = client.cached(info.prop)
|
||||
if value is None:
|
||||
continue
|
||||
values[info.name] = {
|
||||
"raw": value.int_value,
|
||||
"display": runtime.conversions.to_display(
|
||||
info.prop, value.int_value
|
||||
),
|
||||
}
|
||||
|
||||
diag["runtime"] = {
|
||||
"loaded": True,
|
||||
"state": client.state.name.lower(),
|
||||
"last_error": client.last_error.name.lower(),
|
||||
"events_dropped": client.events_dropped,
|
||||
"values": values,
|
||||
}
|
||||
return diag
|
||||
|
||||
Reference in New Issue
Block a user