diff --git a/homeassistant/components/network/strings.json b/homeassistant/components/network/strings.json new file mode 100644 index 00000000000..6aca7343221 --- /dev/null +++ b/homeassistant/components/network/strings.json @@ -0,0 +1,10 @@ +{ + "system_health": { + "info": { + "adapters": "Adapters", + "ipv4_addresses": "IPv4 addresses", + "ipv6_addresses": "IPv6 addresses", + "announce_addresses": "Announce addresses" + } + } +} diff --git a/homeassistant/components/network/system_health.py b/homeassistant/components/network/system_health.py new file mode 100644 index 00000000000..ebabe055539 --- /dev/null +++ b/homeassistant/components/network/system_health.py @@ -0,0 +1,53 @@ +"""Provide info to system health.""" + +from typing import Any + +from homeassistant.components import system_health +from homeassistant.core import HomeAssistant, callback + +from . import Adapter, async_get_adapters, async_get_announce_addresses +from .models import IPv4ConfiguredAddress, IPv6ConfiguredAddress + + +@callback +def async_register( + hass: HomeAssistant, register: system_health.SystemHealthRegistration +) -> None: + """Register system health callbacks.""" + register.async_register_info(system_health_info, "/config/network") + + +def _format_ips(ips: list[IPv4ConfiguredAddress] | list[IPv6ConfiguredAddress]) -> str: + return ", ".join([f"{ip['address']}/{ip['network_prefix']!s}" for ip in ips]) + + +def _get_adapter_info(adapter: Adapter) -> str: + state = "enabled" if adapter["enabled"] else "disabled" + default = ", default" if adapter["default"] else "" + auto = ", auto" if adapter["auto"] else "" + return f"{adapter['name']} ({state}{default}{auto})" + + +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: + """Get info for the info page.""" + + adapters = await async_get_adapters(hass) + data: dict[str, Any] = { + # k: v for adapter in adapters for k, v in _get_adapter_info(adapter).items() + "adapters": ", ".join([_get_adapter_info(adapter) for adapter in adapters]), + "ipv4_addresses": ", ".join( + [ + f"{adapter['name']} ({_format_ips(adapter['ipv4'])})" + for adapter in adapters + ] + ), + "ipv6_addresses": ", ".join( + [ + f"{adapter['name']} ({_format_ips(adapter['ipv6'])})" + for adapter in adapters + ] + ), + "announce_addresses": ", ".join(await async_get_announce_addresses(hass)), + } + + return data diff --git a/tests/components/network/test_system_health.py b/tests/components/network/test_system_health.py new file mode 100644 index 00000000000..eb383aafde7 --- /dev/null +++ b/tests/components/network/test_system_health.py @@ -0,0 +1,32 @@ +"""Test network system health.""" + +import asyncio + +import pytest + +from homeassistant.components.network.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import get_system_health_info + + +@pytest.mark.usefixtures("mock_socket_no_loopback") +async def test_network_system_health(hass: HomeAssistant) -> None: + """Test network system health.""" + + assert await async_setup_component(hass, "system_health", {}) + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + info = await get_system_health_info(hass, "network") + + for key, val in info.items(): + if asyncio.iscoroutine(val): + info[key] = await val + + assert info == { + "adapters": "eth0 (disabled), lo0 (disabled), eth1 (enabled, default, auto), vtun0 (disabled)", + "announce_addresses": "192.168.1.5", + "ipv4_addresses": "eth0 (), lo0 (127.0.0.1/8), eth1 (192.168.1.5/23), vtun0 (169.254.3.2/16)", + "ipv6_addresses": "eth0 (2001:db8::/8), lo0 (), eth1 (), vtun0 ()", + }