From 2035e05f4f27bf23a1144ac93a7fef60ae99033d Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Thu, 26 Sep 2024 21:30:47 -0400 Subject: [PATCH] Sort Device Tracker Selector --- custom_components/opnsense/config_flow.py | 50 +++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/custom_components/opnsense/config_flow.py b/custom_components/opnsense/config_flow.py index 97dfa75..e743b21 100644 --- a/custom_components/opnsense/config_flow.py +++ b/custom_components/opnsense/config_flow.py @@ -1,6 +1,7 @@ """Config flow for OPNsense integration.""" from collections.abc import Mapping +import ipaddress import logging from typing import Any from urllib.parse import quote_plus, urlparse @@ -40,6 +41,14 @@ _LOGGER = logging.getLogger(__name__) +def is_ip_address(value) -> bool: + try: + ipaddress.ip_address(value) + return True + except ValueError: + return False + + def cleanse_sensitive_data(message, secrets=[]): for secret in secrets: if secret is not None: @@ -245,9 +254,11 @@ async def async_step_init(self, user_input=None): async def async_step_device_tracker(self, user_input=None): """Handle device tracker list step.""" url = self.config_entry.data[CONF_URL].strip() - username = self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME) - password = self.config_entry.data[CONF_PASSWORD] - verify_ssl = self.config_entry.data.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) + username: str = self.config_entry.data.get(CONF_USERNAME, DEFAULT_USERNAME) + password: str = self.config_entry.data[CONF_PASSWORD] + verify_ssl: bool = self.config_entry.data.get( + CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL + ) client = OPNsenseClient( url=url, username=username, @@ -256,32 +267,47 @@ async def async_step_device_tracker(self, user_input=None): opts={"verify_ssl": verify_ssl}, ) if user_input is None and (arp_table := await client.get_arp_table(True)): - selected_devices = self.config_entry.options.get(CONF_DEVICES, []) + selected_devices: list = self.config_entry.options.get(CONF_DEVICES, []) # dicts are ordered so put all previously selected items at the top - entries = {} + entries: Mapping[str, Any] = {} for device in selected_devices: entries[device] = device # follow with all arp table entries for entry in arp_table: - mac: str = entry.get("mac", "").lower() + mac: str = entry.get("mac", "").lower().strip() if len(mac) < 1: continue - - hostname: str = entry.get("hostname", "").strip("?") - ip: str = entry.get("ip", "") - - label: str = f"{mac} - {hostname.strip()} ({ip.strip()})" + hostname: str = entry.get("hostname", "").strip("?").strip() + ip: str = entry.get("ip", "").strip() + label: str = f"{ip} {'('+hostname+') ' if hostname else ''}[{mac}]" entries[mac] = label + sorted_entries: Mapping[str, Any] = { + key: value + for key, value in sorted( + entries.items(), + key=lambda item: ( + ( + 0 if not is_ip_address(item[1].split()[0]) else 1 + ), # Sort MAC address only labels first + ( + item[1].split()[0] + if not is_ip_address(item[1].split()[0]) + else ipaddress.ip_address(item[1].split()[0]) + ), + ), + ) + } + return self.async_show_form( step_id="device_tracker", data_schema=vol.Schema( { vol.Optional( CONF_DEVICES, default=selected_devices - ): cv.multi_select(entries), + ): cv.multi_select(sorted_entries), } ), )