Разделение сканирования на разные подсети. Хранение каждой из просканированных подсетей. Исправление багов с заметками и обновленные локали

This commit is contained in:
parent c326edfb9a
commit b1306ff66f
10 changed files with 1124 additions and 285 deletions

View file

@ -1,5 +1,6 @@
import os import os
import logging import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, Depends, Response, Request from fastapi import APIRouter, HTTPException, Depends, Response, Request
from starlette.responses import FileResponse from starlette.responses import FileResponse
from services.settings_service import SettingsService from services.settings_service import SettingsService
@ -13,12 +14,14 @@ router = APIRouter()
settings_service = SettingsService() settings_service = SettingsService()
@router.post("/clear_subnet") @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. """Очищает данные узлов подсети, сохранённые в devices.json.
Удаляет все записи об узлах подсети, оставляя файл пустым. Удаляет все записи об узлах подсети, оставляя файл пустым.
Args: Args:
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
Если None, очищает все подсети.
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
@ -28,9 +31,12 @@ def clear_subnet(translations: Translations = Depends(get_translations())):
HTTPException: Если произошла ошибка очистки (status_code=500). HTTPException: Если произошла ошибка очистки (status_code=500).
""" """
try: try:
success = settings_service.clear_subnet() success = settings_service.clear_subnet(subnet)
if success: if success:
return {"message": translations.gettext("Данные узлов подсети успешно очищены")} if subnet:
return {"message": translations.gettext("Данные узлов подсети {subnet} успешно очищены").format(subnet=subnet)}
else:
return {"message": translations.gettext("Данные всех подсетей успешно очищены")}
else: else:
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные узлов подсети")) raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные узлов подсети"))
except Exception as e: except Exception as e:
@ -91,26 +97,28 @@ DEVICE_PATH = "storage/devices/devices.json"
WIFI_PATH = "storage/wifi/wifi.json" WIFI_PATH = "storage/wifi/wifi.json"
@router.get("/export_devices", response_class=FileResponse) @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: Args:
subnet (str): Подсеть в формате CIDR (например, "10.1.1.0/24").
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
FileResponse: Файл devices.json для скачивания. FileResponse: Файл <subnet>.json для скачивания.
Raises: Raises:
HTTPException: Если файл не найден (status_code=404). HTTPException: Если файл не найден (status_code=404).
""" """
if not os.path.exists(DEVICE_PATH): file_path = f"storage/subnets/{subnet.replace('/', '_')}.json"
logger.warning(f"Export file not found: {DEVICE_PATH}") if not os.path.exists(file_path):
logger.warning(f"Export file not found: {file_path}")
raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден")) raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден"))
logger.info(f"Exporting file: {DEVICE_PATH}") logger.info(f"Exporting file: {file_path}")
return FileResponse( return FileResponse(
path=DEVICE_PATH, path=file_path,
filename="devices.json", filename=f"{subnet.replace('/', '_')}.json",
media_type="application/json" media_type="application/json"
) )

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, HTTPException, Body, Depends
from models.subnet import SubnetScanResult, SubnetScanRequest from models.subnet import SubnetScanResult, SubnetScanRequest
from models.wifi import WifiScanResult from models.wifi import WifiScanResult
from models.request import NoteRequest, WifiConnectRequest, WifiDisconnectRequest 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.wifi_service import WifiService
from services.connect_service import WifiConnectionService from services.connect_service import WifiConnectionService
from utils.i18n import get_translations, _ from utils.i18n import get_translations, _
@ -14,15 +14,15 @@ router = APIRouter()
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@router.post("/devices", response_model=SubnetScanResult) @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: Args:
request (SubnetScanRequest): Объект с полем subnet. request (SubnetScanRequest): Объект с полем subnet.
subnet_service: Сервис сканирования подсети.
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
@ -32,17 +32,16 @@ def scan_subnet(request: SubnetScanRequest = Body(...), translations: Translatio
HTTPException: Если произошла ошибка сканирования (status_code=500). HTTPException: Если произошла ошибка сканирования (status_code=500).
""" """
try: try:
subnet_service = SubnetService()
# Если subnet пустая строка, определяем автоматически # Если subnet пустая строка, определяем автоматически
subnet = request.subnet.strip() if request.subnet else None subnet = request.subnet.strip() if request.subnet else None
logger.info(f"Requested subnet scan for: {subnet or 'auto-detected'}") logger.info(f"Requested subnet scan for: {subnet or 'auto-detected'}")
result = subnet_service.scan_subnet(subnet) 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'}") logger.warning(f"No devices found in subnet {subnet or 'auto-detected'}")
return result return result
except Exception as e: except Exception as e:
logger.error(f"Error in scan_subnet: {str(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) @router.get("/wifi", response_model=WifiScanResult)
def scan_wifi(timeout: int = 30, translations: Translations = Depends(get_translations())): 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))) raise HTTPException(status_code=500, detail=translations.gettext("Ошибка сканирования Wi-Fi: {error}").format(error=str(e)))
@router.post("/devices/{mac}/note") @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-адресу.
Привязывает заметку к устройству, идентифицируемому по MAC-адресу. Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
@ -76,6 +75,7 @@ def add_note(mac: str, request: NoteRequest, translations: Translations = Depend
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для текста заметки. request (NoteRequest): Объект с полем "note" для текста заметки.
subnet_service: Сервис сканирования подсети.
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
@ -86,13 +86,14 @@ def add_note(mac: str, request: NoteRequest, translations: Translations = Depend
существует (status_code=400), или произошла внутренняя ошибка (status_code=500). существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
""" """
try: try:
subnet_service = SubnetService() success, result = subnet_service.add_note(mac, request.note, translations)
success, error_message = subnet_service.add_note(mac, request.note)
if not success: if not success:
if "не найдено" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
elif "уже существует" in error_message.lower(): elif result[0] == "note_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка '{note}' добавлена для устройства {mac}").format(note=request.note, mac=mac)}
except HTTPException as e: except HTTPException as e:
raise 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))) raise HTTPException(status_code=500, detail=translations.gettext("Ошибка добавления заметки: {error}").format(error=str(e)))
@router.put("/devices/{mac}/note") @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-адресу. """Обновляет заметку для устройства по MAC-адресу.
Изменяет существующую заметку, привязанную к устройству. Изменяет существующую заметку, привязанную к устройству.
@ -108,6 +109,7 @@ def update_note(mac: str, request: NoteRequest, translations: Translations = Dep
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для нового текста заметки. request (NoteRequest): Объект с полем "note" для нового текста заметки.
subnet_service: Сервис сканирования подсети.
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
@ -118,13 +120,14 @@ def update_note(mac: str, request: NoteRequest, translations: Translations = Dep
существует (status_code=400), или произошла внутренняя ошибка (status_code=500). существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
""" """
try: try:
subnet_service = SubnetService() success, result = subnet_service.update_note(mac, request.note, translations)
success, error_message = subnet_service.update_note(mac, request.note)
if not success: if not success:
if "не найдено" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
elif "не существует" in error_message.lower(): elif result[0] == "note_not_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка '{note}' обновлена для устройства {mac}").format(note=request.note, mac=mac)}
except HTTPException as e: except HTTPException as e:
raise 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))) raise HTTPException(status_code=500, detail=translations.gettext("Ошибка обновления заметки: {error}").format(error=str(e)))
@router.delete("/devices/{mac}/note") @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-адресу. """Удаляет заметку для устройства по MAC-адресу.
Удаляет привязанную заметку для указанного устройства. Удаляет привязанную заметку для указанного устройства.
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
subnet_service: Сервис сканирования подсети.
translations: Объект переводов для текущего языка. translations: Объект переводов для текущего языка.
Returns: Returns:
@ -149,13 +153,14 @@ def delete_note(mac: str, translations: Translations = Depends(get_translations(
существует (status_code=400), или произошла внутренняя ошибка (status_code=500). существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
""" """
try: try:
subnet_service = SubnetService() success, result = subnet_service.delete_note(mac, translations)
success, error_message = subnet_service.delete_note(mac)
if not success: if not success:
if "не найдено" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
elif "не существует" in error_message.lower(): elif result[0] == "note_not_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка удалена для устройства {mac}").format(mac=mac)}
except HTTPException as e: except HTTPException as e:
raise e raise e
@ -182,12 +187,14 @@ def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations =
""" """
try: try:
wifi_service = WifiService() 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 not success:
if "не найдена" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
elif "уже существует" in error_message.lower(): elif result[0] == "note_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка '{note}' добавлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)}
except HTTPException as e: except HTTPException as e:
raise e raise e
@ -214,12 +221,14 @@ def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations
""" """
try: try:
wifi_service = WifiService() 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 not success:
if "не найдена" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
elif "не существует" in error_message.lower(): elif result[0] == "note_not_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка '{note}' обновлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)}
except HTTPException as e: except HTTPException as e:
raise e raise e
@ -245,12 +254,14 @@ def delete_wifi_note(ssid: str, translations: Translations = Depends(get_transla
""" """
try: try:
wifi_service = WifiService() wifi_service = WifiService()
success, error_message = wifi_service.delete_note(ssid) success, result = wifi_service.delete_note(ssid)
if not success: if not success:
if "не найдена" in error_message.lower(): if result[0] == "not_found":
raise HTTPException(status_code=404, detail=translations.gettext(error_message)) raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
elif "не существует" in error_message.lower(): elif result[0] == "note_not_exists":
raise HTTPException(status_code=400, detail=translations.gettext(error_message)) 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)} return {"message": translations.gettext("Заметка удалена для сети с SSID {ssid}").format(ssid=ssid)}
except HTTPException as e: except HTTPException as e:
raise e raise e

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-15 15:01+0500\n" "POT-Creation-Date: 2025-07-16 13:15+0500\n"
"PO-Revision-Date: 2025-07-15 15:02+0500\n" "PO-Revision-Date: 2025-07-16 13:15+0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n" "Language: en\n"
"Language-Team: en <LL@li.org>\n" "Language-Team: en <LL@li.org>\n"
@ -48,11 +48,11 @@ msgstr "The users.json file exists and contains users"
#: api/v1/auth.py:179 #: api/v1/auth.py:179
msgid "Файл users.json существует, но не содержит пользователей" 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 #: api/v1/auth.py:183
msgid "Ошибка при проверке файла" 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 #: api/v1/ip.py:36 api/v1/ip.py:73 api/v1/ip.py:110
#, python-brace-format #, python-brace-format
@ -96,145 +96,190 @@ msgstr "Failed to update container position: {error}"
msgid "Не удалось получить имя хоста" msgid "Не удалось получить имя хоста"
msgstr "Failed to retrieve hostname" msgstr "Failed to retrieve hostname"
#: api/v1/settings.py:33 #: api/v1/settings.py:37
msgid "Данные узлов подсети успешно очищены" #, python-brace-format
msgstr "Subnet node data successfully cleared" 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 "Не удалось очистить данные узлов подсети" msgid "Не удалось очистить данные узлов подсети"
msgstr "Failed to clear subnet node data" 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 #, python-brace-format
msgid "Ошибка очистки: {error}" msgid "Ошибка очистки: {error}"
msgstr "Clear error: {error}" msgstr "Clear error: {error}"
#: api/v1/settings.py:58 #: api/v1/settings.py:64
msgid "Данные точек доступа Wi-Fi успешно очищены" msgid "Данные точек доступа Wi-Fi успешно очищены"
msgstr "Wi-Fi access point data successfully cleared" msgstr "Wi-Fi access point data successfully cleared"
#: api/v1/settings.py:60 #: api/v1/settings.py:66
msgid "Не удалось очистить данные точек доступа Wi-Fi" msgid "Не удалось очистить данные точек доступа Wi-Fi"
msgstr "Failed to clear Wi-Fi access point data" msgstr "Failed to clear Wi-Fi access point data"
#: api/v1/settings.py:83 #: api/v1/settings.py:89
msgid "Все сетевые данные успешно очищены" msgid "Все сетевые данные успешно очищены"
msgstr "All network data successfully cleared" msgstr "All network data successfully cleared"
#: api/v1/settings.py:85 #: api/v1/settings.py:91
msgid "Не удалось очистить все сетевые данные" msgid "Не удалось очистить все сетевые данные"
msgstr "Failed to clear all network data" 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 "Файл не найден" msgid "Файл не найден"
msgstr "File not found" msgstr "File not found"
#: api/v1/settings.py:158 #: api/v1/settings.py:166
#, python-brace-format #, python-brace-format
msgid "Язык '{locale}' не поддерживается." msgid "Язык '{locale}' не поддерживается."
msgstr "Language '{locale}' is not supported." msgstr "Language '{locale}' is not supported."
#: api/v1/settings.py:164 #: api/v1/settings.py:172
#, python-brace-format #, python-brace-format
msgid "Язык '{locale}' не поддерживается (нет файлов переводов)." msgid "Язык '{locale}' не поддерживается (нет файлов переводов)."
msgstr "Language '{locale}' is not supported (no translation files)." msgstr "Language '{locale}' is not supported (no translation files)."
#: api/v1/settings.py:167 #: api/v1/settings.py:175
#, python-brace-format #, python-brace-format
msgid "Язык успешно установлен: {locale}" msgid "Язык успешно установлен: {locale}"
msgstr "Language successfully set: {locale}" msgstr "Language successfully set: {locale}"
#: api/v1/subnet.py:45 #: api/v1/subnet.py:44
#, python-brace-format #, python-brace-format
msgid "Ошибка сканирования: {error}" msgid "Ошибка сканирования подсети: {error}"
msgstr "Scan error: {error}" msgstr "Subnet scan error: {error}"
#: api/v1/subnet.py:68 #: api/v1/subnet.py:67
#, python-brace-format #, python-brace-format
msgid "Ошибка сканирования Wi-Fi: {error}" msgid "Ошибка сканирования Wi-Fi: {error}"
msgstr "Wi-Fi scan error: {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 #, python-brace-format
msgid "Заметка '{note}' добавлена для устройства {mac}" msgid "Заметка '{note}' добавлена для устройства {mac}"
msgstr "Note '{note}' added for device {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 #, python-brace-format
msgid "Ошибка добавления заметки: {error}" msgid "Ошибка добавления заметки: {error}"
msgstr "Error adding note: {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 #, python-brace-format
msgid "Заметка '{note}' обновлена для устройства {mac}" msgid "Заметка '{note}' обновлена для устройства {mac}"
msgstr "Note '{note}' updated for device {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 #, python-brace-format
msgid "Ошибка обновления заметки: {error}" msgid "Ошибка обновления заметки: {error}"
msgstr "Error updating note: {error}" msgstr "Error updating note: {error}"
#: api/v1/subnet.py:159 #: api/v1/subnet.py:164
#, python-brace-format #, python-brace-format
msgid "Заметка удалена для устройства {mac}" msgid "Заметка удалена для устройства {mac}"
msgstr "Note deleted for device {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 #, python-brace-format
msgid "Ошибка удаления заметки: {error}" msgid "Ошибка удаления заметки: {error}"
msgstr "Error deleting note: {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 #, python-brace-format
msgid "Заметка '{note}' добавлена для сети с SSID {ssid}" msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
msgstr "Note '{note}' added for network with 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 #, python-brace-format
msgid "Заметка '{note}' обновлена для сети с SSID {ssid}" msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
msgstr "Note '{note}' updated for network with SSID {ssid}" msgstr "Note '{note}' updated for network with SSID {ssid}"
#: api/v1/subnet.py:254 #: api/v1/subnet.py:265
#, python-brace-format #, python-brace-format
msgid "Заметка удалена для сети с SSID {ssid}" msgid "Заметка удалена для сети с SSID {ssid}"
msgstr "Note deleted for network with SSID {ssid}" msgstr "Note deleted for network with SSID {ssid}"
#: api/v1/subnet.py:282 #: api/v1/subnet.py:293
#, python-brace-format #, python-brace-format
msgid "Успешно подключено к {ssid}" msgid "Успешно подключено к {ssid}"
msgstr "Successfully connected to {ssid}" msgstr "Successfully connected to {ssid}"
#: api/v1/subnet.py:286 #: api/v1/subnet.py:297
#, python-brace-format #, python-brace-format
msgid "Ошибка подключения к Wi-Fi: {error}" msgid "Ошибка подключения к Wi-Fi: {error}"
msgstr "Wi-Fi connection error: {error}" msgstr "Wi-Fi connection error: {error}"
#: api/v1/subnet.py:309 #: api/v1/subnet.py:320
#, python-brace-format #, python-brace-format
msgid "Успешно отключено от {ssid}" msgid "Успешно отключено от {ssid}"
msgstr "Successfully disconnected from {ssid}" msgstr "Successfully disconnected from {ssid}"
#: api/v1/subnet.py:313 #: api/v1/subnet.py:324
#, python-brace-format #, python-brace-format
msgid "Ошибка отключения от Wi-Fi: {error}" msgid "Ошибка отключения от Wi-Fi: {error}"
msgstr "Wi-Fi disconnection error: {error}" msgstr "Wi-Fi disconnection error: {error}"
#: api/v1/subnet.py:337 #: api/v1/subnet.py:348
msgid "connected" msgid "connected"
msgstr "connected" msgstr "connected"
#: api/v1/subnet.py:341 #: api/v1/subnet.py:352
#, python-brace-format #, python-brace-format
msgid "Ошибка проверки статуса Wi-Fi: {error}" msgid "Ошибка проверки статуса Wi-Fi: {error}"
msgstr "Wi-Fi status check error: {error}" msgstr "Wi-Fi status check error: {error}"
#: api/v1/websocket.py:145 #: api/v1/websocket.py:145
msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети" 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 #: services/arp_service.py:102
msgid "Доступные устройства в сети:" msgid "Доступные устройства в сети:"
msgstr "Available devices in the network:" msgstr "Available devices on the network:"
#: services/arp_service.py:103 #: services/arp_service.py:103
msgid "IP" msgid "IP"
@ -276,10 +321,10 @@ msgstr "Successfully logged out"
msgid "Ошибка при выходе из системы" msgid "Ошибка при выходе из системы"
msgstr "Error during logout" 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 #: services/connect_service.py:154 services/wifi_service.py:227
msgid "Wi-Fi-интерфейс не доступен" msgid "Wi-Fi-интерфейс не доступен"
msgstr "Wi-Fi interface is not available" msgstr "Wi-Fi interface not available"
#: services/connect_service.py:74 #: services/connect_service.py:74
#, python-brace-format #, 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 #: services/connect_service.py:81 services/connect_service.py:92
#, python-brace-format #, python-brace-format
msgid "Ошибка подключения к {ssid}: Неверный пароль" 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 #: services/connect_service.py:83 services/connect_service.py:94
#, python-brace-format #, python-brace-format
@ -361,52 +406,42 @@ msgstr "Failed to execute hostname command: {error}"
msgid "Ошибка обработки вывода hostname: {error}" msgid "Ошибка обработки вывода hostname: {error}"
msgstr "Error processing hostname output: {error}" msgstr "Error processing hostname output: {error}"
#: services/settings_service.py:37 #: services/settings_service.py:55
#, python-brace-format #, python-brace-format
msgid "Ошибка очистки узлов подсети: {error}" 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 #, python-brace-format
msgid "Ошибка очистки точек доступа Wi-Fi: {error}" 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 #, python-brace-format
msgid "Ошибка очистки всех сетевых данных: {error}" 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 #: services/subnet_service.py:75 services/wifi_service.py:115
msgid "Неизвестный" msgid "Неизвестный"
msgstr "Unknown" msgstr "Unknown"
#: services/subnet_service.py:179 #: services/subnet_service.py:185
#, python-brace-format #, python-brace-format
msgid "Ошибка при сохранении устройств: {error}" msgid "Ошибка при сохранении устройств: {error}"
msgstr "Error saving devices: {error}" msgstr "Error saving devices: {error}"
#: services/subnet_service.py:374 services/subnet_service.py:391 #: services/subnet_service.py:268
#: services/subnet_service.py:408 msgid "Недопустимая подсеть по умолчанию"
#, python-brace-format msgstr "Invalid default subnet"
msgid "Устройство с MAC {mac} не найдено"
msgstr "Device with MAC {mac} not found"
#: services/subnet_service.py:377 #: services/subnet_service.py:288
#, python-brace-format msgid "В данной подсети не обнаружено устройств"
msgid "Заметка для устройства с MAC {mac} уже существует" msgstr "No devices found in this subnet"
msgstr "Note for device with MAC {mac} already exists"
#: services/subnet_service.py:384 services/subnet_service.py:401 #: services/subnet_service.py:345
#: services/subnet_service.py:418 services/wifi_service.py:319
#: services/wifi_service.py:337 services/wifi_service.py:355
#, python-brace-format #, python-brace-format
msgid "Внутренняя ошибка сервера: {error}" msgid "Ошибка сканирования: {error}"
msgstr "Internal server error: {error}" msgstr "Scan 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"
#: services/wifi_service.py:61 #: services/wifi_service.py:61
#, python-brace-format #, python-brace-format
@ -451,23 +486,8 @@ msgstr "5 GHz"
#: services/wifi_service.py:165 #: services/wifi_service.py:165
msgid "Интерфейс не определён" msgid "Интерфейс не определён"
msgstr "Interface not defined" msgstr "Interface not specified"
#: services/wifi_service.py:215 #: services/wifi_service.py:215
msgid "Таймаут при выполнении iw scan" msgid "Таймаут при выполнении iw scan"
msgstr "Timeout during 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"

493
SRV/locales/messages.pot Normal file
View file

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View file

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-15 15:01+0500\n" "POT-Creation-Date: 2025-07-16 13:15+0500\n"
"PO-Revision-Date: 2025-07-15 15:02+0500\n" "PO-Revision-Date: 2025-07-16 13:15+0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ru\n" "Language: ru\n"
"Language-Team: ru <LL@li.org>\n" "Language-Team: ru <LL@li.org>\n"
@ -99,134 +99,179 @@ msgstr "Не удалось обновить положение контейне
msgid "Не удалось получить имя хоста" msgid "Не удалось получить имя хоста"
msgstr "Не удалось получить имя хоста" msgstr "Не удалось получить имя хоста"
#: api/v1/settings.py:33 #: api/v1/settings.py:37
msgid "Данные узлов подсети успешно очищены" #, python-brace-format
msgstr "Данные узлов подсети успешно очищены" msgid "Данные узлов подсети {subnet} успешно очищены"
msgstr "Данные узлов подсети {subnet} успешно очищены"
#: api/v1/settings.py:35 #: api/v1/settings.py:39
msgid "Данные всех подсетей успешно очищены"
msgstr "Данные всех подсетей успешно очищены"
#: api/v1/settings.py:41
msgid "Не удалось очистить данные узлов подсети" msgid "Не удалось очистить данные узлов подсети"
msgstr "Не удалось очистить данные узлов подсети" 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 #, python-brace-format
msgid "Ошибка очистки: {error}" msgid "Ошибка очистки: {error}"
msgstr "Ошибка очистки: {error}" msgstr "Ошибка очистки: {error}"
#: api/v1/settings.py:58 #: api/v1/settings.py:64
msgid "Данные точек доступа Wi-Fi успешно очищены" msgid "Данные точек доступа Wi-Fi успешно очищены"
msgstr "Данные точек доступа Wi-Fi успешно очищены" msgstr "Данные точек доступа Wi-Fi успешно очищены"
#: api/v1/settings.py:60 #: api/v1/settings.py:66
msgid "Не удалось очистить данные точек доступа Wi-Fi" msgid "Не удалось очистить данные точек доступа Wi-Fi"
msgstr "Не удалось очистить данные точек доступа Wi-Fi" msgstr "Не удалось очистить данные точек доступа Wi-Fi"
#: api/v1/settings.py:83 #: api/v1/settings.py:89
msgid "Все сетевые данные успешно очищены" msgid "Все сетевые данные успешно очищены"
msgstr "Все сетевые данные успешно очищены" msgstr "Все сетевые данные успешно очищены"
#: api/v1/settings.py:85 #: api/v1/settings.py:91
msgid "Не удалось очистить все сетевые данные" msgid "Не удалось очистить все сетевые данные"
msgstr "Не удалось очистить все сетевые данные" msgstr "Не удалось очистить все сетевые данные"
#: api/v1/settings.py:109 api/v1/settings.py:133 #: api/v1/settings.py:117 api/v1/settings.py:141
msgid "Файл не найден" msgid "Файл не найден"
msgstr "Файл не найден" msgstr "Файл не найден"
#: api/v1/settings.py:158 #: api/v1/settings.py:166
#, python-brace-format #, python-brace-format
msgid "Язык '{locale}' не поддерживается." msgid "Язык '{locale}' не поддерживается."
msgstr "Язык '{locale}' не поддерживается." msgstr "Язык '{locale}' не поддерживается."
#: api/v1/settings.py:164 #: api/v1/settings.py:172
#, python-brace-format #, python-brace-format
msgid "Язык '{locale}' не поддерживается (нет файлов переводов)." msgid "Язык '{locale}' не поддерживается (нет файлов переводов)."
msgstr "Язык '{locale}' не поддерживается (нет файлов переводов)." msgstr "Язык '{locale}' не поддерживается (нет файлов переводов)."
#: api/v1/settings.py:167 #: api/v1/settings.py:175
#, python-brace-format #, python-brace-format
msgid "Язык успешно установлен: {locale}" msgid "Язык успешно установлен: {locale}"
msgstr "Язык успешно установлен: {locale}" msgstr "Язык успешно установлен: {locale}"
#: api/v1/subnet.py:45 #: api/v1/subnet.py:44
#, python-brace-format #, python-brace-format
msgid "Ошибка сканирования: {error}" msgid "Ошибка сканирования подсети: {error}"
msgstr "Ошибка сканирования: {error}" msgstr "Ошибка сканирования подсети: {error}"
#: api/v1/subnet.py:68 #: api/v1/subnet.py:67
#, python-brace-format #, python-brace-format
msgid "Ошибка сканирования Wi-Fi: {error}" msgid "Ошибка сканирования Wi-Fi: {error}"
msgstr "Ошибка сканирования 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 #, python-brace-format
msgid "Заметка '{note}' добавлена для устройства {mac}" msgid "Заметка '{note}' добавлена для устройства {mac}"
msgstr "Заметка '{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 #, python-brace-format
msgid "Ошибка добавления заметки: {error}" msgid "Ошибка добавления заметки: {error}"
msgstr "Ошибка добавления заметки: {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 #, python-brace-format
msgid "Заметка '{note}' обновлена для устройства {mac}" msgid "Заметка '{note}' обновлена для устройства {mac}"
msgstr "Заметка '{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 #, python-brace-format
msgid "Ошибка обновления заметки: {error}" msgid "Ошибка обновления заметки: {error}"
msgstr "Ошибка обновления заметки: {error}" msgstr "Ошибка обновления заметки: {error}"
#: api/v1/subnet.py:159 #: api/v1/subnet.py:164
#, python-brace-format #, python-brace-format
msgid "Заметка удалена для устройства {mac}" msgid "Заметка удалена для устройства {mac}"
msgstr "Заметка удалена для устройства {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 #, python-brace-format
msgid "Ошибка удаления заметки: {error}" msgid "Ошибка удаления заметки: {error}"
msgstr "Ошибка удаления заметки: {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 #, python-brace-format
msgid "Заметка '{note}' добавлена для сети с SSID {ssid}" msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
msgstr "Заметка '{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 #, python-brace-format
msgid "Заметка '{note}' обновлена для сети с SSID {ssid}" msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
msgstr "Заметка '{note}' обновлена для сети с SSID {ssid}" msgstr "Заметка '{note}' обновлена для сети с SSID {ssid}"
#: api/v1/subnet.py:254 #: api/v1/subnet.py:265
#, python-brace-format #, python-brace-format
msgid "Заметка удалена для сети с SSID {ssid}" msgid "Заметка удалена для сети с SSID {ssid}"
msgstr "Заметка удалена для сети с SSID {ssid}" msgstr "Заметка удалена для сети с SSID {ssid}"
#: api/v1/subnet.py:282 #: api/v1/subnet.py:293
#, python-brace-format #, python-brace-format
msgid "Успешно подключено к {ssid}" msgid "Успешно подключено к {ssid}"
msgstr "Успешно подключено к {ssid}" msgstr "Успешно подключено к {ssid}"
#: api/v1/subnet.py:286 #: api/v1/subnet.py:297
#, python-brace-format #, python-brace-format
msgid "Ошибка подключения к Wi-Fi: {error}" msgid "Ошибка подключения к Wi-Fi: {error}"
msgstr "Ошибка подключения к Wi-Fi: {error}" msgstr "Ошибка подключения к Wi-Fi: {error}"
#: api/v1/subnet.py:309 #: api/v1/subnet.py:320
#, python-brace-format #, python-brace-format
msgid "Успешно отключено от {ssid}" msgid "Успешно отключено от {ssid}"
msgstr "Успешно отключено от {ssid}" msgstr "Успешно отключено от {ssid}"
#: api/v1/subnet.py:313 #: api/v1/subnet.py:324
#, python-brace-format #, python-brace-format
msgid "Ошибка отключения от Wi-Fi: {error}" msgid "Ошибка отключения от Wi-Fi: {error}"
msgstr "Ошибка отключения от Wi-Fi: {error}" msgstr "Ошибка отключения от Wi-Fi: {error}"
#: api/v1/subnet.py:337 #: api/v1/subnet.py:348
msgid "connected" msgid "connected"
msgstr "connected" msgstr "connected"
#: api/v1/subnet.py:341 #: api/v1/subnet.py:352
#, python-brace-format #, python-brace-format
msgid "Ошибка проверки статуса Wi-Fi: {error}" msgid "Ошибка проверки статуса Wi-Fi: {error}"
msgstr "Ошибка проверки статуса Wi-Fi: {error}" msgstr "Ошибка проверки статуса Wi-Fi: {error}"
@ -366,17 +411,17 @@ msgstr "Не удалось выполнить команду hostname: {error}"
msgid "Ошибка обработки вывода hostname: {error}" msgid "Ошибка обработки вывода hostname: {error}"
msgstr "Ошибка обработки вывода hostname: {error}" msgstr "Ошибка обработки вывода hostname: {error}"
#: services/settings_service.py:37 #: services/settings_service.py:55
#, python-brace-format #, python-brace-format
msgid "Ошибка очистки узлов подсети: {error}" msgid "Ошибка очистки узлов подсети: {error}"
msgstr "Ошибка очистки узлов подсети: {error}" msgstr "Ошибка очистки узлов подсети: {error}"
#: services/settings_service.py:58 #: services/settings_service.py:76
#, python-brace-format #, python-brace-format
msgid "Ошибка очистки точек доступа Wi-Fi: {error}" msgid "Ошибка очистки точек доступа Wi-Fi: {error}"
msgstr "Ошибка очистки точек доступа Wi-Fi: {error}" msgstr "Ошибка очистки точек доступа Wi-Fi: {error}"
#: services/settings_service.py:84 #: services/settings_service.py:104
#, python-brace-format #, python-brace-format
msgid "Ошибка очистки всех сетевых данных: {error}" msgid "Ошибка очистки всех сетевых данных: {error}"
msgstr "Ошибка очистки всех сетевых данных: {error}" msgstr "Ошибка очистки всех сетевых данных: {error}"
@ -385,33 +430,23 @@ msgstr "Ошибка очистки всех сетевых данных: {error
msgid "Неизвестный" msgid "Неизвестный"
msgstr "Неизвестный" msgstr "Неизвестный"
#: services/subnet_service.py:179 #: services/subnet_service.py:185
#, python-brace-format #, python-brace-format
msgid "Ошибка при сохранении устройств: {error}" msgid "Ошибка при сохранении устройств: {error}"
msgstr "Ошибка при сохранении устройств: {error}" msgstr "Ошибка при сохранении устройств: {error}"
#: services/subnet_service.py:374 services/subnet_service.py:391 #: services/subnet_service.py:268
#: services/subnet_service.py:408 msgid "Недопустимая подсеть по умолчанию"
#, python-brace-format msgstr "Недопустимая подсеть по умолчанию"
msgid "Устройство с MAC {mac} не найдено"
msgstr "Устройство с MAC {mac} не найдено"
#: services/subnet_service.py:377 #: services/subnet_service.py:288
#, python-brace-format msgid "В данной подсети не обнаружено устройств"
msgid "Заметка для устройства с MAC {mac} уже существует" msgstr "В данной подсети не обнаружено устройств"
msgstr "Заметка для устройства с MAC {mac} уже существует"
#: services/subnet_service.py:384 services/subnet_service.py:401 #: services/subnet_service.py:345
#: services/subnet_service.py:418 services/wifi_service.py:319
#: services/wifi_service.py:337 services/wifi_service.py:355
#, python-brace-format #, python-brace-format
msgid "Внутренняя ошибка сервера: {error}" msgid "Ошибка сканирования: {error}"
msgstr "Внутренняя ошибка сервера: {error}" msgstr "Ошибка сканирования: {error}"
#: services/subnet_service.py:394 services/subnet_service.py:411
#, python-brace-format
msgid "Заметка для устройства с MAC {mac} не существует"
msgstr "Заметка для устройства с MAC {mac} не существует"
#: services/wifi_service.py:61 #: services/wifi_service.py:61
#, python-brace-format #, python-brace-format
@ -460,19 +495,4 @@ msgstr "Интерфейс не определён"
#: services/wifi_service.py:215 #: services/wifi_service.py:215
msgid "Таймаут при выполнении iw scan" msgid "Таймаут при выполнении iw scan"
msgstr "Таймаут при выполнении 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} не существует"

View file

@ -1,7 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional, List from typing import Optional, List
class Device(BaseModel): class Device(BaseModel):
""" """
Сканируемое устройство в подсети Сканируемое устройство в подсети
@ -26,17 +25,17 @@ class Device(BaseModel):
last_seen: Optional[str] = None last_seen: Optional[str] = None
note: Optional[str] = None note: Optional[str] = None
class SubnetScanResult(BaseModel): class SubnetScanResult(BaseModel):
""" """
Результат сканирования устройств в подсети Результат сканирования устройств в подсети
Атрибуты: Атрибуты:
devices (List[Device]): Список просканированных устройств devices (List[Device]): Список просканированных устройств
error (Optional[str]): Сообщение об ошибке, если сканирование не удалось
""" """
devices: List[Device] = [] devices: List[Device] = []
error: Optional[str] = None
class SubnetScanRequest(BaseModel): class SubnetScanRequest(BaseModel):
""" """

View file

@ -1,8 +1,11 @@
import json import json
import logging import logging
from utils.i18n import get_translations, _ from typing import Optional
from utils.i18n import get_translations
from babel.support import Translations from babel.support import Translations
from fastapi import Depends from fastapi import Depends
from pathlib import Path
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,12 +14,16 @@ class SettingsService:
"""Сервис для управления настройками и очистки данных сети.""" """Сервис для управления настройками и очистки данных сети."""
def __init__(self, translations: Translations = Depends(get_translations())): 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.wifi_file = "storage/wifi/wifi.json"
self.translations = translations self.translations = translations
def clear_subnet(self) -> bool: def clear_subnet(self, subnet: Optional[str] = None) -> bool:
"""Очищает данные узлов подсети в файле devices.json. """Очищает данные узлов подсети в указанном файле или во всех файлах.
Args:
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
Если None, очищает все подсети.
Returns: Returns:
bool: True, если очистка успешна. bool: True, если очистка успешна.
@ -25,12 +32,23 @@ class SettingsService:
Exception: Если произошла ошибка при записи в файл. Exception: Если произошла ошибка при записи в файл.
""" """
try: try:
with open(self.subnet_file, "w") as f: if subnet:
json.dump([], f) file_path = Path(self.subnet_base_path) / f"{subnet.replace('/', '_')}.json"
logger.info("Subnet nodes data successfully cleared") if file_path.exists():
return True 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: except FileNotFoundError:
logger.info("Devices file not found, clearing considered successful") logger.info("No subnet files found, clearing considered successful")
return True return True
except Exception as e: except Exception as e:
logger.error(f"Error clearing subnet nodes: {str(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))) raise Exception(self.translations.gettext("Ошибка очистки точек доступа Wi-Fi: {error}").format(error=str(e)))
def clear_all_data(self) -> bool: def clear_all_data(self) -> bool:
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах. """Очищает данные узлов всех подсетей и точек доступа Wi-Fi.
Returns: Returns:
bool: True, если очистка успешна. bool: True, если очистка успешна.
@ -68,10 +86,12 @@ class SettingsService:
""" """
try: try:
try: try:
with open(self.subnet_file, "w") as f: for file_path in Path(self.subnet_base_path).glob("*.json"):
json.dump([], f) with open(file_path, "w") as f:
json.dump([], f)
logger.info(f"Cleared subnet data in {file_path}")
except FileNotFoundError: except FileNotFoundError:
logger.info("Devices file not found, clearing considered successful") logger.info("No subnet files found, clearing considered successful")
try: try:
with open(self.wifi_file, "w") as f: with open(self.wifi_file, "w") as f:
json.dump([], f) json.dump([], f)

