From b1306ff66f1389de630f4ebc4e2bd73600d6806c Mon Sep 17 00:00:00 2001 From: "s.galanskiy" Date: Wed, 16 Jul 2025 13:27:49 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BA=D0=B0=D0=BD=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B5=D1=82?= =?UTF-8?q?=D0=B8.=20=D0=A5=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=B6=D0=B4=D0=BE=D0=B9=20=D0=B8=D0=B7=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=BA=D0=B0=D0=BD=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=D0=B4=D1=81=D0=B5?= =?UTF-8?q?=D1=82=D0=B5=D0=B9.=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=B0=D0=B3=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=81=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SRV/api/v1/settings.py | 30 +- SRV/api/v1/subnet.py | 95 ++--- SRV/locales/en/LC_MESSAGES/messages.mo | Bin 10957 -> 11406 bytes SRV/locales/en/LC_MESSAGES/messages.po | 188 +++++----- SRV/locales/messages.pot | 493 +++++++++++++++++++++++++ SRV/locales/ru/LC_MESSAGES/messages.mo | Bin 13491 -> 14115 bytes SRV/locales/ru/LC_MESSAGES/messages.po | 166 +++++---- SRV/models/subnet.py | 5 +- SRV/services/settings_service.py | 46 ++- SRV/services/subnet_service.py | 386 ++++++++++++++++--- 10 files changed, 1124 insertions(+), 285 deletions(-) create mode 100644 SRV/locales/messages.pot diff --git a/SRV/api/v1/settings.py b/SRV/api/v1/settings.py index bce8ed9..94cbdb6 100644 --- a/SRV/api/v1/settings.py +++ b/SRV/api/v1/settings.py @@ -1,5 +1,6 @@ import os import logging +from typing import Optional from fastapi import APIRouter, HTTPException, Depends, Response, Request from starlette.responses import FileResponse from services.settings_service import SettingsService @@ -13,12 +14,14 @@ router = APIRouter() settings_service = SettingsService() @router.post("/clear_subnet") -def clear_subnet(translations: Translations = Depends(get_translations())): +def clear_subnet(subnet: Optional[str] = None, translations: Translations = Depends(get_translations())): """Очищает данные узлов подсети, сохранённые в devices.json. Удаляет все записи об узлах подсети, оставляя файл пустым. Args: + subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24"). + Если None, очищает все подсети. translations: Объект переводов для текущего языка. Returns: @@ -28,9 +31,12 @@ def clear_subnet(translations: Translations = Depends(get_translations())): HTTPException: Если произошла ошибка очистки (status_code=500). """ try: - success = settings_service.clear_subnet() + success = settings_service.clear_subnet(subnet) if success: - return {"message": translations.gettext("Данные узлов подсети успешно очищены")} + if subnet: + return {"message": translations.gettext("Данные узлов подсети {subnet} успешно очищены").format(subnet=subnet)} + else: + return {"message": translations.gettext("Данные всех подсетей успешно очищены")} else: raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные узлов подсети")) except Exception as e: @@ -91,26 +97,28 @@ DEVICE_PATH = "storage/devices/devices.json" WIFI_PATH = "storage/wifi/wifi.json" @router.get("/export_devices", response_class=FileResponse) -async def export_devices(translations: Translations = Depends(get_translations())): +async def export_devices(subnet: str, translations: Translations = Depends(get_translations())): """ - Экспортирует файл с данными устройств в формате JSON. + Экспортирует файл с данными устройств в формате JSON для указанной подсети. Args: + subnet (str): Подсеть в формате CIDR (например, "10.1.1.0/24"). translations: Объект переводов для текущего языка. Returns: - FileResponse: Файл devices.json для скачивания. + FileResponse: Файл .json для скачивания. Raises: HTTPException: Если файл не найден (status_code=404). """ - if not os.path.exists(DEVICE_PATH): - logger.warning(f"Export file not found: {DEVICE_PATH}") + file_path = f"storage/subnets/{subnet.replace('/', '_')}.json" + if not os.path.exists(file_path): + logger.warning(f"Export file not found: {file_path}") raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден")) - logger.info(f"Exporting file: {DEVICE_PATH}") + logger.info(f"Exporting file: {file_path}") return FileResponse( - path=DEVICE_PATH, - filename="devices.json", + path=file_path, + filename=f"{subnet.replace('/', '_')}.json", media_type="application/json" ) diff --git a/SRV/api/v1/subnet.py b/SRV/api/v1/subnet.py index 309c99d..b439a7c 100644 --- a/SRV/api/v1/subnet.py +++ b/SRV/api/v1/subnet.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, HTTPException, Body, Depends from models.subnet import SubnetScanResult, SubnetScanRequest from models.wifi import WifiScanResult from models.request import NoteRequest, WifiConnectRequest, WifiDisconnectRequest -from services.subnet_service import SubnetService +from services.subnet_service import SubnetService, get_subnet_service from services.wifi_service import WifiService from services.connect_service import WifiConnectionService from utils.i18n import get_translations, _ @@ -14,15 +14,15 @@ router = APIRouter() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - @router.post("/devices", response_model=SubnetScanResult) -def scan_subnet(request: SubnetScanRequest = Body(...), translations: Translations = Depends(get_translations())): +def scan_subnet(request: SubnetScanRequest = Body(...), subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())): """Сканирует локальную подсеть и возвращает данные об устройствах. Выполняет сканирование указанной подсети для обнаружения активных устройств. Args: request (SubnetScanRequest): Объект с полем subnet. + subnet_service: Сервис сканирования подсети. translations: Объект переводов для текущего языка. Returns: @@ -32,17 +32,16 @@ def scan_subnet(request: SubnetScanRequest = Body(...), translations: Translatio HTTPException: Если произошла ошибка сканирования (status_code=500). """ try: - subnet_service = SubnetService() # Если subnet пустая строка, определяем автоматически subnet = request.subnet.strip() if request.subnet else None logger.info(f"Requested subnet scan for: {subnet or 'auto-detected'}") result = subnet_service.scan_subnet(subnet) - if not result.devices: + if not result.devices and result.error: logger.warning(f"No devices found in subnet {subnet or 'auto-detected'}") return result except Exception as e: logger.error(f"Error in scan_subnet: {str(e)}") - raise HTTPException(status_code=500, detail=translations.gettext("Ошибка сканирования: {error}").format(error=str(e))) + raise HTTPException(status_code=500, detail=translations.gettext("Ошибка сканирования подсети: {error}").format(error=str(e))) @router.get("/wifi", response_model=WifiScanResult) def scan_wifi(timeout: int = 30, translations: Translations = Depends(get_translations())): @@ -68,7 +67,7 @@ def scan_wifi(timeout: int = 30, translations: Translations = Depends(get_transl raise HTTPException(status_code=500, detail=translations.gettext("Ошибка сканирования Wi-Fi: {error}").format(error=str(e))) @router.post("/devices/{mac}/note") -def add_note(mac: str, request: NoteRequest, translations: Translations = Depends(get_translations())): +def add_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())): """Добавляет заметку для устройства по MAC-адресу. Привязывает заметку к устройству, идентифицируемому по MAC-адресу. @@ -76,6 +75,7 @@ def add_note(mac: str, request: NoteRequest, translations: Translations = Depend Args: mac (str): MAC-адрес устройства. request (NoteRequest): Объект с полем "note" для текста заметки. + subnet_service: Сервис сканирования подсети. translations: Объект переводов для текущего языка. Returns: @@ -86,13 +86,14 @@ def add_note(mac: str, request: NoteRequest, translations: Translations = Depend существует (status_code=400), или произошла внутренняя ошибка (status_code=500). """ try: - subnet_service = SubnetService() - success, error_message = subnet_service.add_note(mac, request.note) + success, result = subnet_service.add_note(mac, request.note, translations) if not success: - if "не найдено" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "уже существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac)) + elif result[0] == "note_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} уже существует").format(mac=mac)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка '{note}' добавлена для устройства {mac}").format(note=request.note, mac=mac)} except HTTPException as e: raise e @@ -100,7 +101,7 @@ def add_note(mac: str, request: NoteRequest, translations: Translations = Depend raise HTTPException(status_code=500, detail=translations.gettext("Ошибка добавления заметки: {error}").format(error=str(e))) @router.put("/devices/{mac}/note") -def update_note(mac: str, request: NoteRequest, translations: Translations = Depends(get_translations())): +def update_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())): """Обновляет заметку для устройства по MAC-адресу. Изменяет существующую заметку, привязанную к устройству. @@ -108,6 +109,7 @@ def update_note(mac: str, request: NoteRequest, translations: Translations = Dep Args: mac (str): MAC-адрес устройства. request (NoteRequest): Объект с полем "note" для нового текста заметки. + subnet_service: Сервис сканирования подсети. translations: Объект переводов для текущего языка. Returns: @@ -118,13 +120,14 @@ def update_note(mac: str, request: NoteRequest, translations: Translations = Dep существует (status_code=400), или произошла внутренняя ошибка (status_code=500). """ try: - subnet_service = SubnetService() - success, error_message = subnet_service.update_note(mac, request.note) + success, result = subnet_service.update_note(mac, request.note, translations) if not success: - if "не найдено" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "не существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac)) + elif result[0] == "note_not_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка '{note}' обновлена для устройства {mac}").format(note=request.note, mac=mac)} except HTTPException as e: raise e @@ -132,13 +135,14 @@ def update_note(mac: str, request: NoteRequest, translations: Translations = Dep raise HTTPException(status_code=500, detail=translations.gettext("Ошибка обновления заметки: {error}").format(error=str(e))) @router.delete("/devices/{mac}/note") -def delete_note(mac: str, translations: Translations = Depends(get_translations())): +def delete_note(mac: str, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())): """Удаляет заметку для устройства по MAC-адресу. Удаляет привязанную заметку для указанного устройства. Args: mac (str): MAC-адрес устройства. + subnet_service: Сервис сканирования подсети. translations: Объект переводов для текущего языка. Returns: @@ -149,13 +153,14 @@ def delete_note(mac: str, translations: Translations = Depends(get_translations( существует (status_code=400), или произошла внутренняя ошибка (status_code=500). """ try: - subnet_service = SubnetService() - success, error_message = subnet_service.delete_note(mac) + success, result = subnet_service.delete_note(mac, translations) if not success: - if "не найдено" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "не существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac)) + elif result[0] == "note_not_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка удалена для устройства {mac}").format(mac=mac)} except HTTPException as e: raise e @@ -182,12 +187,14 @@ def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = """ try: wifi_service = WifiService() - success, error_message = wifi_service.add_note(ssid, request.note) + success, result = wifi_service.add_note(ssid, request.note) if not success: - if "не найдена" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "уже существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid)) + elif result[0] == "note_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} уже существует").format(ssid=ssid)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка '{note}' добавлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)} except HTTPException as e: raise e @@ -214,12 +221,14 @@ def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations """ try: wifi_service = WifiService() - success, error_message = wifi_service.update_note(ssid, request.note) + success, result = wifi_service.update_note(ssid, request.note) if not success: - if "не найдена" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "не существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid)) + elif result[0] == "note_not_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка '{note}' обновлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)} except HTTPException as e: raise e @@ -245,12 +254,14 @@ def delete_wifi_note(ssid: str, translations: Translations = Depends(get_transla """ try: wifi_service = WifiService() - success, error_message = wifi_service.delete_note(ssid) + success, result = wifi_service.delete_note(ssid) if not success: - if "не найдена" in error_message.lower(): - raise HTTPException(status_code=404, detail=translations.gettext(error_message)) - elif "не существует" in error_message.lower(): - raise HTTPException(status_code=400, detail=translations.gettext(error_message)) + if result[0] == "not_found": + raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid)) + elif result[0] == "note_not_exists": + raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid)) + else: + raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"])) return {"message": translations.gettext("Заметка удалена для сети с SSID {ssid}").format(ssid=ssid)} except HTTPException as e: raise e diff --git a/SRV/locales/en/LC_MESSAGES/messages.mo b/SRV/locales/en/LC_MESSAGES/messages.mo index c6051e9e671432dd71595fabe55e9d08a379d810..dcae5aa0bc647cfc08cfe189a17b0cdd1c3078bb 100644 GIT binary patch delta 2220 zcmZwHe@v8h9KiAKAsh%&{7?uG?m-0@MBEEQ&@)MC{wPR#!PQwGX1wq zZ(|MdpUAJWr7AU3sgNqCv5}5iY{k8}0xx1STG(|Ru0a`h2;1;GT!tE}v|u}K!eeIq zhZ!#?`wjFDqKx|*H{+ZPrM9rYdXB~_Iwr9R?_(u4%%U3TL^pncE3qOok)RXj5s%{I zavhhV^`XRuE3l1t0Ne43*B7q)kWq&nAVzIcIuEpPwDXJARO~J6$9uR8gJdT+@Ci2Kk0|RF=2QPQG)R%g~{|^FujDVr7okqMz@g%UERfM{2TdIi!`N5um$Bc?Ls-a5tMOZ zlnR7?rcp{Gm#4hy!)oNkQEg~rf3=GSH>vicJl!8*9e$77aVD$D#%|M>Q0{O9rPO06 z8(l(9N8K^6XOUG7aW(Qns3w%ex1p>T#E{&<5RH|10_Dzcm|A&<_@||+P!_VI++iE8 z!(NoiMNks>1~t5ka%8!jA5B$-aui!p=I=&%cHXU`F!In{q(e5kkE<}1gO&x?p;YGi zYW-R3FD;reZk#ke=jXg}Rx_qxJUU=RqWg^rjoyex&nX%wqy5oA<1-^7L$&B&bRfDv z+Ap)DWt`USsjn0mr{i-EKDbcCIIF*%dL(Ucpl_Gg72FrDNIRNlA7}1VVjW|`IL8Ku zAD9+>H6F4R7tb4$(L?b>C!!2Zooo&)cbayjrl!6VMX5=vPbDr$5%+Jsobg73l@G)wA3_zc!t& zUTe$KyKOlZUpQpDYOw}-T~3c%A1*Bp2TFZOc`e@Ej&4sU`?wu_-9dAy@QwM67X4mH z_S4R8m&32Q{C=O`uI-JxPjz?}ulHyUCy52LUZ2Ms)c$wemUziDudmbf-vRna*+7=l z=l8puL9N#j2)y9)cj}wVi*)_Ja+wwY delta 1932 zcmZ|PYiyHM7{KwT-4$=J$;K{N-fglmwh7xDgsslSts`8P2uK!8*;3kf>D@ z)JrlTQ)coiAu}_C55Ob}p$T3RT_XCy%f!HpF&c?CjDFEY2nLPu|JajgOl;cz&U1R+ zb9v4=J-lOfd-|*5+!usbgwIkw{v3_|c?I)DZXw)>g*c2}+>6UGh5E>(@g?JN%%%Q> z@ig8;d=`1j6}}dTq~$7wja2-C>#(Chqzgw;>(65kdKmp)?8ObZ4>#j`CN8Eojf1!z zr%~I@;}+~-72EJx^yAlfKjVwqs2M$o+V8uilelM3y?w?!rnLS3`vT1C>zH$797JSe8czyYGz9+L>|OI1^L&3j!>ZuKf->z ziG#S6yi=5OsPQbW!6vSX8o>#V$cxB1l?IKtOrjW}IdyFG! z5sxAlLXIE@Sf;QMPa@zQbBvVr73%cbY;R zH3P?tA7ef7A9xqm(kt63J^1%tqo#5K=~HHq@#Ve}sZ|G>*FBQ^?qI33Z_BMj!ds6|6_9B#9h+If^>5SCFw}7ImxUu@!yf zJBV9R2R?(EfeTp4`0@n>P4Nxn87UzRWKTL!SH2Uuq4F4VfaMv~j!vLX{7vk}&rl~; z$*k2b4_%aUZUj0l#2hL~t7B-^Jun+Z`*{Cag4z+``sAuIe>h1B+ zNC#Sre(Xi1#V&H)1tTXGoO?l$SY{Gn%mk}H@gGA zDpPLBjQRRKUMJ~(xvbCKU\n" "Language: en\n" "Language-Team: en \n" @@ -48,11 +48,11 @@ msgstr "The users.json file exists and contains users" #: api/v1/auth.py:179 msgid "Файл users.json существует, но не содержит пользователей" -msgstr "The users.json file exists but does not contain users" +msgstr "The users.json file exists but contains no users" #: api/v1/auth.py:183 msgid "Ошибка при проверке файла" -msgstr "Error checking the file" +msgstr "Error checking file" #: api/v1/ip.py:36 api/v1/ip.py:73 api/v1/ip.py:110 #, python-brace-format @@ -96,145 +96,190 @@ msgstr "Failed to update container position: {error}" msgid "Не удалось получить имя хоста" msgstr "Failed to retrieve hostname" -#: api/v1/settings.py:33 -msgid "Данные узлов подсети успешно очищены" -msgstr "Subnet node data successfully cleared" +#: api/v1/settings.py:37 +#, python-brace-format +msgid "Данные узлов подсети {subnet} успешно очищены" +msgstr "Subnet node data for {subnet} successfully cleared" -#: api/v1/settings.py:35 +#: api/v1/settings.py:39 +msgid "Данные всех подсетей успешно очищены" +msgstr "All subnet data successfully cleared" + +#: api/v1/settings.py:41 msgid "Не удалось очистить данные узлов подсети" msgstr "Failed to clear subnet node data" -#: api/v1/settings.py:38 api/v1/settings.py:63 api/v1/settings.py:88 +#: api/v1/settings.py:44 api/v1/settings.py:69 api/v1/settings.py:94 #, python-brace-format msgid "Ошибка очистки: {error}" msgstr "Clear error: {error}" -#: api/v1/settings.py:58 +#: api/v1/settings.py:64 msgid "Данные точек доступа Wi-Fi успешно очищены" msgstr "Wi-Fi access point data successfully cleared" -#: api/v1/settings.py:60 +#: api/v1/settings.py:66 msgid "Не удалось очистить данные точек доступа Wi-Fi" msgstr "Failed to clear Wi-Fi access point data" -#: api/v1/settings.py:83 +#: api/v1/settings.py:89 msgid "Все сетевые данные успешно очищены" msgstr "All network data successfully cleared" -#: api/v1/settings.py:85 +#: api/v1/settings.py:91 msgid "Не удалось очистить все сетевые данные" msgstr "Failed to clear all network data" -#: api/v1/settings.py:109 api/v1/settings.py:133 +#: api/v1/settings.py:117 api/v1/settings.py:141 msgid "Файл не найден" msgstr "File not found" -#: api/v1/settings.py:158 +#: api/v1/settings.py:166 #, python-brace-format msgid "Язык '{locale}' не поддерживается." msgstr "Language '{locale}' is not supported." -#: api/v1/settings.py:164 +#: api/v1/settings.py:172 #, python-brace-format msgid "Язык '{locale}' не поддерживается (нет файлов переводов)." msgstr "Language '{locale}' is not supported (no translation files)." -#: api/v1/settings.py:167 +#: api/v1/settings.py:175 #, python-brace-format msgid "Язык успешно установлен: {locale}" msgstr "Language successfully set: {locale}" -#: api/v1/subnet.py:45 +#: api/v1/subnet.py:44 #, python-brace-format -msgid "Ошибка сканирования: {error}" -msgstr "Scan error: {error}" +msgid "Ошибка сканирования подсети: {error}" +msgstr "Subnet scan error: {error}" -#: api/v1/subnet.py:68 +#: api/v1/subnet.py:67 #, python-brace-format msgid "Ошибка сканирования Wi-Fi: {error}" msgstr "Wi-Fi scan error: {error}" -#: api/v1/subnet.py:96 +#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159 +#, python-brace-format +msgid "Устройство с MAC {mac} не найдено" +msgstr "Device with MAC {mac} not found" + +#: api/v1/subnet.py:94 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} уже существует" +msgstr "Note for device with MAC {mac} already exists" + +#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163 \ +#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264 \ +#: services/wifi_service.py:319 services/wifi_service.py:337 \ +#: services/wifi_service.py:355 +#, python-brace-format +msgid "Внутренняя ошибка сервера: {error}" +msgstr "Internal server error: {error}" + +#: api/v1/subnet.py:97 #, python-brace-format msgid "Заметка '{note}' добавлена для устройства {mac}" msgstr "Note '{note}' added for device {mac}" -#: api/v1/subnet.py:100 api/v1/subnet.py:195 +#: api/v1/subnet.py:101 api/v1/subnet.py:202 #, python-brace-format msgid "Ошибка добавления заметки: {error}" msgstr "Error adding note: {error}" -#: api/v1/subnet.py:128 +#: api/v1/subnet.py:128 api/v1/subnet.py:161 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} не существует" +msgstr "Note for device with MAC {mac} does not exist" + +#: api/v1/subnet.py:131 #, python-brace-format msgid "Заметка '{note}' обновлена для устройства {mac}" msgstr "Note '{note}' updated for device {mac}" -#: api/v1/subnet.py:132 api/v1/subnet.py:227 +#: api/v1/subnet.py:135 api/v1/subnet.py:236 #, python-brace-format msgid "Ошибка обновления заметки: {error}" msgstr "Error updating note: {error}" -#: api/v1/subnet.py:159 +#: api/v1/subnet.py:164 #, python-brace-format msgid "Заметка удалена для устройства {mac}" msgstr "Note deleted for device {mac}" -#: api/v1/subnet.py:163 api/v1/subnet.py:258 +#: api/v1/subnet.py:168 api/v1/subnet.py:269 #, python-brace-format msgid "Ошибка удаления заметки: {error}" msgstr "Error deleting note: {error}" -#: api/v1/subnet.py:191 +#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260 \ +#: services/wifi_service.py:327 services/wifi_service.py:345 +#, python-brace-format +msgid "Сеть с SSID {ssid} не найдена" +msgstr "Network with SSID {ssid} not found" + +#: api/v1/subnet.py:195 services/wifi_service.py:312 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} уже существует" +msgstr "Note for network with SSID {ssid} already exists" + +#: api/v1/subnet.py:198 #, python-brace-format msgid "Заметка '{note}' добавлена для сети с SSID {ssid}" msgstr "Note '{note}' added for network with SSID {ssid}" -#: api/v1/subnet.py:223 +#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330 \ +#: services/wifi_service.py:348 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} не существует" +msgstr "Note for network with SSID {ssid} does not exist" + +#: api/v1/subnet.py:232 #, python-brace-format msgid "Заметка '{note}' обновлена для сети с SSID {ssid}" msgstr "Note '{note}' updated for network with SSID {ssid}" -#: api/v1/subnet.py:254 +#: api/v1/subnet.py:265 #, python-brace-format msgid "Заметка удалена для сети с SSID {ssid}" msgstr "Note deleted for network with SSID {ssid}" -#: api/v1/subnet.py:282 +#: api/v1/subnet.py:293 #, python-brace-format msgid "Успешно подключено к {ssid}" msgstr "Successfully connected to {ssid}" -#: api/v1/subnet.py:286 +#: api/v1/subnet.py:297 #, python-brace-format msgid "Ошибка подключения к Wi-Fi: {error}" msgstr "Wi-Fi connection error: {error}" -#: api/v1/subnet.py:309 +#: api/v1/subnet.py:320 #, python-brace-format msgid "Успешно отключено от {ssid}" msgstr "Successfully disconnected from {ssid}" -#: api/v1/subnet.py:313 +#: api/v1/subnet.py:324 #, python-brace-format msgid "Ошибка отключения от Wi-Fi: {error}" msgstr "Wi-Fi disconnection error: {error}" -#: api/v1/subnet.py:337 +#: api/v1/subnet.py:348 msgid "connected" msgstr "connected" -#: api/v1/subnet.py:341 +#: api/v1/subnet.py:352 #, python-brace-format msgid "Ошибка проверки статуса Wi-Fi: {error}" msgstr "Wi-Fi status check error: {error}" #: api/v1/websocket.py:145 msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети" -msgstr "Error: The 'activation' key is missing in the received network data" +msgstr "Error: 'activation' key is missing in received network data" #: services/arp_service.py:102 msgid "Доступные устройства в сети:" -msgstr "Available devices in the network:" +msgstr "Available devices on the network:" #: services/arp_service.py:103 msgid "IP" @@ -276,10 +321,10 @@ msgstr "Successfully logged out" msgid "Ошибка при выходе из системы" msgstr "Error during logout" -#: services/connect_service.py:70 services/connect_service.py:117 +#: services/connect_service.py:70 services/connect_service.py:117 \ #: services/connect_service.py:154 services/wifi_service.py:227 msgid "Wi-Fi-интерфейс не доступен" -msgstr "Wi-Fi interface is not available" +msgstr "Wi-Fi interface not available" #: services/connect_service.py:74 #, python-brace-format @@ -289,7 +334,7 @@ msgstr "Network with SSID {ssid} not found in the list of available networks" #: services/connect_service.py:81 services/connect_service.py:92 #, python-brace-format msgid "Ошибка подключения к {ssid}: Неверный пароль" -msgstr "Connection error to {ssid}: Invalid password" +msgstr "Connection error to {ssid}: Incorrect password" #: services/connect_service.py:83 services/connect_service.py:94 #, python-brace-format @@ -361,52 +406,42 @@ msgstr "Failed to execute hostname command: {error}" msgid "Ошибка обработки вывода hostname: {error}" msgstr "Error processing hostname output: {error}" -#: services/settings_service.py:37 +#: services/settings_service.py:55 #, python-brace-format msgid "Ошибка очистки узлов подсети: {error}" -msgstr "Error clearing subnet nodes: {error}" +msgstr "Subnet node clear error: {error}" -#: services/settings_service.py:58 +#: services/settings_service.py:76 #, python-brace-format msgid "Ошибка очистки точек доступа Wi-Fi: {error}" -msgstr "Error clearing Wi-Fi access points: {error}" +msgstr "Wi-Fi access point clear error: {error}" -#: services/settings_service.py:84 +#: services/settings_service.py:104 #, python-brace-format msgid "Ошибка очистки всех сетевых данных: {error}" -msgstr "Error clearing all network data: {error}" +msgstr "All network data clear error: {error}" #: services/subnet_service.py:75 services/wifi_service.py:115 msgid "Неизвестный" msgstr "Unknown" -#: services/subnet_service.py:179 +#: services/subnet_service.py:185 #, python-brace-format msgid "Ошибка при сохранении устройств: {error}" msgstr "Error saving devices: {error}" -#: services/subnet_service.py:374 services/subnet_service.py:391 -#: services/subnet_service.py:408 -#, python-brace-format -msgid "Устройство с MAC {mac} не найдено" -msgstr "Device with MAC {mac} not found" +#: services/subnet_service.py:268 +msgid "Недопустимая подсеть по умолчанию" +msgstr "Invalid default subnet" -#: services/subnet_service.py:377 -#, python-brace-format -msgid "Заметка для устройства с MAC {mac} уже существует" -msgstr "Note for device with MAC {mac} already exists" +#: services/subnet_service.py:288 +msgid "В данной подсети не обнаружено устройств" +msgstr "No devices found in this subnet" -#: services/subnet_service.py:384 services/subnet_service.py:401 -#: services/subnet_service.py:418 services/wifi_service.py:319 -#: services/wifi_service.py:337 services/wifi_service.py:355 +#: services/subnet_service.py:345 #, python-brace-format -msgid "Внутренняя ошибка сервера: {error}" -msgstr "Internal server error: {error}" - -#: services/subnet_service.py:394 services/subnet_service.py:411 -#, python-brace-format -msgid "Заметка для устройства с MAC {mac} не существует" -msgstr "Note for device with MAC {mac} does not exist" +msgid "Ошибка сканирования: {error}" +msgstr "Scan error: {error}" #: services/wifi_service.py:61 #, python-brace-format @@ -451,23 +486,8 @@ msgstr "5 GHz" #: services/wifi_service.py:165 msgid "Интерфейс не определён" -msgstr "Interface not defined" +msgstr "Interface not specified" #: services/wifi_service.py:215 msgid "Таймаут при выполнении iw scan" -msgstr "Timeout during iw scan" - -#: services/wifi_service.py:312 -#, python-brace-format -msgid "Заметка для сети с SSID {ssid} уже существует" -msgstr "Note for network with SSID {ssid} already exists" - -#: services/wifi_service.py:327 services/wifi_service.py:345 -#, python-brace-format -msgid "Сеть с SSID {ssid} не найдена" -msgstr "Network with SSID {ssid} not found" - -#: services/wifi_service.py:330 services/wifi_service.py:348 -#, python-brace-format -msgid "Заметка для сети с SSID {ssid} не существует" -msgstr "Note for network with SSID {ssid} does not exist" \ No newline at end of file +msgstr "Timeout during iw scan" \ No newline at end of file diff --git a/SRV/locales/messages.pot b/SRV/locales/messages.pot new file mode 100644 index 0000000..d24418d --- /dev/null +++ b/SRV/locales/messages.pot @@ -0,0 +1,493 @@ +# Translations template for PROJECT. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-07-16 13:15+0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: api/v1/auth.py:44 +msgid "Не удалось зарегистрировать пользователя" +msgstr "" + +#: api/v1/auth.py:75 +msgid "Не удалось авторизовать пользователя" +msgstr "" + +#: api/v1/auth.py:103 +msgid "bearer" +msgstr "" + +#: api/v1/auth.py:109 +msgid "Не удалось получить данные пользователя" +msgstr "" + +#: api/v1/auth.py:136 +msgid "Не удалось выйти из системы" +msgstr "" + +#: api/v1/auth.py:162 +msgid "Файл users.json не существует" +msgstr "" + +#: api/v1/auth.py:172 +msgid "Файл users.json существует и содержит пользователей" +msgstr "" + +#: api/v1/auth.py:179 +msgid "Файл users.json существует, но не содержит пользователей" +msgstr "" + +#: api/v1/auth.py:183 +msgid "Ошибка при проверке файла" +msgstr "" + +#: api/v1/ip.py:36 api/v1/ip.py:73 api/v1/ip.py:110 +#, python-brace-format +msgid "Ошибка проверки IP конфигурации: {error}" +msgstr "" + +#: api/v1/ip.py:64 api/v1/ip.py:101 +#, python-brace-format +msgid "Настройки сетевого профиля для {connection_id} применены" +msgstr "" + +#: api/v1/network.py:62 +#, python-brace-format +msgid "Неверное имя контейнера: {container_name}" +msgstr "" + +#: api/v1/network.py:129 +msgid "Не удалось получить сетевые данные" +msgstr "" + +#: api/v1/network.py:182 +msgid "" +"Отсутствуют обязательные поля: container_id, container_x, container_y, " +"isOpen" +msgstr "" + +#: api/v1/network.py:194 +msgid "Неверный тип данных в запросе" +msgstr "" + +#: api/v1/network.py:199 +msgid "Позиция и состояние контейнера обновлены" +msgstr "" + +#: api/v1/network.py:203 +#, python-brace-format +msgid "Не удалось обновить положение контейнера: {error}" +msgstr "" + +#: api/v1/network.py:225 +msgid "Не удалось получить имя хоста" +msgstr "" + +#: api/v1/settings.py:37 +#, python-brace-format +msgid "Данные узлов подсети {subnet} успешно очищены" +msgstr "" + +#: api/v1/settings.py:39 +msgid "Данные всех подсетей успешно очищены" +msgstr "" + +#: api/v1/settings.py:41 +msgid "Не удалось очистить данные узлов подсети" +msgstr "" + +#: api/v1/settings.py:44 api/v1/settings.py:69 api/v1/settings.py:94 +#, python-brace-format +msgid "Ошибка очистки: {error}" +msgstr "" + +#: api/v1/settings.py:64 +msgid "Данные точек доступа Wi-Fi успешно очищены" +msgstr "" + +#: api/v1/settings.py:66 +msgid "Не удалось очистить данные точек доступа Wi-Fi" +msgstr "" + +#: api/v1/settings.py:89 +msgid "Все сетевые данные успешно очищены" +msgstr "" + +#: api/v1/settings.py:91 +msgid "Не удалось очистить все сетевые данные" +msgstr "" + +#: api/v1/settings.py:117 api/v1/settings.py:141 +msgid "Файл не найден" +msgstr "" + +#: api/v1/settings.py:166 +#, python-brace-format +msgid "Язык '{locale}' не поддерживается." +msgstr "" + +#: api/v1/settings.py:172 +#, python-brace-format +msgid "Язык '{locale}' не поддерживается (нет файлов переводов)." +msgstr "" + +#: api/v1/settings.py:175 +#, python-brace-format +msgid "Язык успешно установлен: {locale}" +msgstr "" + +#: api/v1/subnet.py:44 +#, python-brace-format +msgid "Ошибка сканирования подсети: {error}" +msgstr "" + +#: api/v1/subnet.py:67 +#, python-brace-format +msgid "Ошибка сканирования Wi-Fi: {error}" +msgstr "" + +#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159 +#, python-brace-format +msgid "Устройство с MAC {mac} не найдено" +msgstr "" + +#: api/v1/subnet.py:94 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} уже существует" +msgstr "" + +#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163 +#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264 +#: services/wifi_service.py:319 services/wifi_service.py:337 +#: services/wifi_service.py:355 +#, python-brace-format +msgid "Внутренняя ошибка сервера: {error}" +msgstr "" + +#: api/v1/subnet.py:97 +#, python-brace-format +msgid "Заметка '{note}' добавлена для устройства {mac}" +msgstr "" + +#: api/v1/subnet.py:101 api/v1/subnet.py:202 +#, python-brace-format +msgid "Ошибка добавления заметки: {error}" +msgstr "" + +#: api/v1/subnet.py:128 api/v1/subnet.py:161 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} не существует" +msgstr "" + +#: api/v1/subnet.py:131 +#, python-brace-format +msgid "Заметка '{note}' обновлена для устройства {mac}" +msgstr "" + +#: api/v1/subnet.py:135 api/v1/subnet.py:236 +#, python-brace-format +msgid "Ошибка обновления заметки: {error}" +msgstr "" + +#: api/v1/subnet.py:164 +#, python-brace-format +msgid "Заметка удалена для устройства {mac}" +msgstr "" + +#: api/v1/subnet.py:168 api/v1/subnet.py:269 +#, python-brace-format +msgid "Ошибка удаления заметки: {error}" +msgstr "" + +#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260 +#: services/wifi_service.py:327 services/wifi_service.py:345 +#, python-brace-format +msgid "Сеть с SSID {ssid} не найдена" +msgstr "" + +#: api/v1/subnet.py:195 services/wifi_service.py:312 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} уже существует" +msgstr "" + +#: api/v1/subnet.py:198 +#, python-brace-format +msgid "Заметка '{note}' добавлена для сети с SSID {ssid}" +msgstr "" + +#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330 +#: services/wifi_service.py:348 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} не существует" +msgstr "" + +#: api/v1/subnet.py:232 +#, python-brace-format +msgid "Заметка '{note}' обновлена для сети с SSID {ssid}" +msgstr "" + +#: api/v1/subnet.py:265 +#, python-brace-format +msgid "Заметка удалена для сети с SSID {ssid}" +msgstr "" + +#: api/v1/subnet.py:293 +#, python-brace-format +msgid "Успешно подключено к {ssid}" +msgstr "" + +#: api/v1/subnet.py:297 +#, python-brace-format +msgid "Ошибка подключения к Wi-Fi: {error}" +msgstr "" + +#: api/v1/subnet.py:320 +#, python-brace-format +msgid "Успешно отключено от {ssid}" +msgstr "" + +#: api/v1/subnet.py:324 +#, python-brace-format +msgid "Ошибка отключения от Wi-Fi: {error}" +msgstr "" + +#: api/v1/subnet.py:348 +msgid "connected" +msgstr "" + +#: api/v1/subnet.py:352 +#, python-brace-format +msgid "Ошибка проверки статуса Wi-Fi: {error}" +msgstr "" + +#: api/v1/websocket.py:145 +msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети" +msgstr "" + +#: services/arp_service.py:102 +msgid "Доступные устройства в сети:" +msgstr "" + +#: services/arp_service.py:103 +msgid "IP" +msgstr "" + +#: services/arp_service.py:103 +msgid "MAC" +msgstr "" + +#: services/auth_service.py:72 +msgid "Ошибка регистрации" +msgstr "" + +#: services/auth_service.py:80 services/auth_service.py:84 +msgid "Неверный логин или пароль" +msgstr "" + +#: services/auth_service.py:100 +msgid "Ошибка авторизации" +msgstr "" + +#: services/auth_service.py:108 +msgid "Неверный токен" +msgstr "" + +#: services/auth_service.py:113 services/auth_service.py:128 +msgid "Пользователь не найден" +msgstr "" + +#: services/auth_service.py:122 +msgid "Сессия недействительна" +msgstr "" + +#: services/auth_service.py:144 +msgid "Успешный выход из системы" +msgstr "" + +#: services/auth_service.py:148 +msgid "Ошибка при выходе из системы" +msgstr "" + +#: services/connect_service.py:70 services/connect_service.py:117 +#: services/connect_service.py:154 services/wifi_service.py:227 +msgid "Wi-Fi-интерфейс не доступен" +msgstr "" + +#: services/connect_service.py:74 +#, python-brace-format +msgid "Сеть с SSID {ssid} не найдена в списке доступных сетей" +msgstr "" + +#: services/connect_service.py:81 services/connect_service.py:92 +#, python-brace-format +msgid "Ошибка подключения к {ssid}: Неверный пароль" +msgstr "" + +#: services/connect_service.py:83 services/connect_service.py:94 +#, python-brace-format +msgid "" +"Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно " +"указана" +msgstr "" + +#: services/connect_service.py:85 services/connect_service.py:95 +#, python-brace-format +msgid "Ошибка подключения к {ssid}: {error}" +msgstr "" + +#: services/connect_service.py:98 +#, python-brace-format +msgid "Ошибка подключения к {ssid}: Неизвестная ошибка - {error}" +msgstr "" + +#: services/connect_service.py:122 +#, python-brace-format +msgid "Нет активного подключения для SSID {ssid}" +msgstr "" + +#: services/connect_service.py:133 +#, python-brace-format +msgid "Активное подключение с SSID {ssid} не найдено" +msgstr "" + +#: services/connect_service.py:136 services/connect_service.py:139 +#, python-brace-format +msgid "Ошибка отключения от {ssid}: {error}" +msgstr "" + +#: services/connect_service.py:160 +msgid "Не подключено к Wi-Fi" +msgstr "" + +#: services/connect_service.py:163 +#, python-brace-format +msgid "Ошибка проверки статуса: {error}" +msgstr "" + +#: services/ip_service.py:69 +#, python-brace-format +msgid "IP-адрес {ip} уже занят." +msgstr "" + +#: services/ip_service.py:73 +msgid "Настройки сетевого профиля доступны для использования." +msgstr "" + +#: services/ip_service.py:102 +#, python-brace-format +msgid "Подключение {connection_id} не найдено" +msgstr "" + +#: services/ip_service.py:130 +#, python-brace-format +msgid "Устройство для интерфейса {interface} не найдено" +msgstr "" + +#: services/network_service.py:232 +#, python-brace-format +msgid "Не удалось выполнить команду hostname: {error}" +msgstr "" + +#: services/network_service.py:234 +#, python-brace-format +msgid "Ошибка обработки вывода hostname: {error}" +msgstr "" + +#: services/settings_service.py:55 +#, python-brace-format +msgid "Ошибка очистки узлов подсети: {error}" +msgstr "" + +#: services/settings_service.py:76 +#, python-brace-format +msgid "Ошибка очистки точек доступа Wi-Fi: {error}" +msgstr "" + +#: services/settings_service.py:104 +#, python-brace-format +msgid "Ошибка очистки всех сетевых данных: {error}" +msgstr "" + +#: services/subnet_service.py:75 services/wifi_service.py:115 +msgid "Неизвестный" +msgstr "" + +#: services/subnet_service.py:185 +#, python-brace-format +msgid "Ошибка при сохранении устройств: {error}" +msgstr "" + +#: services/subnet_service.py:268 +msgid "Недопустимая подсеть по умолчанию" +msgstr "" + +#: services/subnet_service.py:288 +msgid "В данной подсети не обнаружено устройств" +msgstr "" + +#: services/subnet_service.py:345 +#, python-brace-format +msgid "Ошибка сканирования: {error}" +msgstr "" + +#: services/wifi_service.py:61 +#, python-brace-format +msgid "Ошибка при сохранении заметок: {error}" +msgstr "" + +#: services/wifi_service.py:81 +msgid "Не найдено сетевых интерфейсов" +msgstr "" + +#: services/wifi_service.py:102 +msgid "Не найден активный Wi-Fi-интерфейс" +msgstr "" + +#: services/wifi_service.py:122 +msgid "WPA3" +msgstr "" + +#: services/wifi_service.py:124 +msgid "WPA2" +msgstr "" + +#: services/wifi_service.py:126 +msgid "WPA" +msgstr "" + +#: services/wifi_service.py:128 +msgid "WEP" +msgstr "" + +#: services/wifi_service.py:129 +msgid "Открытая" +msgstr "" + +#: services/wifi_service.py:152 +msgid "2.4 ГГц" +msgstr "" + +#: services/wifi_service.py:154 +msgid "5 ГГц" +msgstr "" + +#: services/wifi_service.py:165 +msgid "Интерфейс не определён" +msgstr "" + +#: services/wifi_service.py:215 +msgid "Таймаут при выполнении iw scan" +msgstr "" + diff --git a/SRV/locales/ru/LC_MESSAGES/messages.mo b/SRV/locales/ru/LC_MESSAGES/messages.mo index 6df629f8fd1e8b6cf0a5a9d28176190976939c54..d1ae31a51376e255dbd255d981df783a64c3a4d3 100644 GIT binary patch delta 2127 zcmdVZ|7+D%9LMpqIq&AwrAuAgT)TeRm(-Tmc`xp6Yqz-(+mvQ@wJ(E7Bb(4Qs0E3| z-ItaHmC5bPFJ1&S&z+@9e?r^t9_M}T z`Fzg%yw5qQJ;(OOXN&XRHT?ARSn!CiW+wSx8fDth<@JEfl=Io zN4)r^7l+9HLHb8f zIEn509yg$`$WGXRTZxA-ikH3qsyl1~G3;V~GfATp=PxoQggY>TZ{q#<72byn7u)Zf zv7C4SmGIlR3?1Bz*O5oe+G4w{O}L8q15{!X)c;-@i>aq< zL1h}lZcL&k`q}$_HCJRO@x$1QA0qEEg%vigLRDnc^E^)HPzmJRnW;*9i@w2J;_u@$ zbd9c|u2G@iTI5-bdGs&yEJszqkGe(=AqU;;K%Jpo*oCLC0e`~?23FXTKZhE326Z^& zxhrjnyHR^SiYnn{FJ8_*DBg{_Mk&;|KXDtjb0D_k``Cmxumx*b)f()_9!#Ka(JgGm zoz>X{<0e6axy%%{YXE9*LN#{6J*Z5_Fp7VB{n1*RKoYg$Ur;M+yvJ_As~91^fI8%V zp|-Y}th7ZjEZ6-%PD2@ALJns3#B7EVPL8h86R1P>46N6tD@mp`P!@W=tZlHs4}g0}H9QRv5uBob4JnEV)Z1&+Edi1Ue)(NMoT;tsjP?x3Dh%Nb9E z^ItD@#oDF~-|cGAAbnQ+cC!NJ=ebKl5@ zOvRTMopbJiY@!qHTdAqqPko8QYpYYcN{-CU5BUT2;Xp9qt61@GJ5^Uvll#A4TjSh+ E08Xg3c>n+a delta 1718 zcmZwHduYvJ9LMpmnO)p>+%|OB=5E6oTkR4jHkw)CB+)kImP;veDOP9sV=Uy4GtxvZ zAt|(zg+gw*--_g(7%6{H(8&Gy`q1gswA_Hgw>dWOE3Xf;$U2jx~0jx*LwgH=|Ai}hNFm2B42yOpH#D`y`wRe zj!#&K#mQ!2T!ZRw!BT9;)5qatEXNHv4X^q*z-)@Ea0(tpjr)u2hPJv;v+Z_5b++=4VO?ie8*`xx~I!jE!Gh~L2b?4UT)#rF-&{` zHLq4Z5z0^*-He*|N^kP775}6|69oIXLlQv^*ob3)wkC!3ow^bkqhxjJG0%$ zd9pP@S0#0rK-_?;lC7vJY4)~w&qiq^a^bx9GOA3jBL!jKkOOROs6&%7!0l}W!^GQA z{dZBR&KT%2S%ccz7;4L2_;^I7i&vwnBzlpC2Bv1YQyRet@gXe1A2zWNr4*n;vpiQi_YU zH+yNY27BQBjm%}aT%3;8s0STKjeCo#lE7G370Xana?{7Hs7#cxT3I_T)|B)nb|bky v)|j#=o{$<#i9bp^mlVIBzNtfU{=iUv(U?%ICYb5ldGY$-(1ibv&+hOCt9Q{_ diff --git a/SRV/locales/ru/LC_MESSAGES/messages.po b/SRV/locales/ru/LC_MESSAGES/messages.po index 42ba62b..519a662 100644 --- a/SRV/locales/ru/LC_MESSAGES/messages.po +++ b/SRV/locales/ru/LC_MESSAGES/messages.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-07-15 15:01+0500\n" -"PO-Revision-Date: 2025-07-15 15:02+0500\n" +"POT-Creation-Date: 2025-07-16 13:15+0500\n" +"PO-Revision-Date: 2025-07-16 13:15+0500\n" "Last-Translator: FULL NAME \n" "Language: ru\n" "Language-Team: ru \n" @@ -99,134 +99,179 @@ msgstr "Не удалось обновить положение контейне msgid "Не удалось получить имя хоста" msgstr "Не удалось получить имя хоста" -#: api/v1/settings.py:33 -msgid "Данные узлов подсети успешно очищены" -msgstr "Данные узлов подсети успешно очищены" +#: api/v1/settings.py:37 +#, python-brace-format +msgid "Данные узлов подсети {subnet} успешно очищены" +msgstr "Данные узлов подсети {subnet} успешно очищены" -#: api/v1/settings.py:35 +#: api/v1/settings.py:39 +msgid "Данные всех подсетей успешно очищены" +msgstr "Данные всех подсетей успешно очищены" + +#: api/v1/settings.py:41 msgid "Не удалось очистить данные узлов подсети" msgstr "Не удалось очистить данные узлов подсети" -#: api/v1/settings.py:38 api/v1/settings.py:63 api/v1/settings.py:88 +#: api/v1/settings.py:44 api/v1/settings.py:69 api/v1/settings.py:94 #, python-brace-format msgid "Ошибка очистки: {error}" msgstr "Ошибка очистки: {error}" -#: api/v1/settings.py:58 +#: api/v1/settings.py:64 msgid "Данные точек доступа Wi-Fi успешно очищены" msgstr "Данные точек доступа Wi-Fi успешно очищены" -#: api/v1/settings.py:60 +#: api/v1/settings.py:66 msgid "Не удалось очистить данные точек доступа Wi-Fi" msgstr "Не удалось очистить данные точек доступа Wi-Fi" -#: api/v1/settings.py:83 +#: api/v1/settings.py:89 msgid "Все сетевые данные успешно очищены" msgstr "Все сетевые данные успешно очищены" -#: api/v1/settings.py:85 +#: api/v1/settings.py:91 msgid "Не удалось очистить все сетевые данные" msgstr "Не удалось очистить все сетевые данные" -#: api/v1/settings.py:109 api/v1/settings.py:133 +#: api/v1/settings.py:117 api/v1/settings.py:141 msgid "Файл не найден" msgstr "Файл не найден" -#: api/v1/settings.py:158 +#: api/v1/settings.py:166 #, python-brace-format msgid "Язык '{locale}' не поддерживается." msgstr "Язык '{locale}' не поддерживается." -#: api/v1/settings.py:164 +#: api/v1/settings.py:172 #, python-brace-format msgid "Язык '{locale}' не поддерживается (нет файлов переводов)." msgstr "Язык '{locale}' не поддерживается (нет файлов переводов)." -#: api/v1/settings.py:167 +#: api/v1/settings.py:175 #, python-brace-format msgid "Язык успешно установлен: {locale}" msgstr "Язык успешно установлен: {locale}" -#: api/v1/subnet.py:45 +#: api/v1/subnet.py:44 #, python-brace-format -msgid "Ошибка сканирования: {error}" -msgstr "Ошибка сканирования: {error}" +msgid "Ошибка сканирования подсети: {error}" +msgstr "Ошибка сканирования подсети: {error}" -#: api/v1/subnet.py:68 +#: api/v1/subnet.py:67 #, python-brace-format msgid "Ошибка сканирования Wi-Fi: {error}" msgstr "Ошибка сканирования Wi-Fi: {error}" -#: api/v1/subnet.py:96 +#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159 +#, python-brace-format +msgid "Устройство с MAC {mac} не найдено" +msgstr "Устройство с MAC {mac} не найдено" + +#: api/v1/subnet.py:94 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} уже существует" +msgstr "Заметка для устройства с MAC {mac} уже существует" + +#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163 +#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264 +#: services/wifi_service.py:319 services/wifi_service.py:337 +#: services/wifi_service.py:355 +#, python-brace-format +msgid "Внутренняя ошибка сервера: {error}" +msgstr "Внутренняя ошибка сервера: {error}" + +#: api/v1/subnet.py:97 #, python-brace-format msgid "Заметка '{note}' добавлена для устройства {mac}" msgstr "Заметка '{note}' добавлена для устройства {mac}" -#: api/v1/subnet.py:100 api/v1/subnet.py:195 +#: api/v1/subnet.py:101 api/v1/subnet.py:202 #, python-brace-format msgid "Ошибка добавления заметки: {error}" msgstr "Ошибка добавления заметки: {error}" -#: api/v1/subnet.py:128 +#: api/v1/subnet.py:128 api/v1/subnet.py:161 +#, python-brace-format +msgid "Заметка для устройства с MAC {mac} не существует" +msgstr "Заметка для устройства с MAC {mac} не существует" + +#: api/v1/subnet.py:131 #, python-brace-format msgid "Заметка '{note}' обновлена для устройства {mac}" msgstr "Заметка '{note}' обновлена для устройства {mac}" -#: api/v1/subnet.py:132 api/v1/subnet.py:227 +#: api/v1/subnet.py:135 api/v1/subnet.py:236 #, python-brace-format msgid "Ошибка обновления заметки: {error}" msgstr "Ошибка обновления заметки: {error}" -#: api/v1/subnet.py:159 +#: api/v1/subnet.py:164 #, python-brace-format msgid "Заметка удалена для устройства {mac}" msgstr "Заметка удалена для устройства {mac}" -#: api/v1/subnet.py:163 api/v1/subnet.py:258 +#: api/v1/subnet.py:168 api/v1/subnet.py:269 #, python-brace-format msgid "Ошибка удаления заметки: {error}" msgstr "Ошибка удаления заметки: {error}" -#: api/v1/subnet.py:191 +#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260 +#: services/wifi_service.py:327 services/wifi_service.py:345 +#, python-brace-format +msgid "Сеть с SSID {ssid} не найдена" +msgstr "Сеть с SSID {ssid} не найдена" + +#: api/v1/subnet.py:195 services/wifi_service.py:312 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} уже существует" +msgstr "Заметка для сети с SSID {ssid} уже существует" + +#: api/v1/subnet.py:198 #, python-brace-format msgid "Заметка '{note}' добавлена для сети с SSID {ssid}" msgstr "Заметка '{note}' добавлена для сети с SSID {ssid}" -#: api/v1/subnet.py:223 +#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330 +#: services/wifi_service.py:348 +#, python-brace-format +msgid "Заметка для сети с SSID {ssid} не существует" +msgstr "Заметка для сети с SSID {ssid} не существует" + +#: api/v1/subnet.py:232 #, python-brace-format msgid "Заметка '{note}' обновлена для сети с SSID {ssid}" msgstr "Заметка '{note}' обновлена для сети с SSID {ssid}" -#: api/v1/subnet.py:254 +#: api/v1/subnet.py:265 #, python-brace-format msgid "Заметка удалена для сети с SSID {ssid}" msgstr "Заметка удалена для сети с SSID {ssid}" -#: api/v1/subnet.py:282 +#: api/v1/subnet.py:293 #, python-brace-format msgid "Успешно подключено к {ssid}" msgstr "Успешно подключено к {ssid}" -#: api/v1/subnet.py:286 +#: api/v1/subnet.py:297 #, python-brace-format msgid "Ошибка подключения к Wi-Fi: {error}" msgstr "Ошибка подключения к Wi-Fi: {error}" -#: api/v1/subnet.py:309 +#: api/v1/subnet.py:320 #, python-brace-format msgid "Успешно отключено от {ssid}" msgstr "Успешно отключено от {ssid}" -#: api/v1/subnet.py:313 +#: api/v1/subnet.py:324 #, python-brace-format msgid "Ошибка отключения от Wi-Fi: {error}" msgstr "Ошибка отключения от Wi-Fi: {error}" -#: api/v1/subnet.py:337 +#: api/v1/subnet.py:348 msgid "connected" msgstr "connected" -#: api/v1/subnet.py:341 +#: api/v1/subnet.py:352 #, python-brace-format msgid "Ошибка проверки статуса Wi-Fi: {error}" msgstr "Ошибка проверки статуса Wi-Fi: {error}" @@ -366,17 +411,17 @@ msgstr "Не удалось выполнить команду hostname: {error}" msgid "Ошибка обработки вывода hostname: {error}" msgstr "Ошибка обработки вывода hostname: {error}" -#: services/settings_service.py:37 +#: services/settings_service.py:55 #, python-brace-format msgid "Ошибка очистки узлов подсети: {error}" msgstr "Ошибка очистки узлов подсети: {error}" -#: services/settings_service.py:58 +#: services/settings_service.py:76 #, python-brace-format msgid "Ошибка очистки точек доступа Wi-Fi: {error}" msgstr "Ошибка очистки точек доступа Wi-Fi: {error}" -#: services/settings_service.py:84 +#: services/settings_service.py:104 #, python-brace-format msgid "Ошибка очистки всех сетевых данных: {error}" msgstr "Ошибка очистки всех сетевых данных: {error}" @@ -385,33 +430,23 @@ msgstr "Ошибка очистки всех сетевых данных: {error msgid "Неизвестный" msgstr "Неизвестный" -#: services/subnet_service.py:179 +#: services/subnet_service.py:185 #, python-brace-format msgid "Ошибка при сохранении устройств: {error}" msgstr "Ошибка при сохранении устройств: {error}" -#: services/subnet_service.py:374 services/subnet_service.py:391 -#: services/subnet_service.py:408 -#, python-brace-format -msgid "Устройство с MAC {mac} не найдено" -msgstr "Устройство с MAC {mac} не найдено" +#: services/subnet_service.py:268 +msgid "Недопустимая подсеть по умолчанию" +msgstr "Недопустимая подсеть по умолчанию" -#: services/subnet_service.py:377 -#, python-brace-format -msgid "Заметка для устройства с MAC {mac} уже существует" -msgstr "Заметка для устройства с MAC {mac} уже существует" +#: services/subnet_service.py:288 +msgid "В данной подсети не обнаружено устройств" +msgstr "В данной подсети не обнаружено устройств" -#: services/subnet_service.py:384 services/subnet_service.py:401 -#: services/subnet_service.py:418 services/wifi_service.py:319 -#: services/wifi_service.py:337 services/wifi_service.py:355 +#: services/subnet_service.py:345 #, python-brace-format -msgid "Внутренняя ошибка сервера: {error}" -msgstr "Внутренняя ошибка сервера: {error}" - -#: services/subnet_service.py:394 services/subnet_service.py:411 -#, python-brace-format -msgid "Заметка для устройства с MAC {mac} не существует" -msgstr "Заметка для устройства с MAC {mac} не существует" +msgid "Ошибка сканирования: {error}" +msgstr "Ошибка сканирования: {error}" #: services/wifi_service.py:61 #, python-brace-format @@ -460,19 +495,4 @@ msgstr "Интерфейс не определён" #: services/wifi_service.py:215 msgid "Таймаут при выполнении iw scan" -msgstr "Таймаут при выполнении iw scan" - -#: services/wifi_service.py:312 -#, python-brace-format -msgid "Заметка для сети с SSID {ssid} уже существует" -msgstr "Заметка для сети с SSID {ssid} уже существует" - -#: services/wifi_service.py:327 services/wifi_service.py:345 -#, python-brace-format -msgid "Сеть с SSID {ssid} не найдена" -msgstr "Сеть с SSID {ssid} не найдена" - -#: services/wifi_service.py:330 services/wifi_service.py:348 -#, python-brace-format -msgid "Заметка для сети с SSID {ssid} не существует" -msgstr "Заметка для сети с SSID {ssid} не существует" \ No newline at end of file +msgstr "Таймаут при выполнении iw scan" \ No newline at end of file diff --git a/SRV/models/subnet.py b/SRV/models/subnet.py index 8352e9c..148f64c 100644 --- a/SRV/models/subnet.py +++ b/SRV/models/subnet.py @@ -1,7 +1,6 @@ from pydantic import BaseModel from typing import Optional, List - class Device(BaseModel): """ Сканируемое устройство в подсети @@ -26,17 +25,17 @@ class Device(BaseModel): last_seen: Optional[str] = None note: Optional[str] = None - class SubnetScanResult(BaseModel): """ Результат сканирования устройств в подсети Атрибуты: devices (List[Device]): Список просканированных устройств + error (Optional[str]): Сообщение об ошибке, если сканирование не удалось """ devices: List[Device] = [] - + error: Optional[str] = None class SubnetScanRequest(BaseModel): """ diff --git a/SRV/services/settings_service.py b/SRV/services/settings_service.py index 227d89c..df2c7dc 100644 --- a/SRV/services/settings_service.py +++ b/SRV/services/settings_service.py @@ -1,8 +1,11 @@ import json import logging -from utils.i18n import get_translations, _ +from typing import Optional + +from utils.i18n import get_translations from babel.support import Translations from fastapi import Depends +from pathlib import Path logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -11,12 +14,16 @@ class SettingsService: """Сервис для управления настройками и очистки данных сети.""" def __init__(self, translations: Translations = Depends(get_translations())): - self.subnet_file = "storage/devices/devices.json" + self.subnet_base_path = "storage/subnets" self.wifi_file = "storage/wifi/wifi.json" self.translations = translations - def clear_subnet(self) -> bool: - """Очищает данные узлов подсети в файле devices.json. + def clear_subnet(self, subnet: Optional[str] = None) -> bool: + """Очищает данные узлов подсети в указанном файле или во всех файлах. + + Args: + subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24"). + Если None, очищает все подсети. Returns: bool: True, если очистка успешна. @@ -25,12 +32,23 @@ class SettingsService: Exception: Если произошла ошибка при записи в файл. """ try: - with open(self.subnet_file, "w") as f: - json.dump([], f) - logger.info("Subnet nodes data successfully cleared") - return True + if subnet: + file_path = Path(self.subnet_base_path) / f"{subnet.replace('/', '_')}.json" + if file_path.exists(): + with open(file_path, "w") as f: + json.dump([], f) + logger.info(f"Subnet nodes data cleared for {subnet}") + else: + logger.info(f"Subnet file not found for {subnet}, clearing considered successful") + return True + else: + for file_path in Path(self.subnet_base_path).glob("*.json"): + with open(file_path, "w") as f: + json.dump([], f) + logger.info(f"Cleared subnet data in {file_path}") + return True except FileNotFoundError: - logger.info("Devices file not found, clearing considered successful") + logger.info("No subnet files found, clearing considered successful") return True except Exception as e: logger.error(f"Error clearing subnet nodes: {str(e)}") @@ -58,7 +76,7 @@ class SettingsService: raise Exception(self.translations.gettext("Ошибка очистки точек доступа Wi-Fi: {error}").format(error=str(e))) def clear_all_data(self) -> bool: - """Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах. + """Очищает данные узлов всех подсетей и точек доступа Wi-Fi. Returns: bool: True, если очистка успешна. @@ -68,10 +86,12 @@ class SettingsService: """ try: try: - with open(self.subnet_file, "w") as f: - json.dump([], f) + for file_path in Path(self.subnet_base_path).glob("*.json"): + with open(file_path, "w") as f: + json.dump([], f) + logger.info(f"Cleared subnet data in {file_path}") except FileNotFoundError: - logger.info("Devices file not found, clearing considered successful") + logger.info("No subnet files found, clearing considered successful") try: with open(self.wifi_file, "w") as f: json.dump([], f) diff --git a/SRV/services/subnet_service.py b/SRV/services/subnet_service.py index c946e68..e6a84b1 100644 --- a/SRV/services/subnet_service.py +++ b/SRV/services/subnet_service.py @@ -27,14 +27,53 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +def get_subnet_service(translations: Translations = Depends(get_translations())): + """Фабричная функция для создания экземпляра SubnetService. + + Использует зависимости FastAPI для получения объекта переводов и возвращает + экземпляр SubnetService с текущей локализацией. + + Args: + translations (Translations): Объект переводов для текущего языка. + + Returns: + SubnetService: Экземпляр сервиса для работы с подсетями. + """ + return SubnetService(translations) + class NetworkScanner: - """Класс для сканирования подсети с использованием ARP-запросов.""" - def __init__(self, interface: str, translations: Translations = Depends(get_translations())): + """Класс для сканирования подсети с использованием ARP-запросов. + + Выполняет отправку ARP-запросов для обнаружения устройств в указанной подсети. + """ + def __init__(self, interface: str, translations: Translations): + """Инициализирует сканер сети. + + Устанавливает интерфейс для сканирования и объект переводов. + + Args: + interface (str): Имя сетевого интерфейса (например, "wlp0s20f3"). + translations (Translations): Объект переводов для текущего языка. + """ self.interface = interface conf.iface = interface self.translations = translations def scan(self, subnet: str) -> List[Tuple[str, str]]: + """Сканирует подсеть с использованием ARP-запросов. + + Отправляет ARP-пакеты для обнаружения активных устройств и возвращает + список кортежей (IP, MAC). + + Args: + subnet (str): Подсеть в формате CIDR (например, "10.1.1.0/24"). + + Returns: + List[Tuple[str, str]]: Список кортежей (IP-адрес, MAC-адрес) обнаруженных устройств. + + Raises: + Exception: Если произошла ошибка сканирования. + """ try: arp = ARP(pdst=subnet) ether = Ether(dst="ff:ff:ff:ff:ff:ff") @@ -46,17 +85,48 @@ class NetworkScanner: return [] class PortScanner: - """Класс для сканирования открытых портов на устройствах.""" - def __init__(self, interface: str, translations: Translations = Depends(get_translations())): + """Класс для сканирования открытых портов на устройствах. + + Предоставляет функциональность для проверки доступности портов, пока не реализована. + """ + def __init__(self, interface: str, translations: Translations): + """Инициализирует сканер портов. + + Устанавливает интерфейс для сканирования и объект переводов. + + Args: + interface (str): Имя сетевого интерфейса. + translations (Translations): Объект переводов для текущего языка. + """ self.interface = interface self.translations = translations def scan(self, ip: str) -> List[str]: + """Сканирует открытые порты на указанном IP-адресе. + + Пока возвращает пустой список, так как функциональность не реализована. + + Args: + ip (str): IP-адрес устройства для сканирования. + + Returns: + List[str]: Список открытых портов (пока пустой). + """ return [] class DeviceInfoCollector: - """Класс для сбора информации об устройствах, включая вендоров и хостнеймы.""" - def __init__(self, translations: Translations = Depends(get_translations())): + """Класс для сбора информации об устройствах, включая вендоров и хостнеймы. + + Использует внешние сервисы (MacLookup, NBTSCAN, DNS) для получения данных. + """ + def __init__(self, translations: Translations): + """Инициализирует коллектор информации об устройствах. + + Настраивает кэширование и объект переводов. + + Args: + translations (Translations): Объект переводов для текущего языка. + """ self.mac_lookup = MacLookup() self.nbtscan_cache = {} self.nbtscan_results = {} @@ -64,6 +134,16 @@ class DeviceInfoCollector: @lru_cache(maxsize=1000) def get_vendor(self, mac: str) -> str: + """Получает вендора устройства по MAC-адресу. + + Использует кэширование для повышения производительности. + + Args: + mac (str): MAC-адрес устройства. + + Returns: + str: Название вендора или "Неизвестный" в случае ошибки. + """ try: return self.mac_lookup.lookup(mac) except Exception as e: @@ -71,6 +151,13 @@ class DeviceInfoCollector: return self.translations.gettext("Неизвестный") def scan_nbtscan(self, subnet: str) -> None: + """Сканирует подсеть с помощью nbtscan для получения хостнеймов. + + Сохраняет результаты в кэш и логирует процесс. + + Args: + subnet (str): Подсеть в формате CIDR. + """ if subnet not in self.nbtscan_results: try: logger.debug(f"Starting nbtscan for subnet {subnet}") @@ -97,6 +184,16 @@ class DeviceInfoCollector: logger.error(f"NetBIOS: Error for subnet {subnet}: {str(e)}") def get_hostname(self, ip: str) -> Optional[str]: + """Получает хостнейм для указанного IP-адреса. + + Пробует сначала DNS, затем NetBIOS. + + Args: + ip (str): IP-адрес устройства. + + Returns: + Optional[str]: Хостнейм или None, если не удалось определить. + """ try: socket.setdefaulttimeout(2) return socket.gethostbyaddr(ip)[0] @@ -114,20 +211,56 @@ class DeviceInfoCollector: return None class DeviceStorage: - """Класс для хранения и управления данными об устройствах.""" - def __init__(self, storage_path: str = "storage/devices/devices.json", translations: Translations = Depends(get_translations())): - self.storage_file = Path(storage_path) - self.storage_file.parent.mkdir(parents=True, exist_ok=True) - self.offline_timeout = timedelta(days=7) - self.translations = translations + """Класс для хранения и управления данными об устройствах. - def load_devices(self) -> Dict[str, Device]: + Обеспечивает загрузку и сохранение данных в JSON-файлы. + """ + def __init__(self, base_path: str = "storage/subnets", translations: Translations = None): + """Инициализирует хранилище устройств. + + Создаёт директорию, если её нет, и задаёт параметры очистки. + + Args: + base_path (str): Путь к директории хранения данных. + translations (Translations, optional): Объект переводов. + """ + self.base_path = Path(base_path) + self.base_path.mkdir(parents=True, exist_ok=True) + self.offline_timeout = timedelta(days=7) + self.translations = translations or Translations.load("locales", ["ru"], "messages") # Fallback на русский + + def get_subnet_file(self, subnet: str) -> Path: + """Возвращает путь к файлу для конкретной подсети. + + Заменяет символы '/' на '_' для корректного имени файла. + + Args: + subnet (str): Подсеть в формате CIDR. + + Returns: + Path: Путь к файлу. + """ + sanitized_subnet = subnet.replace("/", "_") + return self.base_path / f"{sanitized_subnet}.json" + + def load_devices(self, subnet: str) -> Dict[str, Device]: + """Загружает устройства для указанной подсети. + + Читает данные из JSON-файла и фильтрует устаревшие устройства. + + Args: + subnet (str): Подсеть в формате CIDR. + + Returns: + Dict[str, Device]: Словарь устройств с MAC как ключом. + """ + file_path = self.get_subnet_file(subnet) try: - if self.storage_file.exists(): - with open(self.storage_file, "r", encoding="utf-8") as f: + if file_path.exists(): + with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) if not isinstance(data, dict) or "devices" not in data: - logger.warning(f"Invalid JSON structure in {self.storage_file}, expected {'devices': [...]}, got {data}") + logger.warning(f"Invalid JSON structure in {file_path}, expected {'devices': [...]}, got {data}") return {} devices = {} for device_data in data.get("devices", []): @@ -137,13 +270,23 @@ class DeviceStorage: return self._clean_old_devices(devices) return {} except json.JSONDecodeError as e: - logger.error(f"JSON decode error in {self.storage_file}: {str(e)}") + logger.error(f"JSON decode error in {file_path}: {str(e)}") return {} except Exception as e: - logger.error(f"Error loading devices: {str(e)}") + logger.error(f"Error loading devices for subnet {subnet}: {str(e)}") return {} def _clean_old_devices(self, devices: Dict[str, Device]) -> Dict[str, Device]: + """Очищает список устройств от устаревших оффлайн-устройств. + + Удаляет устройства, которые оффлайн более 7 дней. + + Args: + devices (Dict[str, Device]): Словарь устройств. + + Returns: + Dict[str, Device]: Отфильтрованный словарь устройств. + """ now = datetime.now() filtered = {} for mac, device in devices.items(): @@ -160,28 +303,63 @@ class DeviceStorage: filtered[mac] = device return filtered - def save_devices(self, devices: List[Device]) -> None: + def save_devices(self, subnet: str, devices: List[Device]) -> None: + """Сохраняет устройства для указанной подсети. + + Записывает данные в JSON-файл. + + Args: + subnet (str): Подсеть в формате CIDR. + devices (List[Device]): Список устройств для сохранения. + + Raises: + Exception: Если произошла ошибка при сохранении. + """ + file_path = self.get_subnet_file(subnet) try: data = SubnetScanResult(devices=devices).model_dump() - with open(self.storage_file, "w", encoding="utf-8") as f: + with open(file_path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) - logger.info(f"Saved {len(devices)} devices to {self.storage_file}") + logger.info(f"Saved {len(devices)} devices to {file_path}") except Exception as e: - logger.error(f"Error saving devices: {str(e)}") - raise Exception(self.translations.gettext("Ошибка при сохранении устройств: {error}").format(error=str(e))) + logger.error(f"Error saving devices for subnet {subnet}: {str(e)}") + if self.translations: + raise Exception(self.translations.gettext("Ошибка при сохранении устройств: {error}").format(error=str(e))) + else: + raise Exception(f"Ошибка при сохранении устройств: {str(e)}") class SubnetService: - """Сервис для сканирования подсетей и управления устройствами.""" - def __init__(self, translations: Translations = Depends(get_translations())): + """Сервис для сканирования подсетей и управления устройствами. + + Объединяет функциональность сканирования, сбора информации и хранения данных. + """ + def __init__(self, translations: Translations): + """Инициализирует сервис подсети. + + Настраивает интерфейс, сканеры и хранилище с текущей локализацией. + + Args: + translations (Translations): Объект переводов для текущего языка. + """ self.interface = self._get_default_interface() - self.network_scanner = NetworkScanner(self.interface) - self.port_scanner = PortScanner(self.interface) - self.device_info = DeviceInfoCollector() - self.storage = DeviceStorage() + self.network_scanner = NetworkScanner(self.interface, translations) + self.port_scanner = PortScanner(self.interface, translations) + self.device_info = DeviceInfoCollector(translations) + self.storage = DeviceStorage(translations=translations) self.translations = translations logger.info(f"Initialized SubnetService with interface: {self.interface}") def _get_default_interface(self) -> str: + """Определяет интерфейс по умолчанию. + + Использует информацию о шлюзе или первый доступный интерфейс. + + Returns: + str: Имя интерфейса (например, "wlp0s20f3" или "eth0"). + + Raises: + Exception: Если не удалось определить интерфейс. + """ try: gateways = netifaces.gateways() default_gateway = gateways.get("default", {}).get(netifaces.AF_INET) @@ -203,7 +381,16 @@ class SubnetService: return "eth0" def _get_default_subnet(self) -> str: - """Автоматически определяет подсеть локального устройства.""" + """Автоматически определяет подсеть локального устройства. + + Использует информацию о текущем интерфейсе для определения подсети. + + Returns: + str: Подсеть в формате CIDR (например, "10.1.1.0/24"). + + Raises: + Exception: Если не удалось определить подсеть. + """ try: interfaces = netifaces.interfaces() for iface in interfaces: @@ -228,13 +415,18 @@ class SubnetService: def scan_subnet(self, subnet: Optional[str] = None) -> SubnetScanResult: """Сканирует указанную подсеть и возвращает результаты. + Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные. + Args: subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24"). Если None или пустая строка, определяется автоматически. Returns: SubnetScanResult: Объект с результатами сканирования, содержащий список - устройств. + устройств или сообщение об ошибке. + + Raises: + Exception: Если произошла ошибка сканирования. """ start_time = time.time() devices = [] @@ -246,9 +438,9 @@ class SubnetService: subnet = self._get_default_subnet() if subnet == "0.0.0.0/0": logger.warning("Default subnet is invalid, scan aborted") - return SubnetScanResult(devices=devices) + return SubnetScanResult(devices=devices, error=self.translations.gettext("Недопустимая подсеть по умолчанию")) - saved_devices = self.storage.load_devices() + saved_devices = self.storage.load_devices(subnet) local_device = self._get_local_device(subnet) if local_device: @@ -264,6 +456,10 @@ class SubnetService: logger.info(f"Discovered devices: {len(discovered_devices)}") self.device_info.scan_nbtscan(subnet) + if not discovered_devices and not local_device: + logger.warning(f"No devices found in subnet {subnet}") + return SubnetScanResult(devices=[], error=self.translations.gettext("В данной подсети не обнаружено устройств")) + def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]: ip, mac = ip_mac if local_device and ip == local_device.ip: @@ -309,7 +505,7 @@ class SubnetService: for device in devices: if device.mac: saved_devices[device.mac] = device - self.storage.save_devices(list(saved_devices.values())) + self.storage.save_devices(subnet, list(saved_devices.values())) logger.info( f"Subnet scan {subnet} completed in {time.time() - start_time:.2f} seconds, " @@ -319,9 +515,19 @@ class SubnetService: except Exception as e: logger.error(f"Error scanning subnet {subnet}: {str(e)}") - return SubnetScanResult(devices=devices) + return SubnetScanResult(devices=devices, error=self.translations.gettext("Ошибка сканирования: {error}").format(error=str(e))) def _get_local_device(self, subnet: str) -> Optional[Device]: + """Определяет локальное устройство в указанной подсети. + + Использует информацию об интерфейсе для обнаружения. + + Args: + subnet (str): Подсеть в формате CIDR. + + Returns: + Optional[Device]: Объект устройства или None, если не найдено. + """ now = datetime.now().astimezone() formatted_now = now.strftime("%d-%m-%Y, %H:%M") try: @@ -354,53 +560,115 @@ class SubnetService: logger.warning(f"Failed to get local device: {str(e)}") return None - def add_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]: + def add_note(self, mac: str, note: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]: + """Добавляет заметку для устройства по MAC-адресу. + + Привязывает заметку к устройству, если оно существует. + + Args: + mac (str): MAC-адрес устройства. + note (str): Текст заметки. + translations (Translations): Объект переводов для текущего языка. + + Returns: + Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат), + где результат содержит код ошибки/успеха и данные. + """ try: - saved_devices = self.storage.load_devices() + saved_devices = {} + for subnet in self.storage.base_path.glob("*.json"): + devices = self.storage.load_devices(subnet.stem.replace("_", "/")) + saved_devices.update(devices) if mac not in saved_devices: logger.warning(f"Device with MAC {mac} not found") - return False, self.translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac) + return False, ("not_found", {"mac": mac}) if saved_devices[mac].note is not None: logger.error(f"Note for device with MAC {mac} already exists") - return False, self.translations.gettext("Заметка для устройства с MAC {mac} уже существует").format(mac=mac) - saved_devices[mac].note = note - self.storage.save_devices(list(saved_devices.values())) + return False, ("note_exists", {"mac": mac}) + for subnet_file in self.storage.base_path.glob("*.json"): + subnet = subnet_file.stem.replace("_", "/") + devices = self.storage.load_devices(subnet) + if mac in devices: + devices[mac].note = note + self.storage.save_devices(subnet, list(devices.values())) + break logger.info(f"Added note '{note}' for device with MAC {mac}") - return True, None + return True, ("success", {"mac": mac, "note": note}) except Exception as e: logger.error(f"Error adding note for MAC {mac}: {str(e)}") - return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e)) + return False, ("internal_error", {"error": str(e)}) - def update_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]: + def update_note(self, mac: str, note: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]: + """Обновляет заметку для устройства по MAC-адресу. + + Изменяет существующую заметку, если она есть. + + Args: + mac (str): MAC-адрес устройства. + note (str): Новый текст заметки. + translations (Translations): Объект переводов для текущего языка. + + Returns: + Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат), + где результат содержит код ошибки/успеха и данные. + """ try: - saved_devices = self.storage.load_devices() + saved_devices = {} + for subnet in self.storage.base_path.glob("*.json"): + devices = self.storage.load_devices(subnet.stem.replace("_", "/")) + saved_devices.update(devices) if mac not in saved_devices: logger.warning(f"Device with MAC {mac} not found") - return False, self.translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac) + return False, ("not_found", {"mac": mac}) if saved_devices[mac].note is None: logger.error(f"Note for device with MAC {mac} does not exist") - return False, self.translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac) - saved_devices[mac].note = note - self.storage.save_devices(list(saved_devices.values())) + return False, ("note_not_exists", {"mac": mac}) + for subnet_file in self.storage.base_path.glob("*.json"): + subnet = subnet_file.stem.replace("_", "/") + devices = self.storage.load_devices(subnet) + if mac in devices: + devices[mac].note = note + self.storage.save_devices(subnet, list(devices.values())) + break logger.info(f"Updated note '{note}' for device with MAC {mac}") - return True, None + return True, ("success", {"mac": mac, "note": note}) except Exception as e: logger.error(f"Error updating note for MAC {mac}: {str(e)}") - return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e)) + return False, ("internal_error", {"error": str(e)}) - def delete_note(self, mac: str) -> Tuple[bool, Optional[str]]: + def delete_note(self, mac: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]: + """Удаляет заметку для устройства по MAC-адресу. + + Удаляет привязанную заметку, если она существует. + + Args: + mac (str): MAC-адрес устройства. + translations (Translations): Объект переводов для текущего языка. + + Returns: + Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат), + где результат содержит код ошибки/успеха и данные. + """ try: - saved_devices = self.storage.load_devices() + saved_devices = {} + for subnet in self.storage.base_path.glob("*.json"): + devices = self.storage.load_devices(subnet.stem.replace("_", "/")) + saved_devices.update(devices) if mac not in saved_devices: logger.warning(f"Device with MAC {mac} not found") - return False, self.translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac) + return False, ("not_found", {"mac": mac}) if saved_devices[mac].note is None: logger.error(f"Note for device with MAC {mac} does not exist") - return False, self.translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac) - saved_devices[mac].note = None - self.storage.save_devices(list(saved_devices.values())) + return False, ("note_not_exists", {"mac": mac}) + for subnet_file in self.storage.base_path.glob("*.json"): + subnet = subnet_file.stem.replace("_", "/") + devices = self.storage.load_devices(subnet) + if mac in devices: + devices[mac].note = None + self.storage.save_devices(subnet, list(devices.values())) + break logger.info(f"Deleted note for device with MAC {mac}") - return True, None + return True, ("success", {"mac": mac}) except Exception as e: logger.error(f"Error deleting note for MAC {mac}: {str(e)}") - return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e)) \ No newline at end of file + return False, ("internal_error", {"error": str(e)}) \ No newline at end of file