Skip to content

Commit cca5c80

Browse files
authored
Store nmap tracker options as lists (home-assistant#154378)
1 parent 8943321 commit cca5c80

File tree

6 files changed

+195
-57
lines changed

6 files changed

+195
-57
lines changed

homeassistant/components/nmap_tracker/__init__.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
from .const import (
3131
CONF_HOME_INTERVAL,
32+
CONF_HOSTS_EXCLUDE,
33+
CONF_HOSTS_LIST,
3234
CONF_MAC_EXCLUDE,
3335
CONF_OPTIONS,
3436
DOMAIN,
@@ -104,6 +106,40 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
104106
return unload_ok
105107

106108

109+
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
110+
"""Migrate config entry."""
111+
_LOGGER.debug(
112+
"Migrating configuration from version %s.%s", entry.version, entry.minor_version
113+
)
114+
115+
if entry.version > 1:
116+
# This means the user has downgraded from a future version
117+
return False
118+
119+
if entry.version == 1:
120+
new_options = {**entry.options}
121+
if entry.minor_version < 2:
122+
new_options[CONF_HOSTS_LIST] = cv.ensure_list_csv(
123+
new_options.get(CONF_HOSTS, [])
124+
)
125+
new_options[CONF_HOSTS_EXCLUDE] = cv.ensure_list_csv(
126+
new_options.get(CONF_EXCLUDE, [])
127+
)
128+
new_options[CONF_MAC_EXCLUDE] = []
129+
130+
hass.config_entries.async_update_entry(
131+
entry, options=new_options, minor_version=2, version=1
132+
)
133+
134+
_LOGGER.debug(
135+
"Migration to configuration version %s.%s successful",
136+
entry.version,
137+
entry.minor_version,
138+
)
139+
140+
return True
141+
142+
107143
@callback
108144
def _async_untrack_devices(hass: HomeAssistant, entry: ConfigEntry) -> None:
109145
"""Remove tracking for devices owned by this config entry."""
@@ -159,10 +195,8 @@ async def async_setup(self):
159195
self._scan_interval = timedelta(
160196
seconds=config.get(CONF_SCAN_INTERVAL, TRACKER_SCAN_INTERVAL)
161197
)
162-
hosts_list = cv.ensure_list_csv(config[CONF_HOSTS])
163-
self._hosts = [host for host in hosts_list if host != ""]
164-
excludes_list = cv.ensure_list_csv(config[CONF_EXCLUDE])
165-
self._exclude = [exclude for exclude in excludes_list if exclude != ""]
198+
self._hosts = config.get(CONF_HOSTS_LIST, [])
199+
self._exclude = config.get(CONF_HOSTS_EXCLUDE, [])
166200
self._mac_exclude = config.get(CONF_MAC_EXCLUDE, [])
167201
self._options = config[CONF_OPTIONS]
168202
self.home_interval = timedelta(

homeassistant/components/nmap_tracker/config_flow.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@
2121
ConfigFlowResult,
2222
OptionsFlowWithReload,
2323
)
24-
from homeassistant.const import CONF_EXCLUDE, CONF_HOSTS
2524
from homeassistant.core import HomeAssistant, callback
26-
from homeassistant.helpers import config_validation as cv
2725
from homeassistant.helpers.device_registry import format_mac
2826
from homeassistant.helpers.selector import TextSelector, TextSelectorConfig
2927
from homeassistant.helpers.typing import VolDictType
3028

3129
from .const import (
3230
CONF_HOME_INTERVAL,
31+
CONF_HOSTS_EXCLUDE,
32+
CONF_HOSTS_LIST,
3333
CONF_MAC_EXCLUDE,
3434
CONF_OPTIONS,
3535
DEFAULT_OPTIONS,
@@ -56,11 +56,9 @@ async def async_get_network(hass: HomeAssistant) -> str:
5656
return str(ip_network(f"{local_ip}/{network_prefix}", False))
5757

5858

59-
def _normalize_ips_and_network(hosts_str: str) -> list[str] | None:
59+
def _normalize_ips_and_network(hosts: list[str]) -> list[str] | None:
6060
"""Check if a list of hosts are all ips or ip networks."""
61-
6261
normalized_hosts = []
63-
hosts = [host for host in cv.ensure_list_csv(hosts_str) if host != ""]
6462

6563
for host in sorted(hosts):
6664
try:
@@ -118,17 +116,17 @@ def _normalize_mac_addresses(mac_addresses: list[str]) -> list[str] | None:
118116
def normalize_input(user_input: dict[str, Any]) -> dict[str, str]:
119117
"""Validate hosts and exclude are valid."""
120118
errors = {}
121-
normalized_hosts = _normalize_ips_and_network(user_input[CONF_HOSTS])
119+
normalized_hosts = _normalize_ips_and_network(user_input[CONF_HOSTS_LIST])
122120
if not normalized_hosts:
123-
errors[CONF_HOSTS] = "invalid_hosts"
121+
errors[CONF_HOSTS_LIST] = "invalid_hosts"
124122
else:
125-
user_input[CONF_HOSTS] = ",".join(normalized_hosts)
123+
user_input[CONF_HOSTS_LIST] = normalized_hosts
126124

127-
normalized_exclude = _normalize_ips_and_network(user_input[CONF_EXCLUDE])
125+
normalized_exclude = _normalize_ips_and_network(user_input[CONF_HOSTS_EXCLUDE])
128126
if normalized_exclude is None:
129-
errors[CONF_EXCLUDE] = "invalid_hosts"
127+
errors[CONF_HOSTS_EXCLUDE] = "invalid_hosts"
130128
else:
131-
user_input[CONF_EXCLUDE] = ",".join(normalized_exclude)
129+
user_input[CONF_HOSTS_EXCLUDE] = normalized_exclude
132130

133131
normalized_mac_exclude = _normalize_mac_addresses(user_input[CONF_MAC_EXCLUDE])
134132
if normalized_mac_exclude is None:
@@ -142,18 +140,23 @@ def normalize_input(user_input: dict[str, Any]) -> dict[str, str]:
142140
async def _async_build_schema_with_user_input(
143141
hass: HomeAssistant, user_input: dict[str, Any], include_options: bool
144142
) -> vol.Schema:
145-
hosts = user_input.get(CONF_HOSTS, await async_get_network(hass))
146-
exclude = user_input.get(
147-
CONF_EXCLUDE, await network.async_get_source_ip(hass, MDNS_TARGET_IP)
143+
hosts = user_input.get(CONF_HOSTS_LIST, [await async_get_network(hass)])
144+
ip_exclude = user_input.get(
145+
CONF_HOSTS_EXCLUDE, [await network.async_get_source_ip(hass, MDNS_TARGET_IP)]
148146
)
147+
149148
mac_exclude = user_input.get(CONF_MAC_EXCLUDE, [])
150149

151150
schema: VolDictType = {
152-
vol.Required(CONF_HOSTS, default=hosts): str,
151+
vol.Required(CONF_HOSTS_LIST, default=hosts): TextSelector(
152+
TextSelectorConfig(multiple=True)
153+
),
153154
vol.Required(
154155
CONF_HOME_INTERVAL, default=user_input.get(CONF_HOME_INTERVAL, 0)
155156
): int,
156-
vol.Optional(CONF_EXCLUDE, default=exclude): str,
157+
vol.Optional(CONF_HOSTS_EXCLUDE, default=ip_exclude): TextSelector(
158+
TextSelectorConfig(multiple=True)
159+
),
157160
vol.Optional(CONF_MAC_EXCLUDE, default=mac_exclude): TextSelector(
158161
TextSelectorConfig(multiple=True)
159162
),
@@ -195,8 +198,9 @@ async def async_step_init(
195198
self.options.update(user_input)
196199

197200
if not errors:
201+
title_hosts = ", ".join(self.options[CONF_HOSTS_LIST])
198202
return self.async_create_entry(
199-
title=f"Nmap Tracker {self.options[CONF_HOSTS]}", data=self.options
203+
title=f"Nmap Tracker {title_hosts}", data=self.options
200204
)
201205

202206
return self.async_show_form(
@@ -212,6 +216,7 @@ class NmapTrackerConfigFlow(ConfigFlow, domain=DOMAIN):
212216
"""Handle a config flow for Nmap Tracker."""
213217

214218
VERSION = 1
219+
MINOR_VERSION = 2
215220

216221
def __init__(self) -> None:
217222
"""Initialize config flow."""
@@ -230,8 +235,9 @@ async def async_step_user(
230235
self.options.update(user_input)
231236

232237
if not errors:
238+
title_hosts = ", ".join(user_input[CONF_HOSTS_LIST])
233239
return self.async_create_entry(
234-
title=f"Nmap Tracker {user_input[CONF_HOSTS]}",
240+
title=f"Nmap Tracker {title_hosts}",
235241
data={},
236242
options=user_input,
237243
)
@@ -245,9 +251,9 @@ async def async_step_user(
245251
)
246252

247253
def _async_is_unique_host_list(self, user_input: dict[str, Any]) -> bool:
248-
hosts = _normalize_ips_and_network(user_input[CONF_HOSTS])
254+
hosts = _normalize_ips_and_network(user_input[CONF_HOSTS_LIST])
249255
for entry in self._async_current_entries():
250-
if _normalize_ips_and_network(entry.options[CONF_HOSTS]) == hosts:
256+
if _normalize_ips_and_network(entry.options[CONF_HOSTS_LIST]) == hosts:
251257
return False
252258
return True
253259

homeassistant/components/nmap_tracker/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# Interval in minutes to exclude devices from a scan while they are home
1414
CONF_HOME_INTERVAL: Final = "home_interval"
1515
CONF_OPTIONS: Final = "scan_options"
16+
CONF_HOSTS_LIST: Final = "hosts_list"
17+
CONF_HOSTS_EXCLUDE: Final = "hosts_exclude"
1618
CONF_MAC_EXCLUDE: Final = "mac_exclude"
1719
DEFAULT_OPTIONS: Final = "-n -sn -PR -T4 --min-rate 10 --host-timeout 5s"
1820

homeassistant/components/nmap_tracker/strings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
"user": {
2525
"description": "Configure hosts to be scanned by Nmap. IP address and excludes can be addresses (192.168.1.1), networks (192.168.0.0/24) or ranges (192.168.1.0-32).",
2626
"data": {
27-
"hosts": "IP addresses or ranges (comma-separated) to scan",
27+
"hosts": "IP address or range to scan",
2828
"home_interval": "Minimum number of minutes between scans of active devices (preserve battery)",
29-
"exclude": "IP addresses (comma-separated) to exclude from tracking",
29+
"exclude": "IP address to exclude from tracking",
3030
"mac_exclude": "MAC address to exclude from tracking",
3131
"scan_options": "Raw configurable scan options for Nmap"
3232
}

0 commit comments

Comments
 (0)