From 090cbcd47717204048b201f4e79719e01a3651ae Mon Sep 17 00:00:00 2001 From: "s.galanskiy" Date: Tue, 10 Jun 2025 16:19:09 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=20=D0=BA=20=D1=83=D1=81=D1=82=D1=80=D0=BE=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B0=D0=BC.=20=D0=92=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C,=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D1=82?= =?UTF-8?q?=D1=8C,=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D1=83.=20=D0=A1=D0=B8?= =?UTF-8?q?=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D1=83?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D1=81=20=D0=BD=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=88=D0=BD=D0=B5=D0=B9=20=D0=B8=20=D1=81=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SRV/api/v1/subnet.py | 39 ++++++++++++++++++- SRV/models/subnet.py | 7 ++-- SRV/services/subnet_service.py | 71 ++++++++++++++++++++++++---------- 3 files changed, 92 insertions(+), 25 deletions(-) diff --git a/SRV/api/v1/subnet.py b/SRV/api/v1/subnet.py index 8f047b0..8884256 100644 --- a/SRV/api/v1/subnet.py +++ b/SRV/api/v1/subnet.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, HTTPException from typing import List +from pydantic import BaseModel from models.subnet import SubnetScanResult from models.wifi import WifiScanResult from services.subnet_service import SubnetService @@ -7,6 +8,9 @@ from services.wifi_service import WifiService router = APIRouter() +class NoteRequest(BaseModel): + note: str + @router.get("/scan", response_model=SubnetScanResult) def scan_subnet(subnet: str = "192.168.1.0/24", ports: List[int] = None): """Сканирует локальную подсеть и возвращает данные об устройствах""" @@ -25,4 +29,37 @@ def scan_wifi(timeout: int = 30): result = wifi_service.scan_wifi(timeout=timeout) return result except Exception as e: - raise HTTPException(status_code=500, detail=f"Ошибка сканирования Wi-Fi: {str(e)}") \ No newline at end of file + raise HTTPException(status_code=500, detail=f"Ошибка сканирования Wi-Fi: {str(e)}") + +@router.post("/devices/{mac}/note") +def add_note(mac: str, request: NoteRequest): + """Добавляет заметку для устройства по MAC-адресу""" + try: + subnet_service = SubnetService() + if not subnet_service.add_note(mac, request.note): + raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + return {"message": f"Заметка '{request.note}' добавлена для устройства {mac}"} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Ошибка добавления заметки: {str(e)}") + +@router.put("/devices/{mac}/note") +def update_note(mac: str, request: NoteRequest): + """Обновляет заметку для устройства по MAC-адресу""" + try: + subnet_service = SubnetService() + if not subnet_service.update_note(mac, request.note): + raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + return {"message": f"Заметка '{request.note}' обновлена для устройства {mac}"} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Ошибка обновления заметки: {str(e)}") + +@router.delete("/devices/{mac}/note") +def delete_note(mac: str): + """Удаляет заметку для устройства по MAC-адресу""" + try: + subnet_service = SubnetService() + if not subnet_service.delete_note(mac): + raise HTTPException(status_code=404, detail=f"Устройство с MAC {mac} не найдено") + return {"message": f"Заметка удалена для устройства {mac}"} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Ошибка удаления заметки: {str(e)}") \ No newline at end of file diff --git a/SRV/models/subnet.py b/SRV/models/subnet.py index b038291..8ea9fa3 100644 --- a/SRV/models/subnet.py +++ b/SRV/models/subnet.py @@ -6,9 +6,10 @@ class Device(BaseModel): mac: Optional[str] = None hostname: Optional[str] = None vendor: Optional[str] = None - open_ports: Optional[List[str]] = None - status: str = "online" + open_ports: List[str] = [] + status: str last_seen: Optional[str] = None + note: Optional[str] = None class SubnetScanResult(BaseModel): - devices: List[Device] \ No newline at end of file + devices: List[Device] = [] \ No newline at end of file diff --git a/SRV/services/subnet_service.py b/SRV/services/subnet_service.py index 9c21315..3ae2fc8 100644 --- a/SRV/services/subnet_service.py +++ b/SRV/services/subnet_service.py @@ -22,10 +22,7 @@ from scapy.sendrecv import srp, sr1 logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) - class NetworkScanner: - """Класс для сканирования сети с помощью ARP-запросов.""" - def __init__(self, interface: str): self.interface = interface conf.iface = interface @@ -41,10 +38,7 @@ class NetworkScanner: logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}") return [] - class PortScanner: - """Класс для сканирования портов на устройстве.""" - def __init__(self, interface: str): self.interface = interface @@ -67,7 +61,7 @@ class PortScanner: return None try: - max_workers = min(len(port_config.get_ports()), 15) # Снижено с 100 + max_workers = min(len(port_config.get_ports()), 15) with ThreadPoolExecutor(max_workers=max_workers) as executor: results = executor.map(check_port, port_config.get_ports()) open_ports = [port for port in results if port is not None] @@ -77,10 +71,7 @@ class PortScanner: logger.warning(f"Ошибка при сканировании портов для IP {ip}: {str(e)}") return [] - class DeviceInfoCollector: - """Класс для сбора информации об устройстве.""" - def __init__(self): self.mac_lookup = MacLookup() @@ -98,10 +89,7 @@ class DeviceInfoCollector: except: return None - class DeviceStorage: - """Класс для хранения и управления картой устройств в JSON.""" - def __init__(self, storage_path: str = "storage/devices/devices.json"): self.storage_file = Path(storage_path) self.storage_file.parent.mkdir(parents=True, exist_ok=True) @@ -149,10 +137,7 @@ class DeviceStorage: except Exception as e: logger.error(f"Ошибка при сохранении устройств: {str(e)}") - class SubnetService: - """Сервис для сканирования подсети и управления картой устройств.""" - def __init__(self): self.interface = self._get_default_interface() self.network_scanner = NetworkScanner(self.interface) @@ -206,7 +191,8 @@ class SubnetService: vendor=self.device_info.get_vendor(mac), open_ports=self.port_scanner.scan(ip) or existing_device.open_ports, status="online", - last_seen=now + last_seen=now, + note=existing_device.note ) vendor = self.device_info.get_vendor(mac) hostname = self.device_info.get_hostname(ip) @@ -218,14 +204,14 @@ class SubnetService: vendor=vendor, open_ports=open_ports, status="online", - last_seen=now + last_seen=now, + note=None ) with ThreadPoolExecutor(max_workers=10) as executor: device_results = executor.map(process_device, discovered_devices) devices.extend([d for d in device_results if d is not None]) - # Добавляет устройства со старой карты, в нынешнюю при отсутствии со статусом offline active_macs = {d.mac for d in devices if d.mac} for mac, device in saved_devices.items(): if mac not in active_macs: @@ -273,10 +259,53 @@ class SubnetService: vendor=vendor, open_ports=open_ports, status="online", - last_seen=now + last_seen=now, + note=None ) logger.warning(f"Локальное устройство не найдено в подсети {subnet}") return None except Exception as e: logger.warning(f"Не удалось получить локальное устройство: {str(e)}") - return None \ No newline at end of file + return None + + def add_note(self, mac: str, note: str) -> bool: + try: + saved_devices = self.storage.load_devices() + if mac not in saved_devices: + logger.warning(f"Устройство с MAC {mac} не найдено") + return False + saved_devices[mac].note = note + self.storage.save_devices(list(saved_devices.values())) + logger.info(f"Добавлена заметка '{note}' для устройства с MAC {mac}") + return True + except Exception as e: + logger.error(f"Ошибка при добавлении заметки для MAC {mac}: {str(e)}") + return False + + def update_note(self, mac: str, note: str) -> bool: + try: + saved_devices = self.storage.load_devices() + if mac not in saved_devices: + logger.warning(f"Устройство с MAC {mac} не найдено") + return False + saved_devices[mac].note = note + self.storage.save_devices(list(saved_devices.values())) + logger.info(f"Обновлена заметка '{note}' для устройства с MAC {mac}") + return True + except Exception as e: + logger.error(f"Ошибка при обновлении заметки для MAC {mac}: {str(e)}") + return False + + def delete_note(self, mac: str) -> bool: + try: + saved_devices = self.storage.load_devices() + if mac not in saved_devices: + logger.warning(f"Устройство с MAC {mac} не найдено") + return False + saved_devices[mac].note = None + self.storage.save_devices(list(saved_devices.values())) + logger.info(f"Удалена заметка для устройства с MAC {mac}") + return True + except Exception as e: + logger.error(f"Ошибка при удалении заметки для MAC {mac}: {str(e)}") + return False \ No newline at end of file