Support ignoring discovered devices from the dashboard (#7665)

This commit is contained in:
Jesse Hills 2024-10-25 07:55:14 +13:00 committed by GitHub
parent 5b5c2fe71b
commit ca5c73d170
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 0 deletions

View file

@ -5,10 +5,14 @@ from collections.abc import Coroutine
import contextlib import contextlib
from dataclasses import dataclass from dataclasses import dataclass
from functools import partial from functools import partial
import json
import logging import logging
from pathlib import Path
import threading import threading
from typing import TYPE_CHECKING, Any, Callable from typing import TYPE_CHECKING, Any, Callable
from esphome.storage_json import ignored_devices_storage_path
from ..zeroconf import DiscoveredImport from ..zeroconf import DiscoveredImport
from .dns import DNSCache from .dns import DNSCache
from .entries import DashboardEntries from .entries import DashboardEntries
@ -20,6 +24,8 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
IGNORED_DEVICES_STORAGE_PATH = "ignored-devices.json"
@dataclass @dataclass
class Event: class Event:
@ -74,6 +80,7 @@ class ESPHomeDashboard:
"settings", "settings",
"dns_cache", "dns_cache",
"_background_tasks", "_background_tasks",
"ignored_devices",
) )
def __init__(self) -> None: def __init__(self) -> None:
@ -89,12 +96,30 @@ class ESPHomeDashboard:
self.settings = DashboardSettings() self.settings = DashboardSettings()
self.dns_cache = DNSCache() self.dns_cache = DNSCache()
self._background_tasks: set[asyncio.Task] = set() self._background_tasks: set[asyncio.Task] = set()
self.ignored_devices: set[str] = set()
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Setup the dashboard.""" """Setup the dashboard."""
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
self.ping_request = asyncio.Event() self.ping_request = asyncio.Event()
self.entries = DashboardEntries(self) self.entries = DashboardEntries(self)
self.load_ignored_devices()
def load_ignored_devices(self) -> None:
storage_path = Path(ignored_devices_storage_path())
try:
with storage_path.open("r", encoding="utf-8") as f_handle:
data = json.load(f_handle)
self.ignored_devices = set(data.get("ignored_devices", set()))
except FileNotFoundError:
pass
def save_ignored_devices(self) -> None:
storage_path = Path(ignored_devices_storage_path())
with storage_path.open("w", encoding="utf-8") as f_handle:
json.dump(
{"ignored_devices": sorted(self.ignored_devices)}, indent=2, fp=f_handle
)
async def async_run(self) -> None: async def async_run(self) -> None:
"""Run the dashboard.""" """Run the dashboard."""

View file

@ -541,6 +541,46 @@ class ImportRequestHandler(BaseHandler):
self.finish() self.finish()
class IgnoreDeviceRequestHandler(BaseHandler):
@authenticated
def post(self) -> None:
dashboard = DASHBOARD
try:
args = json.loads(self.request.body.decode())
device_name = args["name"]
ignore = args["ignore"]
except (json.JSONDecodeError, KeyError):
self.set_status(400)
self.set_header("content-type", "application/json")
self.write(json.dumps({"error": "Invalid payload"}))
return
ignored_device = next(
(
res
for res in dashboard.import_result.values()
if res.device_name == device_name
),
None,
)
if ignored_device is None:
self.set_status(404)
self.set_header("content-type", "application/json")
self.write(json.dumps({"error": "Device not found"}))
return
if ignore:
dashboard.ignored_devices.add(ignored_device.device_name)
else:
dashboard.ignored_devices.discard(ignored_device.device_name)
dashboard.save_ignored_devices()
self.set_status(204)
self.finish()
class DownloadListRequestHandler(BaseHandler): class DownloadListRequestHandler(BaseHandler):
@authenticated @authenticated
@bind_config @bind_config
@ -688,6 +728,7 @@ class ListDevicesHandler(BaseHandler):
"project_name": res.project_name, "project_name": res.project_name,
"project_version": res.project_version, "project_version": res.project_version,
"network": res.network, "network": res.network,
"ignored": res.device_name in dashboard.ignored_devices,
} }
for res in dashboard.import_result.values() for res in dashboard.import_result.values()
if res.device_name not in configured if res.device_name not in configured
@ -1156,6 +1197,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
(f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler), (f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler),
(f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler), (f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler),
(f"{rel}version", EsphomeVersionHandler), (f"{rel}version", EsphomeVersionHandler),
(f"{rel}ignore-device", IgnoreDeviceRequestHandler),
], ],
**app_settings, **app_settings,
) )

View file

@ -28,6 +28,10 @@ def esphome_storage_path() -> str:
return os.path.join(CORE.data_dir, "esphome.json") return os.path.join(CORE.data_dir, "esphome.json")
def ignored_devices_storage_path() -> str:
return os.path.join(CORE.data_dir, "ignored-devices.json")
def trash_storage_path() -> str: def trash_storage_path() -> str:
return CORE.relative_config_path("trash") return CORE.relative_config_path("trash")