View file

@ -27,14 +27,53 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) 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: class NetworkScanner:
"""Класс для сканирования подсети с использованием ARP-запросов.""" """Класс для сканирования подсети с использованием ARP-запросов.
def __init__(self, interface: str, translations: Translations = Depends(get_translations())):
Выполняет отправку ARP-запросов для обнаружения устройств в указанной подсети.
"""
def __init__(self, interface: str, translations: Translations):
"""Инициализирует сканер сети.
Устанавливает интерфейс для сканирования и объект переводов.
Args:
interface (str): Имя сетевого интерфейса (например, "wlp0s20f3").
translations (Translations): Объект переводов для текущего языка.
"""
self.interface = interface self.interface = interface
conf.iface = interface conf.iface = interface
self.translations = translations self.translations = translations
def scan(self, subnet: str) -> List[Tuple[str, str]]: 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: try:
arp = ARP(pdst=subnet) arp = ARP(pdst=subnet)
ether = Ether(dst="ff:ff:ff:ff:ff:ff") ether = Ether(dst="ff:ff:ff:ff:ff:ff")
@ -46,17 +85,48 @@ class NetworkScanner:
return [] return []
class PortScanner: 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.interface = interface
self.translations = translations self.translations = translations
def scan(self, ip: str) -> List[str]: def scan(self, ip: str) -> List[str]:
"""Сканирует открытые порты на указанном IP-адресе.
Пока возвращает пустой список, так как функциональность не реализована.
Args:
ip (str): IP-адрес устройства для сканирования.
Returns:
List[str]: Список открытых портов (пока пустой).
"""
return [] return []
class DeviceInfoCollector: 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.mac_lookup = MacLookup()
self.nbtscan_cache = {} self.nbtscan_cache = {}
self.nbtscan_results = {} self.nbtscan_results = {}
@ -64,6 +134,16 @@ class DeviceInfoCollector:
@lru_cache(maxsize=1000) @lru_cache(maxsize=1000)
def get_vendor(self, mac: str) -> str: def get_vendor(self, mac: str) -> str:
"""Получает вендора устройства по MAC-адресу.
Использует кэширование для повышения производительности.
Args:
mac (str): MAC-адрес устройства.
Returns:
str: Название вендора или "Неизвестный" в случае ошибки.
"""
try: try:
return self.mac_lookup.lookup(mac) return self.mac_lookup.lookup(mac)
except Exception as e: except Exception as e:
@ -71,6 +151,13 @@ class DeviceInfoCollector:
return self.translations.gettext("Неизвестный") return self.translations.gettext("Неизвестный")
def scan_nbtscan(self, subnet: str) -> None: def scan_nbtscan(self, subnet: str) -> None:
"""Сканирует подсеть с помощью nbtscan для получения хостнеймов.
Сохраняет результаты в кэш и логирует процесс.
Args:
subnet (str): Подсеть в формате CIDR.
"""
if subnet not in self.nbtscan_results: if subnet not in self.nbtscan_results:
try: try:
logger.debug(f"Starting nbtscan for subnet {subnet}") logger.debug(f"Starting nbtscan for subnet {subnet}")
@ -97,6 +184,16 @@ class DeviceInfoCollector:
logger.error(f"NetBIOS: Error for subnet {subnet}: {str(e)}") logger.error(f"NetBIOS: Error for subnet {subnet}: {str(e)}")
def get_hostname(self, ip: str) -> Optional[str]: def get_hostname(self, ip: str) -> Optional[str]:
"""Получает хостнейм для указанного IP-адреса.
Пробует сначала DNS, затем NetBIOS.
Args:
ip (str): IP-адрес устройства.
Returns:
Optional[str]: Хостнейм или None, если не удалось определить.
"""
try: try:
socket.setdefaulttimeout(2) socket.setdefaulttimeout(2)
return socket.gethostbyaddr(ip)[0] return socket.gethostbyaddr(ip)[0]
@ -114,20 +211,56 @@ class DeviceInfoCollector:
return None return None
class DeviceStorage: 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: try:
if self.storage_file.exists(): if file_path.exists():
with open(self.storage_file, "r", encoding="utf-8") as f: with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f) data = json.load(f)
if not isinstance(data, dict) or "devices" not in data: 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 {} return {}
devices = {} devices = {}
for device_data in data.get("devices", []): for device_data in data.get("devices", []):
@ -137,13 +270,23 @@ class DeviceStorage:
return self._clean_old_devices(devices) return self._clean_old_devices(devices)
return {} return {}
except json.JSONDecodeError as e: 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 {} return {}
except Exception as e: except Exception as e:
logger.error(f"Error loading devices: {str(e)}") logger.error(f"Error loading devices for subnet {subnet}: {str(e)}")
return {} return {}
def _clean_old_devices(self, devices: Dict[str, Device]) -> Dict[str, Device]: 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() now = datetime.now()
filtered = {} filtered = {}
for mac, device in devices.items(): for mac, device in devices.items():
@ -160,28 +303,63 @@ class DeviceStorage:
filtered[mac] = device filtered[mac] = device
return filtered 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: try:
data = SubnetScanResult(devices=devices).model_dump() 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) 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: except Exception as e:
logger.error(f"Error saving devices: {str(e)}") logger.error(f"Error saving devices for subnet {subnet}: {str(e)}")
raise Exception(self.translations.gettext("Ошибка при сохранении устройств: {error}").format(error=str(e))) if self.translations:
raise Exception(self.translations.gettext("Ошибка при сохранении устройств: {error}").format(error=str(e)))
else:
raise Exception(f"Ошибка при сохранении устройств: {str(e)}")
class SubnetService: 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.interface = self._get_default_interface()
self.network_scanner = NetworkScanner(self.interface) self.network_scanner = NetworkScanner(self.interface, translations)
self.port_scanner = PortScanner(self.interface) self.port_scanner = PortScanner(self.interface, translations)
self.device_info = DeviceInfoCollector() self.device_info = DeviceInfoCollector(translations)
self.storage = DeviceStorage() self.storage = DeviceStorage(translations=translations)
self.translations = translations self.translations = translations
logger.info(f"Initialized SubnetService with interface: {self.interface}") logger.info(f"Initialized SubnetService with interface: {self.interface}")
def _get_default_interface(self) -> str: def _get_default_interface(self) -> str:
"""Определяет интерфейс по умолчанию.
Использует информацию о шлюзе или первый доступный интерфейс.
Returns:
str: Имя интерфейса (например, "wlp0s20f3" или "eth0").
Raises:
Exception: Если не удалось определить интерфейс.
"""
try: try:
gateways = netifaces.gateways() gateways = netifaces.gateways()
default_gateway = gateways.get("default", {}).get(netifaces.AF_INET) default_gateway = gateways.get("default", {}).get(netifaces.AF_INET)
@ -203,7 +381,16 @@ class SubnetService:
return "eth0" return "eth0"
def _get_default_subnet(self) -> str: def _get_default_subnet(self) -> str:
"""Автоматически определяет подсеть локального устройства.""" """Автоматически определяет подсеть локального устройства.
Использует информацию о текущем интерфейсе для определения подсети.
Returns:
str: Подсеть в формате CIDR (например, "10.1.1.0/24").
Raises:
Exception: Если не удалось определить подсеть.
"""
try: try:
interfaces = netifaces.interfaces() interfaces = netifaces.interfaces()
for iface in interfaces: for iface in interfaces:
@ -228,13 +415,18 @@ class SubnetService:
def scan_subnet(self, subnet: Optional[str] = None) -> SubnetScanResult: def scan_subnet(self, subnet: Optional[str] = None) -> SubnetScanResult:
"""Сканирует указанную подсеть и возвращает результаты. """Сканирует указанную подсеть и возвращает результаты.
Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные.
Args: Args:
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24"). subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
Если None или пустая строка, определяется автоматически. Если None или пустая строка, определяется автоматически.
Returns: Returns:
SubnetScanResult: Объект с результатами сканирования, содержащий список SubnetScanResult: Объект с результатами сканирования, содержащий список
устройств. устройств или сообщение об ошибке.
Raises:
Exception: Если произошла ошибка сканирования.
""" """
start_time = time.time() start_time = time.time()
devices = [] devices = []
@ -246,9 +438,9 @@ class SubnetService:
subnet = self._get_default_subnet() subnet = self._get_default_subnet()
if subnet == "0.0.0.0/0": if subnet == "0.0.0.0/0":
logger.warning("Default subnet is invalid, scan aborted") 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) local_device = self._get_local_device(subnet)
if local_device: if local_device:
@ -264,6 +456,10 @@ class SubnetService:
logger.info(f"Discovered devices: {len(discovered_devices)}") logger.info(f"Discovered devices: {len(discovered_devices)}")
self.device_info.scan_nbtscan(subnet) 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]: def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]:
ip, mac = ip_mac ip, mac = ip_mac
if local_device and ip == local_device.ip: if local_device and ip == local_device.ip:
@ -309,7 +505,7 @@ class SubnetService:
for device in devices: for device in devices:
if device.mac: if device.mac:
saved_devices[device.mac] = device saved_devices[device.mac] = device
self.storage.save_devices(list(saved_devices.values())) self.storage.save_devices(subnet, list(saved_devices.values()))
logger.info( logger.info(
f"Subnet scan {subnet} completed in {time.time() - start_time:.2f} seconds, " f"Subnet scan {subnet} completed in {time.time() - start_time:.2f} seconds, "
@ -319,9 +515,19 @@ class SubnetService:
except Exception as e: except Exception as e:
logger.error(f"Error scanning subnet {subnet}: {str(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]: def _get_local_device(self, subnet: str) -> Optional[Device]:
"""Определяет локальное устройство в указанной подсети.
Использует информацию об интерфейсе для обнаружения.
Args:
subnet (str): Подсеть в формате CIDR.
Returns:
Optional[Device]: Объект устройства или None, если не найдено.
"""
now = datetime.now().astimezone() now = datetime.now().astimezone()
formatted_now = now.strftime("%d-%m-%Y, %H:%M") formatted_now = now.strftime("%d-%m-%Y, %H:%M")
try: try:
@ -354,53 +560,115 @@ class SubnetService:
logger.warning(f"Failed to get local device: {str(e)}") logger.warning(f"Failed to get local device: {str(e)}")
return None 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: 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: if mac not in saved_devices:
logger.warning(f"Device with MAC {mac} not found") 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: if saved_devices[mac].note is not None:
logger.error(f"Note for device with MAC {mac} already exists") logger.error(f"Note for device with MAC {mac} already exists")
return False, self.translations.gettext("Заметка для устройства с MAC {mac} уже существует").format(mac=mac) return False, ("note_exists", {"mac": mac})
saved_devices[mac].note = note for subnet_file in self.storage.base_path.glob("*.json"):
self.storage.save_devices(list(saved_devices.values())) 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}") 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: except Exception as e:
logger.error(f"Error adding note for MAC {mac}: {str(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: 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: if mac not in saved_devices:
logger.warning(f"Device with MAC {mac} not found") 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: if saved_devices[mac].note is None:
logger.error(f"Note for device with MAC {mac} does not exist") logger.error(f"Note for device with MAC {mac} does not exist")
return False, self.translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac) return False, ("note_not_exists", {"mac": mac})
saved_devices[mac].note = note for subnet_file in self.storage.base_path.glob("*.json"):
self.storage.save_devices(list(saved_devices.values())) 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}") 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: except Exception as e:
logger.error(f"Error updating note for MAC {mac}: {str(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: 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: if mac not in saved_devices:
logger.warning(f"Device with MAC {mac} not found") 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: if saved_devices[mac].note is None:
logger.error(f"Note for device with MAC {mac} does not exist") logger.error(f"Note for device with MAC {mac} does not exist")
return False, self.translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac) return False, ("note_not_exists", {"mac": mac})
saved_devices[mac].note = None for subnet_file in self.storage.base_path.glob("*.json"):
self.storage.save_devices(list(saved_devices.values())) 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}") logger.info(f"Deleted note for device with MAC {mac}")
return True, None return True, ("success", {"mac": mac})
except Exception as e: except Exception as e:
logger.error(f"Error deleting note for MAC {mac}: {str(e)}") logger.error(f"Error deleting note for MAC {mac}: {str(e)}")
return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e)) return False, ("internal_error", {"error": str(e)})