Добавление заметок к устройствам. Возможность добавить, удалить, изменить заметку. Синхронизируется с нынешней и старой картой

This commit is contained in:
parent 82a91c0e88
commit 090cbcd477
3 changed files with 92 additions and 25 deletions

View file

@ -1,5 +1,6 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from typing import List from typing import List
from pydantic import BaseModel
from models.subnet import SubnetScanResult from models.subnet import SubnetScanResult
from models.wifi import WifiScanResult from models.wifi import WifiScanResult
from services.subnet_service import SubnetService from services.subnet_service import SubnetService
@ -7,6 +8,9 @@ from services.wifi_service import WifiService
router = APIRouter() router = APIRouter()
class NoteRequest(BaseModel):
note: str
@router.get("/scan", response_model=SubnetScanResult) @router.get("/scan", response_model=SubnetScanResult)
def scan_subnet(subnet: str = "192.168.1.0/24", ports: List[int] = None): def scan_subnet(subnet: str = "192.168.1.0/24", ports: List[int] = None):
"""Сканирует локальную подсеть и возвращает данные об устройствах""" """Сканирует локальную подсеть и возвращает данные об устройствах"""
@ -26,3 +30,36 @@ def scan_wifi(timeout: int = 30):
return result return result
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка сканирования Wi-Fi: {str(e)}") 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)}")

View file

@ -6,9 +6,10 @@ class Device(BaseModel):
mac: Optional[str] = None mac: Optional[str] = None
hostname: Optional[str] = None hostname: Optional[str] = None
vendor: Optional[str] = None vendor: Optional[str] = None
open_ports: Optional[List[str]] = None open_ports: List[str] = []
status: str = "online" status: str
last_seen: Optional[str] = None last_seen: Optional[str] = None
note: Optional[str] = None
class SubnetScanResult(BaseModel): class SubnetScanResult(BaseModel):
devices: List[Device] devices: List[Device] = []

View file

@ -22,10 +22,7 @@ from scapy.sendrecv import srp, sr1
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NetworkScanner: class NetworkScanner:
"""Класс для сканирования сети с помощью ARP-запросов."""
def __init__(self, interface: str): def __init__(self, interface: str):
self.interface = interface self.interface = interface
conf.iface = interface conf.iface = interface
@ -41,10 +38,7 @@ class NetworkScanner:
logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}") logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}")
return [] return []
class PortScanner: class PortScanner:
"""Класс для сканирования портов на устройстве."""
def __init__(self, interface: str): def __init__(self, interface: str):
self.interface = interface self.interface = interface
@ -67,7 +61,7 @@ class PortScanner:
return None return None
try: 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: with ThreadPoolExecutor(max_workers=max_workers) as executor:
results = executor.map(check_port, port_config.get_ports()) results = executor.map(check_port, port_config.get_ports())
open_ports = [port for port in results if port is not None] 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)}") logger.warning(f"Ошибка при сканировании портов для IP {ip}: {str(e)}")
return [] return []
class DeviceInfoCollector: class DeviceInfoCollector:
"""Класс для сбора информации об устройстве."""
def __init__(self): def __init__(self):
self.mac_lookup = MacLookup() self.mac_lookup = MacLookup()
@ -98,10 +89,7 @@ class DeviceInfoCollector:
except: except:
return None return None
class DeviceStorage: class DeviceStorage:
"""Класс для хранения и управления картой устройств в JSON."""
def __init__(self, storage_path: str = "storage/devices/devices.json"): def __init__(self, storage_path: str = "storage/devices/devices.json"):
self.storage_file = Path(storage_path) self.storage_file = Path(storage_path)
self.storage_file.parent.mkdir(parents=True, exist_ok=True) self.storage_file.parent.mkdir(parents=True, exist_ok=True)
@ -149,10 +137,7 @@ class DeviceStorage:
except Exception as e: except Exception as e:
logger.error(f"Ошибка при сохранении устройств: {str(e)}") logger.error(f"Ошибка при сохранении устройств: {str(e)}")
class SubnetService: class SubnetService:
"""Сервис для сканирования подсети и управления картой устройств."""
def __init__(self): def __init__(self):
self.interface = self._get_default_interface() self.interface = self._get_default_interface()
self.network_scanner = NetworkScanner(self.interface) self.network_scanner = NetworkScanner(self.interface)
@ -206,7 +191,8 @@ class SubnetService:
vendor=self.device_info.get_vendor(mac), vendor=self.device_info.get_vendor(mac),
open_ports=self.port_scanner.scan(ip) or existing_device.open_ports, open_ports=self.port_scanner.scan(ip) or existing_device.open_ports,
status="online", status="online",
last_seen=now last_seen=now,
note=existing_device.note
) )
vendor = self.device_info.get_vendor(mac) vendor = self.device_info.get_vendor(mac)
hostname = self.device_info.get_hostname(ip) hostname = self.device_info.get_hostname(ip)
@ -218,14 +204,14 @@ class SubnetService:
vendor=vendor, vendor=vendor,
open_ports=open_ports, open_ports=open_ports,
status="online", status="online",
last_seen=now last_seen=now,
note=None
) )
with ThreadPoolExecutor(max_workers=10) as executor: with ThreadPoolExecutor(max_workers=10) as executor:
device_results = executor.map(process_device, discovered_devices) device_results = executor.map(process_device, discovered_devices)
devices.extend([d for d in device_results if d is not None]) 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} active_macs = {d.mac for d in devices if d.mac}
for mac, device in saved_devices.items(): for mac, device in saved_devices.items():
if mac not in active_macs: if mac not in active_macs:
@ -273,10 +259,53 @@ class SubnetService:
vendor=vendor, vendor=vendor,
open_ports=open_ports, open_ports=open_ports,
status="online", status="online",
last_seen=now last_seen=now,
note=None
) )
logger.warning(f"Локальное устройство не найдено в подсети {subnet}") logger.warning(f"Локальное устройство не найдено в подсети {subnet}")
return None return None
except Exception as e: except Exception as e:
logger.warning(f"Не удалось получить локальное устройство: {str(e)}") logger.warning(f"Не удалось получить локальное устройство: {str(e)}")
return None 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