From 896cec95ad60e099792687c176178d940b43bed3 Mon Sep 17 00:00:00 2001 From: Snuffy2 Date: Sun, 24 Nov 2024 15:04:02 -0500 Subject: [PATCH] Alias Toggle Action/Service --- custom_components/opnsense/const.py | 1 + .../opnsense/pyopnsense/__init__.py | 55 +++++++++++++++++++ custom_components/opnsense/services.py | 36 ++++++++++++ custom_components/opnsense/services.yaml | 54 ++++++++++++++++++ 4 files changed, 146 insertions(+) diff --git a/custom_components/opnsense/const.py b/custom_components/opnsense/const.py index aabc2d4..819dc67 100644 --- a/custom_components/opnsense/const.py +++ b/custom_components/opnsense/const.py @@ -280,3 +280,4 @@ SERVICE_RELOAD_INTERFACE = "reload_interface" SERVICE_GENERATE_VOUCHERS = "generate_vouchers" SERVICE_KILL_STATES = "kill_states" +SERVICE_TOGGLE_ALIAS = "toggle_alias" diff --git a/custom_components/opnsense/pyopnsense/__init__.py b/custom_components/opnsense/pyopnsense/__init__.py index 8f2a91e..614b3e8 100644 --- a/custom_components/opnsense/pyopnsense/__init__.py +++ b/custom_components/opnsense/pyopnsense/__init__.py @@ -2162,3 +2162,58 @@ async def kill_states(self, ip_addr) -> Mapping[str, Any]: "success": bool(response.get("result", None) == "ok"), "dropped_states": response.get("dropped_states", 0), } + + async def toggle_alias(self, alias, toggle_on_off) -> bool: + + alias_list_resp: Mapping[str, Any] | list = await self._get( + "/api/firewall/alias/searchItem" + ) + if not isinstance(alias_list_resp, Mapping): + return False + alias_list: list = alias_list_resp.get("rows", []) + if not isinstance(alias_list, list): + return False + uuid: str | None = None + for item in alias_list: + if not isinstance(item, Mapping): + continue + if item.get("name") == alias: + uuid = item.get("uuid") + break + if not uuid: + return False + payload: Mapping[str, Any] = {} + url: str = f"/api/firewall/alias/toggleItem/{uuid}" + if toggle_on_off == "on": + url = url + "/1" + elif toggle_on_off == "off": + url = url + "/0" + response: Mapping[str, Any] | list = await self._post( + url, + payload=payload, + ) + _LOGGER.debug( + f"[toggle_alias] alias: {alias}, uuid: {uuid}, action: {toggle_on_off}, " + f"url: {url}, response: {response}" + ) + if ( + not isinstance(response, Mapping) + or "result" not in response + or response.get("result") == "failed" + ): + return False + + set_resp: Mapping[str, Any] | list = await self._post("/api/firewall/alias/set") + if not isinstance(set_resp, Mapping) or set_resp.get("result") != "saved": + return False + + reconfigure_resp: Mapping[str, Any] | list = await self._post( + "/api/firewall/alias/reconfigure" + ) + if ( + not isinstance(reconfigure_resp, Mapping) + or reconfigure_resp.get("status") != "ok" + ): + return False + + return True diff --git a/custom_components/opnsense/services.py b/custom_components/opnsense/services.py index 350112e..e6ec1d6 100644 --- a/custom_components/opnsense/services.py +++ b/custom_components/opnsense/services.py @@ -26,6 +26,7 @@ SERVICE_STOP_SERVICE, SERVICE_SYSTEM_HALT, SERVICE_SYSTEM_REBOOT, + SERVICE_TOGGLE_ALIAS, ) from .pyopnsense import VoucherServerError @@ -268,6 +269,25 @@ async def service_kill_states(call: ServiceCall) -> ServiceResponse: if call.return_response: return return_response + async def service_toggle_alias(call: ServiceCall) -> None: + clients: list = await _get_clients( + call.data.get("device_id", []), call.data.get("entity_id", []) + ) + success = None + for client in clients: + response = await client.toggle_alias( + call.data.get("alias"), call.data.get("toggle_on_off") + ) + _LOGGER.debug( + f"[service_toggle_alias] client: {client.name}, alias: {call.data.get("alias")}, response: {response}" + ) + if success is None or success: + success = response + if success is None or not success: + raise ServiceValidationError( + f"Toggle Alias Failed. client: {client.name}, alias: {call.data.get("alias")}, action: {call.data.get("toggle_on_off")}" + ) + hass.services.async_register( domain=DOMAIN, service=SERVICE_CLOSE_NOTICE, @@ -430,3 +450,19 @@ async def service_kill_states(call: ServiceCall) -> ServiceResponse: service_func=service_kill_states, supports_response=SupportsResponse.OPTIONAL, ) + + hass.services.async_register( + domain=DOMAIN, + service=SERVICE_TOGGLE_ALIAS, + schema=vol.Schema( + { + vol.Required("alias"): vol.Any(cv.string), + vol.Required("toggle_on_off", default="toggle"): vol.In( + {"toggle": "Toggle", "on": "On", "off": "Off"} + ), + vol.Optional("device_id"): vol.Any(cv.string), + vol.Optional("entity_id"): vol.Any(cv.string), + } + ), + service_func=service_toggle_alias, + ) diff --git a/custom_components/opnsense/services.yaml b/custom_components/opnsense/services.yaml index 26ce803..0fd2c0f 100644 --- a/custom_components/opnsense/services.yaml +++ b/custom_components/opnsense/services.yaml @@ -474,3 +474,57 @@ kill_states: filter: - integration: opnsense domain: sensor + +toggle_alias: + name: Toggle Alias + fields: + alias: + name: Alias Name + required: true + advanced: false + example: "iphones" + selector: + text: + toggle_on_off: + name: Alias Action + required: true + advanced: false + default: "toggle" + selector: + select: + multiple: false + custom_value: false + mode: list + options: + - label: "Toggle" + value: "toggle" + - label: "On" + value: "on" + - label: "Off" + value: "off" + multiple_opnsense: + name: Only needed if there is more than one OPNsense Router + collapsed: true + fields: + device_id: + name: OPNsense Device + description: Select the OPNsense Router to call the command on. If not specified, the command will be sent to all OPNsense Routers. + required: false + selector: + device: + multiple: false + filter: + - integration: opnsense + entity: + - domain: sensor + entity_id: + name: OPNsense Entity + description: Pick any sensor in the OPNsense Router you want to call the command on. If not specified, the command will be sent to all OPNsense Routers. + example: "sensor.opnsense_interface_lan_status" + required: false + selector: + entity: + multiple: false + filter: + - integration: opnsense + domain: sensor