Инициализация интернационализации. Перевод на en, ru для эндпоинтов api/v1

This commit is contained in:
parent 48e8551b0e
commit 9ac52c8a8b
16 changed files with 990 additions and 181 deletions

View file

@ -3,6 +3,8 @@ from fastapi.security import OAuth2PasswordBearer
from services.auth_service import AuthService from services.auth_service import AuthService
from models.auth import UserCreate, UserLogin, UserResponse, Token from models.auth import UserCreate, UserLogin, UserResponse, Token
from storage.json_storage import JsonStorage from storage.json_storage import JsonStorage
from utils.i18n import get_translations, _
from babel.support import Translations
import logging import logging
import os import os
@ -13,9 +15,8 @@ router = APIRouter()
auth_service = AuthService() auth_service = AuthService()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
@router.post("/register", response_model=UserResponse) @router.post("/register", response_model=UserResponse)
async def register(user: UserCreate): async def register(user: UserCreate, translations: Translations = Depends(get_translations("api_v1"))):
"""Регистрирует нового пользователя. """Регистрирует нового пользователя.
Создаёт нового пользователя на основе предоставленных данных и возвращает Создаёт нового пользователя на основе предоставленных данных и возвращает
@ -23,6 +24,7 @@ async def register(user: UserCreate):
Args: Args:
user (UserCreate): Объект с данными нового пользователя (username, password, is_admin). user (UserCreate): Объект с данными нового пользователя (username, password, is_admin).
translations: Объект переводов для текущего языка.
Returns: Returns:
UserResponse: Объект с данными зарегистрированного пользователя (id, username, is_admin). UserResponse: Объект с данными зарегистрированного пользователя (id, username, is_admin).
@ -38,12 +40,11 @@ async def register(user: UserCreate):
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
logger.error(f"Ошибка в register: {type(e).__name__} - {str(e)}") logger.error(f"Error in register: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Не удалось зарегистрировать пользователя") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось зарегистрировать пользователя"))
@router.post("/login", response_model=Token) @router.post("/login", response_model=Token)
async def login(user: UserLogin, request: Request): async def login(user: UserLogin, request: Request, translations: Translations = Depends(get_translations("api_v1"))):
"""Авторизует пользователя и создаёт сессию. """Авторизует пользователя и создаёт сессию.
Проверяет учетные данные пользователя и возвращает токен и session_id для Проверяет учетные данные пользователя и возвращает токен и session_id для
@ -52,6 +53,7 @@ async def login(user: UserLogin, request: Request):
Args: Args:
user (UserLogin): Объект с данными для входа (username, password). user (UserLogin): Объект с данными для входа (username, password).
request (Request): Объект запроса для получения client_id (IP-адрес клиента). request (Request): Объект запроса для получения client_id (IP-адрес клиента).
translations: Объект переводов для текущего языка.
Returns: Returns:
Token: Объект с access_token и session_id для авторизации. Token: Объект с access_token и session_id для авторизации.
@ -62,21 +64,18 @@ async def login(user: UserLogin, request: Request):
""" """
try: try:
client_id = request.client.host client_id = request.client.host
logger.debug(f"Попытка авторизации для client_id {client_id}") logger.debug(f"Попытка входа в систему клиента {client_id}")
result = auth_service.login(user, client_id) result = auth_service.login(user, client_id)
logger.info( logger.info(f"Успешная авторизация для клиента {client_id}, Идентификатор сессии: {result['session_id']}")
f"Успешная авторизация для client_id {client_id}, session_id: {result['session_id']}"
)
return result return result
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
logger.error(f"Ошибка в login: {type(e).__name__} - {str(e)}") logger.error(f"Ошибка при авторизации: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Не удалось авторизовать пользователя") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось авторизовать пользователя"))
@router.get("/me") @router.get("/me")
async def get_current_user(request: Request): async def get_current_user(request: Request, translations: Translations = Depends(get_translations("api_v1"))):
"""Возвращает данные текущего пользователя и токен. """Возвращает данные текущего пользователя и токен.
Проверяет активную сессию по client_id и возвращает информацию о пользователе Проверяет активную сессию по client_id и возвращает информацию о пользователе
@ -84,6 +83,7 @@ async def get_current_user(request: Request):
Args: Args:
request (Request): Объект запроса для получения client_id (IP-адрес клиента). request (Request): Объект запроса для получения client_id (IP-адрес клиента).
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключами "user" (UserResponse), "access_token" (str) и dict: Словарь с ключами "user" (UserResponse), "access_token" (str) и
@ -95,29 +95,28 @@ async def get_current_user(request: Request):
""" """
try: try:
client_id = request.client.host client_id = request.client.host
logger.debug(f"Запрос /me для client_id {client_id}") logger.debug(f"Запрос /me для клиента {client_id}")
result = auth_service.get_user_by_session(client_id) result = auth_service.get_user_by_session(client_id)
return { return {
"user": result["user"], "user": result["user"],
"access_token": result["access_token"], "access_token": result["access_token"],
"token_type": "bearer", "token_type": translations.gettext("bearer"),
} }
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
logger.error( logger.error(f"Ошибка при get_current_user: {type(e).__name__} - {str(e)}")
f"Ошибка в get_current_user: {type(e).__name__} - {str(e)}") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить данные пользователя"))
raise HTTPException(status_code=500, detail="Не удалось получить данные пользователя")
@router.get("/logout") @router.get("/logout")
async def logout(request: Request): async def logout(request: Request, translations: Translations = Depends(get_translations("api_v1"))):
"""Выход из системы, удаление сессии. """Выход из системы, удаление сессии.
Удаляет активную сессию пользователя по client_id. Удаляет активную сессию пользователя по client_id.
Args: Args:
request (Request): Объект запроса для получения client_id (IP-адрес клиента). request (Request): Объект запроса для получения client_id (IP-адрес клиента).
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Пустой словарь или сообщение об успешном выходе. dict: Пустой словарь или сообщение об успешном выходе.
@ -128,17 +127,16 @@ async def logout(request: Request):
""" """
try: try:
client_id = request.client.host client_id = request.client.host
logger.debug(f"Запрос logout для client_id {client_id}") logger.debug(f"Запрос на выход из системы для клиента {client_id}")
return auth_service.logout(client_id) return auth_service.logout(client_id)
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
logger.error(f"Ошибка в logout: {type(e).__name__} - {str(e)}") logger.error(f"Не удалось выйти из системы: {type(e).__name__} - {str(e)}")
raise HTTPException(status_code=500, detail="Не удалось выйти из системы") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось выйти из системы"))
@router.get("/check_json") @router.get("/check_json")
async def check_users_file(): async def check_users_file(translations: Translations = Depends(get_translations("api_v1"))):
"""Проверяет, существует ли файл users.json и содержит ли он пользователей. """Проверяет, существует ли файл users.json и содержит ли он пользователей.
Возвращает информацию о состоянии файла и наличии пользователей. Возвращает информацию о состоянии файла и наличии пользователей.
@ -161,31 +159,25 @@ async def check_users_file():
return { return {
"exists": False, "exists": False,
"has_users": False, "has_users": False,
"message": "Файл users.json не существует", "message": translations.gettext("Файл users.json не существует"),
} }
data = storage._read_data() data = storage._read_data()
has_users = len(data.get("users", [])) > 0 has_users = len(data.get("users", [])) > 0
if has_users: if has_users:
logger.info( logger.info(f"Файл {file_path} существует и содержит пользователей")
f"Файл {file_path} существует и содержит пользователей"
)
return { return {
"exists": True, "exists": True,
"has_users": True, "has_users": True,
"message": "Файл users.json существует и содержит пользователей", "message": translations.gettext("Файл users.json существует и содержит пользователей"),
} }
else: else:
logger.info( logger.info(f"Файл {file_path} существует, но не содержит пользователей")
f"Файл {file_path} существует, но не содержит пользователей"
)
return { return {
"exists": True, "exists": True,
"has_users": False, "has_users": False,
"message": "Файл users.json существует, но не содержит пользователей", "message": translations.gettext("Файл users.json существует, но не содержит пользователей"),
} }
except Exception as e: except Exception as e:
logger.error( logger.error(f"Ошибка при проверке файла: {type(e).__name__} - {str(e)}")
f"Ошибка при проверке файла users.json: {type(e).__name__} - {str(e)}" raise HTTPException(status_code=500, detail=translations.gettext("Ошибка при проверке файла"))
)
raise HTTPException(status_code=500, detail="Ошибка при проверке файла")

View file

@ -1,13 +1,14 @@
from typing import Dict, Any from typing import Dict, Any
from fastapi import APIRouter, Body, HTTPException from fastapi import APIRouter, Body, HTTPException, Depends
from services.ip_service import IPService from services.ip_service import IPService
from utils.i18n import get_translations, _
from babel.support import Translations
router = APIRouter() router = APIRouter()
ip_service = IPService() ip_service = IPService()
@router.post("/check_ip") @router.post("/check_ip")
async def check_ip_config(data: Dict[str, Any] = Body(...)): async def check_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations("api_v1"))):
"""Проверяет доступность IP-адреса перед его применением. """Проверяет доступность IP-адреса перед его применением.
Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес, Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
@ -16,6 +17,7 @@ async def check_ip_config(data: Dict[str, Any] = Body(...)):
Args: Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая data (Dict[str, Any]): Словарь с данными конфигурации, включая
"ipv4_updates" с полем "ipv4.address-data" для нового IP-адреса. "ipv4_updates" с полем "ipv4.address-data" для нового IP-адреса.
translations: Объект переводов для текущего языка.
Returns: Returns:
Dict[str, Any]: Словарь с ключами: Dict[str, Any]: Словарь с ключами:
@ -31,11 +33,11 @@ async def check_ip_config(data: Dict[str, Any] = Body(...)):
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
status_code=500, status_code=500,
detail=f"Ошибка проверки IP конфигурации: {str(e)}") detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
)
@router.post("/apply_ip") @router.post("/apply_ip")
async def apply_ip_config(data: Dict[str, Any] = Body(...)): async def apply_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations("api_v1"))):
"""Применяет проверенные IP-настройки к указанному сетевому профилю. """Применяет проверенные IP-настройки к указанному сетевому профилю.
Обновляет настройки соединения через NetworkManager, отключает и Обновляет настройки соединения через NetworkManager, отключает и
@ -44,6 +46,7 @@ async def apply_ip_config(data: Dict[str, Any] = Body(...)):
Args: Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая data (Dict[str, Any]): Словарь с данными конфигурации, включая
"connection_id" и "ipv4_updates" с обновлениями IP-настроек. "connection_id" и "ipv4_updates" с обновлениями IP-настроек.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключами: dict: Словарь с ключами:
@ -58,16 +61,20 @@ async def apply_ip_config(data: Dict[str, Any] = Body(...)):
await ip_service.apply_ip_config(data) await ip_service.apply_ip_config(data)
return { return {
"success": True, "success": True,
"message": f"Настройки сетевого профиля для {data['connection_id']} применены", "message": translations.gettext("Настройки сетевого профиля для {connection_id} применены").format(
connection_id=data["connection_id"]
),
} }
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=translations.gettext(str(e)))
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка проверки IP конфигурации: {str(e)}") raise HTTPException(
status_code=500,
detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
)
@router.post("/save_ip") @router.post("/save_ip")
async def save_ip_config(data: Dict[str, Any] = Body(...)): async def save_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations("api_v1"))):
"""Применяет проверенные IP-настройки для отката сетевого профиля. """Применяет проверенные IP-настройки для отката сетевого профиля.
Используется для сохранения и отката настроек сетевого профиля к предыдущему Используется для сохранения и отката настроек сетевого профиля к предыдущему
@ -76,6 +83,7 @@ async def save_ip_config(data: Dict[str, Any] = Body(...)):
Args: Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая data (Dict[str, Any]): Словарь с данными конфигурации, включая
"connection_id" и "ipv4_updates" с обновлениями IP-настроек. "connection_id" и "ipv4_updates" с обновлениями IP-настроек.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключами: dict: Словарь с ключами:
@ -90,9 +98,14 @@ async def save_ip_config(data: Dict[str, Any] = Body(...)):
await ip_service.apply_ip_config(data) await ip_service.apply_ip_config(data)
return { return {
"success": True, "success": True,
"message": f"Настройки сетевого профиля для {data['connection_id']} применены", "message": translations.gettext("Настройки сетевого профиля для {connection_id} применены").format(
connection_id=data["connection_id"]
),
} }
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=translations.gettext(str(e)))
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка проверки IP конфигурации: {str(e)}") raise HTTPException(
status_code=500,
detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
)

View file

@ -1,7 +1,6 @@
from fastapi import APIRouter, Body, HTTPException from fastapi import APIRouter, Body, HTTPException, Depends
import logging import logging
from typing import Dict, Tuple, List, Union from typing import Dict, Tuple, List, Union
from services.network_service import NetworkService from services.network_service import NetworkService
from models.network import ( from models.network import (
NetworkInterfacesContainer, NetworkInterfacesContainer,
@ -10,6 +9,8 @@ from models.network import (
NetworkInterface, NetworkInterface,
WiFiDevice, WiFiDevice,
) )
from utils.i18n import get_translations, _
from babel.support import Translations
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -17,7 +18,6 @@ logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
network_service = NetworkService() network_service = NetworkService()
class ContainerPositionManager: class ContainerPositionManager:
"""Класс для управления позициями и состоянием контейнеров. """Класс для управления позициями и состоянием контейнеров.
@ -44,11 +44,12 @@ class ContainerPositionManager:
}, },
} }
def get_position_and_state(self, container_name: str) -> Tuple[int, int, bool]: def get_position_and_state(self, container_name: str, translations: Translations = Depends(get_translations("api_v1"))) -> Tuple[int, int, bool]:
"""Получает текущие координаты и состояние указанного контейнера. """Получает текущие координаты и состояние указанного контейнера.
Args: Args:
container_name (str): Имя контейнера ("activation" или "deactivation"). container_name (str): Имя контейнера ("activation" или "deactivation").
translations: Объект переводов для текущего языка.
Returns: Returns:
Tuple[int, int, bool]: Кортеж с координатами (x, y) и состоянием (isOpen). Tuple[int, int, bool]: Кортеж с координатами (x, y) и состоянием (isOpen).
@ -58,7 +59,7 @@ class ContainerPositionManager:
""" """
container = self.positions.get(container_name) container = self.positions.get(container_name)
if not container: if not container:
raise ValueError(f"Неверное имя контейнера: {container_name}") raise ValueError(translations.gettext("Неверное имя контейнера: {container_name}").format(container_name=container_name))
return container["x"], container["y"], container["isOpen"] return container["x"], container["y"], container["isOpen"]
def update_position(self, container_id: int, x: int, y: int, isOpen: bool) -> None: def update_position(self, container_id: int, x: int, y: int, isOpen: bool) -> None:
@ -76,17 +77,18 @@ class ContainerPositionManager:
container_name = "activation" if container_id == 1 else "deactivation" container_name = "activation" if container_id == 1 else "deactivation"
self.positions[container_name] = {"x": x, "y": y, "isOpen": isOpen} self.positions[container_name] = {"x": x, "y": y, "isOpen": isOpen}
container_manager = ContainerPositionManager() container_manager = ContainerPositionManager()
async def get_network_data(translations: Translations = Depends(get_translations("api_v1"))) -> Dict[str, Dict]:
async def get_network_data() -> Dict[str, Dict]:
"""Возвращает текущие сетевые данные в виде словаря. """Возвращает текущие сетевые данные в виде словаря.
Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные, Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
и возвращает их в формате Activation и Deactivation с учётом позиций и состояний и возвращает их в формате Activation и Deactivation с учётом позиций и состояний
контейнеров. контейнеров.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
Dict[str, Dict]: Словарь с ключами "activation" и "deactivation", содержащими Dict[str, Dict]: Словарь с ключами "activation" и "deactivation", содержащими
данные о соответствующих контейнерах. данные о соответствующих контейнерах.
@ -94,7 +96,7 @@ async def get_network_data() -> Dict[str, Dict]:
Raises: Raises:
HTTPException: Если произошла ошибка при получении данных (status_code=500). HTTPException: Если произошла ошибка при получении данных (status_code=500).
""" """
logger.debug("Вызов get_network_data()") logger.debug("Calling get_network_data()")
try: try:
network_data = await network_service.get_network_interfaces_container() network_data = await network_service.get_network_interfaces_container()
@ -123,9 +125,8 @@ async def get_network_data() -> Dict[str, Dict]:
"deactivation": deactivation.model_dump(), "deactivation": deactivation.model_dump(),
} }
except Exception as e: except Exception as e:
logger.error(f"Ошибка в get_network_data: {e}") logger.error(f"Error in get_network_data: {e}")
raise HTTPException(status_code=500, detail="Не удалось получить сетевые данные") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить сетевые данные"))
def _split_interfaces( def _split_interfaces(
interfaces: List[Union[NetworkInterface, WiFiDevice]], interfaces: List[Union[NetworkInterface, WiFiDevice]],
@ -148,9 +149,8 @@ def _split_interfaces(
deactivation_interfaces.append(interface) deactivation_interfaces.append(interface)
return activation_interfaces, deactivation_interfaces return activation_interfaces, deactivation_interfaces
@router.post("/update_container_position") @router.post("/update_container_position")
async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...)): async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...), translations: Translations = Depends(get_translations("api_v1"))):
"""Обновляет позицию и состояние контейнера. """Обновляет позицию и состояние контейнера.
Принимает данные о новом положении и состоянии контейнера и обновляет их Принимает данные о новом положении и состоянии контейнера и обновляет их
@ -162,6 +162,7 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
- "container_x" (int): Новая координата X. - "container_x" (int): Новая координата X.
- "container_y" (int): Новая координата Y. - "container_y" (int): Новая координата Y.
- "isOpen" (bool): Новое состояние контейнера (открыт/закрыт). - "isOpen" (bool): Новое состояние контейнера (открыт/закрыт).
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключами: dict: Словарь с ключами:
@ -178,7 +179,7 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
] ]
if not all(key in data for key in required_keys): if not all(key in data for key in required_keys):
raise ValueError( raise ValueError(
"Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen" translations.gettext("Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen")
) )
container_id = data["container_id"] container_id = data["container_id"]
@ -190,24 +191,26 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
or not isinstance(container_x, int) or not isinstance(container_x, int)
or not isinstance(container_y, int) or not isinstance(container_y, int)
or not isinstance(is_open, bool)): or not isinstance(is_open, bool)):
raise ValueError("Неверный тип данных в запросе") raise ValueError(translations.gettext("Неверный тип данных в запросе"))
container_manager.update_position(container_id, container_x, container_y, is_open) container_manager.update_position(container_id, container_x, container_y, is_open)
return { return {
"success": True, "success": True,
"message": "Позиция и состояние контейнера обновлены" "message": translations.gettext("Позиция и состояние контейнера обновлены")
} }
except Exception as e: except Exception as e:
logger.error(f"Ошибка обновления позиции контейнера: {e}") logger.error(f"Error updating container position: {e}")
raise HTTPException(status_code=400, detail=f"Не удалось обновить положение контейнера: {str(e)}") raise HTTPException(status_code=400, detail=translations.gettext("Не удалось обновить положение контейнера: {error}").format(error=str(e)))
@router.get("/get_hostname") @router.get("/get_hostname")
async def get_hostname(): async def get_hostname(translations: Translations = Depends(get_translations("api_v1"))):
"""Возвращает имя хоста системы. """Возвращает имя хоста системы.
Выполняет запрос к сервису для получения имени хоста. Выполняет запрос к сервису для получения имени хоста.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключом "hostname" и значением имени хоста. dict: Словарь с ключом "hostname" и значением имени хоста.
@ -218,5 +221,5 @@ async def get_hostname():
hostname_info = await network_service.get_hostname() hostname_info = await network_service.get_hostname()
return hostname_info return hostname_info
except Exception as e: except Exception as e:
logger.error(f"Ошибка в get_hostname: {e}") logger.error(f"Error in get_hostname: {e}")
raise HTTPException(status_code=500, detail="Не удалось получить имя хоста") raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить имя хоста"))

View file

@ -1,11 +1,12 @@
# api/v1/settings.py
import os import os
import json import json
import logging import logging
import tempfile import tempfile
from fastapi import APIRouter, HTTPException, Body from fastapi import APIRouter, HTTPException, Body, Depends
from starlette.responses import FileResponse from starlette.responses import FileResponse
from services.settings_service import SettingsService from services.settings_service import SettingsService
from utils.i18n import get_translations, _
from babel.support import Translations
import shutil import shutil
# Настройка логирования # Настройка логирования
@ -16,11 +17,14 @@ router = APIRouter()
settings_service = SettingsService() settings_service = SettingsService()
@router.post("/clear_subnet") @router.post("/clear_subnet")
def clear_subnet(): def clear_subnet(translations: Translations = Depends(get_translations("api_v1"))):
"""Очищает данные узлов подсети, сохранённые в devices.json. """Очищает данные узлов подсети, сохранённые в devices.json.
Удаляет все записи об узлах подсети, оставляя файл пустым. Удаляет все записи об узлах подсети, оставляя файл пустым.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешной очистке. dict: Словарь с сообщением об успешной очистке.
@ -30,16 +34,22 @@ def clear_subnet():
try: try:
success = settings_service.clear_subnet() success = settings_service.clear_subnet()
if success: if success:
return {"message": "Данные узлов подсети успешно очищены"} return {"message": translations.gettext("Данные узлов подсети успешно очищены")}
else:
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные узлов подсети"))
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка очистки: {str(e)}") logger.error(f"Error clearing subnet data: {str(e)}")
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка очистки: {error}").format(error=str(e)))
@router.post("/clear_wifi") @router.post("/clear_wifi")
def clear_wifi(): def clear_wifi(translations: Translations = Depends(get_translations("api_v1"))):
"""Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json. """Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
Удаляет все записи о точках доступа, оставляя файл пустым. Удаляет все записи о точках доступа, оставляя файл пустым.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешной очистке. dict: Словарь с сообщением об успешной очистке.
@ -49,16 +59,22 @@ def clear_wifi():
try: try:
success = settings_service.clear_wifi() success = settings_service.clear_wifi()
if success: if success:
return {"message": "Данные точек доступа Wi-Fi успешно очищены"} return {"message": translations.gettext("Данные точек доступа Wi-Fi успешно очищены")}
else:
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные точек доступа Wi-Fi"))
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка очистки: {str(e)}") logger.error(f"Error clearing wifi data: {str(e)}")
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка очистки: {error}").format(error=str(e)))
@router.post("/clear_all_data") @router.post("/clear_all_data")
def clear_all_data(): def clear_all_data(translations: Translations = Depends(get_translations("api_v1"))):
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах. """Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
Удаляет все записи из devices.json и wifi.json. Удаляет все записи из devices.json и wifi.json.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешной очистке. dict: Словарь с сообщением об успешной очистке.
@ -68,18 +84,24 @@ def clear_all_data():
try: try:
success = settings_service.clear_all_data() success = settings_service.clear_all_data()
if success: if success:
return {"message": "Все сетевые данные успешно очищены"} return {"message": translations.gettext("Все сетевые данные успешно очищены")}
else:
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить все сетевые данные"))
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка очистки: {str(e)}") logger.error(f"Error clearing all data: {str(e)}")
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка очистки: {error}").format(error=str(e)))
DEVICE_PATH = "storage/devices/devices.json" 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(): async def export_devices(translations: Translations = Depends(get_translations("api_v1"))):
""" """
Экспортирует файл с данными устройств в формате JSON. Экспортирует файл с данными устройств в формате JSON.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
FileResponse: Файл devices.json для скачивания. FileResponse: Файл devices.json для скачивания.
@ -87,9 +109,9 @@ async def export_devices():
HTTPException: Если файл не найден (status_code=404). HTTPException: Если файл не найден (status_code=404).
""" """
if not os.path.exists(DEVICE_PATH): if not os.path.exists(DEVICE_PATH):
logger.warning(f"Файл для экспорта не найден: {DEVICE_PATH}") logger.warning(f"Export file not found: {DEVICE_PATH}")
raise HTTPException(status_code=404, detail="Файл не найден") raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден"))
logger.info(f"Экспорт файла: {DEVICE_PATH}") logger.info(f"Exporting file: {DEVICE_PATH}")
return FileResponse( return FileResponse(
path=DEVICE_PATH, path=DEVICE_PATH,
filename="devices.json", filename="devices.json",
@ -97,20 +119,23 @@ async def export_devices():
) )
@router.get("/export_wifi", response_class=FileResponse) @router.get("/export_wifi", response_class=FileResponse)
async def export_wifi(): async def export_wifi(translations: Translations = Depends(get_translations("api_v1"))):
""" """
Экспортирует файл с данными устройств в формате JSON. Экспортирует файл с данными устройств в формате JSON.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
FileResponse: Файл devices.json для скачивания. FileResponse: Файл wifi.json для скачивания.
Raises: Raises:
HTTPException: Если файл не найден (status_code=404). HTTPException: Если файл не найден (status_code=404).
""" """
if not os.path.exists(WIFI_PATH): if not os.path.exists(WIFI_PATH):
logger.warning(f"Файл для экспорта не найден: {WIFI_PATH}") logger.warning(f"Export file not found: {WIFI_PATH}")
raise HTTPException(status_code=404, detail="Файл не найден") raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден"))
logger.info(f"Экспорт файла: {WIFI_PATH}") logger.info(f"Exporting file: {WIFI_PATH}")
return FileResponse( return FileResponse(
path=WIFI_PATH, path=WIFI_PATH,
filename="wifi.json", filename="wifi.json",

View file

@ -1,24 +1,25 @@
from fastapi import APIRouter, HTTPException, Body 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
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 babel.support import Translations
router = APIRouter() router = APIRouter()
@router.post("/devices", response_model=SubnetScanResult) @router.post("/devices", response_model=SubnetScanResult)
def scan_subnet(request: SubnetScanRequest = Body(...)): def scan_subnet(request: SubnetScanRequest = Body(...), translations: Translations = Depends(get_translations("api_v1"))):
"""Сканирует локальную подсеть и возвращает данные об устройствах. """Сканирует локальную подсеть и возвращает данные об устройствах.
Выполняет сканирование указанной подсети для обнаружения активных устройств Выполняет сканирование указанной подсети для обнаружения активных устройств
с возможностью проверки указанных портов. с возможностью проверки указанных портов.
Args: Args:
subnet (str, optional): Адрес подсети в формате CIDR (например, "192.168.1.0/24"). request (SubnetScanRequest): Объект с полями subnet и ports.
По умолчанию "192.168.1.0/24". translations: Объект переводов для текущего языка.
ports (List[int], optional): Список портов для сканирования. По умолчанию None.
Returns: Returns:
SubnetScanResult: Объект с результатами сканирования (устройства и их данные). SubnetScanResult: Объект с результатами сканирования (устройства и их данные).
@ -32,16 +33,17 @@ def scan_subnet(request: SubnetScanRequest = Body(...)):
result = subnet_service.scan_subnet(subnet) result = subnet_service.scan_subnet(subnet)
return result return result
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка сканирования: {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): def scan_wifi(timeout: int = 30, translations: Translations = Depends(get_translations("api_v1"))):
"""Сканирует Wi-Fi точки доступа и возвращает данные о них. """Сканирует Wi-Fi точки доступа и возвращает данные о них.
Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом. Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
Args: Args:
timeout (int, optional): Таймаут сканирования в секундах. По умолчанию 30. timeout (int, optional): Таймаут сканирования в секундах. По умолчанию 30.
translations: Объект переводов для текущего языка.
Returns: Returns:
WifiScanResult: Объект с результатами сканирования (список Wi-Fi сетей). WifiScanResult: Объект с результатами сканирования (список Wi-Fi сетей).
@ -54,10 +56,10 @@ def scan_wifi(timeout: int = 30):
result = wifi_service.scan_wifi(timeout=timeout) result = wifi_service.scan_wifi(timeout=timeout)
return result return result
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка сканирования Wi-Fi: {str(e)}") raise HTTPException(status_code=500, detail=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): def add_note(mac: str, request: NoteRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Добавляет заметку для устройства по MAC-адресу. """Добавляет заметку для устройства по MAC-адресу.
Привязывает заметку к устройству, идентифицируемому по MAC-адресу. Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
@ -65,6 +67,7 @@ def add_note(mac: str, request: NoteRequest):
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для текста заметки. request (NoteRequest): Объект с полем "note" для текста заметки.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном добавлении заметки. dict: Словарь с сообщением об успешном добавлении заметки.
@ -77,18 +80,18 @@ def add_note(mac: str, request: NoteRequest):
subnet_service = SubnetService() subnet_service = SubnetService()
success, error_message = subnet_service.add_note(mac, request.note) success, error_message = subnet_service.add_note(mac, request.note)
if not success: if not success:
if "не найдено" in error_message: if "не найдено" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "уже существует" in error_message: elif "уже существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка '{request.note}' добавлена для устройства {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
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка добавления заметки: {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): def update_note(mac: str, request: NoteRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Обновляет заметку для устройства по MAC-адресу. """Обновляет заметку для устройства по MAC-адресу.
Изменяет существующую заметку, привязанную к устройству. Изменяет существующую заметку, привязанную к устройству.
@ -96,6 +99,7 @@ def update_note(mac: str, request: NoteRequest):
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для нового текста заметки. request (NoteRequest): Объект с полем "note" для нового текста заметки.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном обновлении заметки. dict: Словарь с сообщением об успешном обновлении заметки.
@ -108,24 +112,25 @@ def update_note(mac: str, request: NoteRequest):
subnet_service = SubnetService() subnet_service = SubnetService()
success, error_message = subnet_service.update_note(mac, request.note) success, error_message = subnet_service.update_note(mac, request.note)
if not success: if not success:
if "не найдено" in error_message: if "не найдено" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "не существует" in error_message: elif "не существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка '{request.note}' обновлена для устройства {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
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка обновления заметки: {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): def delete_note(mac: str, translations: Translations = Depends(get_translations("api_v1"))):
"""Удаляет заметку для устройства по MAC-адресу. """Удаляет заметку для устройства по MAC-адресу.
Удаляет привязанную заметку для указанного устройства. Удаляет привязанную заметку для указанного устройства.
Args: Args:
mac (str): MAC-адрес устройства. mac (str): MAC-адрес устройства.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном удалении заметки. dict: Словарь с сообщением об успешном удалении заметки.
@ -138,18 +143,18 @@ def delete_note(mac: str):
subnet_service = SubnetService() subnet_service = SubnetService()
success, error_message = subnet_service.delete_note(mac) success, error_message = subnet_service.delete_note(mac)
if not success: if not success:
if "не найдено" in error_message: if "не найдено" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "не существует" in error_message: elif "не существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка удалена для устройства {mac}"} return {"message": translations.gettext("Заметка удалена для устройства {mac}").format(mac=mac)}
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка удаления заметки: {str(e)}") raise HTTPException(status_code=500, detail=translations.gettext("Ошибка удаления заметки: {error}").format(error=str(e)))
@router.post("/wifi/{ssid}/note") @router.post("/wifi/{ssid}/note")
def add_wifi_note(ssid: str, request: NoteRequest): def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Добавляет заметку для сети Wi-Fi по SSID. """Добавляет заметку для сети Wi-Fi по SSID.
Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID. Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
@ -157,6 +162,7 @@ def add_wifi_note(ssid: str, request: NoteRequest):
Args: Args:
ssid (str): Идентификатор Wi-Fi сети. ssid (str): Идентификатор Wi-Fi сети.
request (NoteRequest): Объект с полем "note" для текста заметки. request (NoteRequest): Объект с полем "note" для текста заметки.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном добавлении заметки. dict: Словарь с сообщением об успешном добавлении заметки.
@ -169,18 +175,18 @@ def add_wifi_note(ssid: str, request: NoteRequest):
wifi_service = WifiService() wifi_service = WifiService()
success, error_message = wifi_service.add_note(ssid, request.note) success, error_message = wifi_service.add_note(ssid, request.note)
if not success: if not success:
if "не найдена" in error_message: if "не найдена" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "уже существует" in error_message: elif "уже существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка '{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
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка добавления заметки: {str(e)}") raise HTTPException(status_code=500, detail=translations.gettext("Ошибка добавления заметки: {error}").format(error=str(e)))
@router.put("/wifi/{ssid}/note") @router.put("/wifi/{ssid}/note")
def update_wifi_note(ssid: str, request: NoteRequest): def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Обновляет заметку для сети Wi-Fi по SSID. """Обновляет заметку для сети Wi-Fi по SSID.
Изменяет существующую заметку, привязанную к Wi-Fi сети. Изменяет существующую заметку, привязанную к Wi-Fi сети.
@ -188,6 +194,7 @@ def update_wifi_note(ssid: str, request: NoteRequest):
Args: Args:
ssid (str): Идентификатор Wi-Fi сети. ssid (str): Идентификатор Wi-Fi сети.
request (NoteRequest): Объект с полем "note" для нового текста заметки. request (NoteRequest): Объект с полем "note" для нового текста заметки.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном обновлении заметки. dict: Словарь с сообщением об успешном обновлении заметки.
@ -200,24 +207,25 @@ def update_wifi_note(ssid: str, request: NoteRequest):
wifi_service = WifiService() wifi_service = WifiService()
success, error_message = wifi_service.update_note(ssid, request.note) success, error_message = wifi_service.update_note(ssid, request.note)
if not success: if not success:
if "не найдена" in error_message: if "не найдена" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "не существует" in error_message: elif "не существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка '{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
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка обновления заметки: {str(e)}") raise HTTPException(status_code=500, detail=translations.gettext("Ошибка обновления заметки: {error}").format(error=str(e)))
@router.delete("/wifi/{ssid}/note") @router.delete("/wifi/{ssid}/note")
def delete_wifi_note(ssid: str): def delete_wifi_note(ssid: str, translations: Translations = Depends(get_translations("api_v1"))):
"""Удаляет заметку для сети Wi-Fi по SSID. """Удаляет заметку для сети Wi-Fi по SSID.
Удаляет привязанную заметку для указанной Wi-Fi сети. Удаляет привязанную заметку для указанной Wi-Fi сети.
Args: Args:
ssid (str): Идентификатор Wi-Fi сети. ssid (str): Идентификатор Wi-Fi сети.
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном удалении заметки. dict: Словарь с сообщением об успешном удалении заметки.
@ -230,18 +238,18 @@ def delete_wifi_note(ssid: str):
wifi_service = WifiService() wifi_service = WifiService()
success, error_message = wifi_service.delete_note(ssid) success, error_message = wifi_service.delete_note(ssid)
if not success: if not success:
if "не найдена" in error_message: if "не найдена" in error_message.lower():
raise HTTPException(status_code=404, detail=error_message) raise HTTPException(status_code=404, detail=translations.gettext(error_message))
elif "не существует" in error_message: elif "не существует" in error_message.lower():
raise HTTPException(status_code=400, detail=error_message) raise HTTPException(status_code=400, detail=translations.gettext(error_message))
return {"message": f"Заметка удалена для сети с SSID {ssid}"} return {"message": translations.gettext("Заметка удалена для сети с SSID {ssid}").format(ssid=ssid)}
except HTTPException as e: except HTTPException as e:
raise e raise e
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка удаления заметки: {str(e)}") raise HTTPException(status_code=500, detail=translations.gettext("Ошибка удаления заметки: {error}").format(error=str(e)))
@router.post("/wifi/connect") @router.post("/wifi/connect")
def connect_to_wifi(request: WifiConnectRequest): def connect_to_wifi(request: WifiConnectRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Подключается к Wi-Fi сети по SSID и паролю. """Подключается к Wi-Fi сети по SSID и паролю.
Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
@ -249,6 +257,7 @@ def connect_to_wifi(request: WifiConnectRequest):
Args: Args:
request (WifiConnectRequest): Объект с полями "ssid" и "password". request (WifiConnectRequest): Объект с полями "ssid" и "password".
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном подключении. dict: Словарь с сообщением об успешном подключении.
@ -256,20 +265,26 @@ def connect_to_wifi(request: WifiConnectRequest):
Raises: Raises:
HTTPException: Если подключение не удалось (status_code=400). HTTPException: Если подключение не удалось (status_code=400).
""" """
try:
wifi_service = WifiConnectionService() wifi_service = WifiConnectionService()
success, message = wifi_service.connect(request.ssid, request.password) success, message = wifi_service.connect(request.ssid, request.password)
if not success: if not success:
raise HTTPException(status_code=400, detail=message) raise HTTPException(status_code=400, detail=translations.gettext(message))
return {"message": f"Успешно подключено к {request.ssid}"} return {"message": translations.gettext("Успешно подключено к {ssid}").format(ssid=request.ssid)}
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка подключения к Wi-Fi: {error}").format(error=str(e)))
@router.post("/wifi/disconnect") @router.post("/wifi/disconnect")
def disconnect_from_wifi(request: WifiDisconnectRequest): def disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translations = Depends(get_translations("api_v1"))):
"""Отключается от Wi-Fi сети по указанному SSID. """Отключается от Wi-Fi сети по указанному SSID.
Выполняет отключение от указанной Wi-Fi сети. Выполняет отключение от указанной Wi-Fi сети.
Args: Args:
request (WifiDisconnectRequest): Объект с полем "ssid". request (WifiDisconnectRequest): Объект с полем "ssid".
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с сообщением об успешном отключении. dict: Словарь с сообщением об успешном отключении.
@ -277,18 +292,26 @@ def disconnect_from_wifi(request: WifiDisconnectRequest):
Raises: Raises:
HTTPException: Если отключение не удалось (status_code=400). HTTPException: Если отключение не удалось (status_code=400).
""" """
try:
wifi_service = WifiConnectionService() wifi_service = WifiConnectionService()
success, message = wifi_service.disconnect(request.ssid) success, message = wifi_service.disconnect(request.ssid)
if not success: if not success:
raise HTTPException(status_code=400, detail=message) raise HTTPException(status_code=400, detail=translations.gettext(message))
return {"message": message or f"Успешно отключено от {request.ssid}"} return {"message": translations.gettext(message or "Успешно отключено от {ssid}").format(ssid=request.ssid)}
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка отключения от Wi-Fi: {error}").format(error=str(e)))
@router.get("/wifi/status") @router.get("/wifi/status")
def get_wifi_status(): def get_wifi_status(translations: Translations = Depends(get_translations("api_v1"))):
"""Проверяет статус текущего подключения к Wi-Fi. """Проверяет статус текущего подключения к Wi-Fi.
Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно. Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
Args:
translations: Объект переводов для текущего языка.
Returns: Returns:
dict: Словарь с ключами: dict: Словарь с ключами:
- "status" (str): Состояние ("connected" или другое). - "status" (str): Состояние ("connected" или другое).
@ -297,8 +320,13 @@ def get_wifi_status():
Raises: Raises:
HTTPException: Если проверка статуса не удалась (status_code=400). HTTPException: Если проверка статуса не удалась (status_code=400).
""" """
try:
wifi_service = WifiConnectionService() wifi_service = WifiConnectionService()
success, message = wifi_service.get_status() success, message = wifi_service.get_status()
if not success: if not success:
raise HTTPException(status_code=400, detail=message) raise HTTPException(status_code=400, detail=translations.gettext(message))
return {"status": "connected", "ssid": message} return {"status": translations.gettext("connected"), "ssid": message}
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка проверки статуса Wi-Fi: {error}").format(error=str(e)))

View file

@ -2,8 +2,10 @@ import asyncio
import logging import logging
from typing import Set from typing import Set
from fastapi import APIRouter, WebSocket, WebSocketDisconnect from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from starlette.websockets import WebSocketState from starlette.websockets import WebSocketState
from utils.i18n import get_translations, _
from babel.support import Translations
from .network import update_container_position, get_network_data from .network import update_container_position, get_network_data
@ -70,15 +72,13 @@ class WebSocketManager:
except WebSocketDisconnect: except WebSocketDisconnect:
self.disconnect(connection) self.disconnect(connection)
except Exception as e: except Exception as e:
logger.error(f"Сообщение об ошибке трансляции: {e}") logger.error(f"Broadcast error: {e}")
self.disconnect(connection) self.disconnect(connection)
manager = WebSocketManager() manager = WebSocketManager()
@router.websocket("/api/v1") @router.websocket("/api/v1")
async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket, translations: Translations = Depends(get_translations("api_v1"))):
"""Обработчик WebSocket-соединения. """Обработчик WebSocket-соединения.
Управляет соединением клиента, принимает данные для обновления позиций Управляет соединением клиента, принимает данные для обновления позиций
@ -86,6 +86,7 @@ async def websocket_endpoint(websocket: WebSocket):
Args: Args:
websocket (WebSocket): Объект WebSocket для обработки соединения. websocket (WebSocket): Объект WebSocket для обработки соединения.
translations: Объект переводов для текущего языка.
Raises: Raises:
WebSocketDisconnect: Если клиент отключился. WebSocketDisconnect: Если клиент отключился.
@ -95,26 +96,25 @@ async def websocket_endpoint(websocket: WebSocket):
try: try:
while True: while True:
data = await websocket.receive_json() data = await websocket.receive_json()
logger.info(f"Полученные данные: {data}") logger.info(f"Received data: {data}")
if all(key in data for key in ["container_id", "container_x", "container_y"]): if all(key in data for key in ["container_id", "container_x", "container_y"]):
await update_container_position(data) await update_container_position(data)
network_data = await get_network_data() network_data = await get_network_data()
logger.info(f"Текущие сетевые данные: {network_data}") logger.info(f"Current network data: {network_data}")
await manager.broadcast(network_data) await manager.broadcast(network_data)
await asyncio.sleep(2) await asyncio.sleep(2)
except WebSocketDisconnect: except WebSocketDisconnect:
logger.info("Клиент отключен") logger.info("Client disconnected")
except Exception as e: except Exception as e:
logger.error(f"Ошибка в websocket_endpoint:{e}") logger.error(f"Error in websocket_endpoint: {e}")
finally: finally:
manager.disconnect(websocket) manager.disconnect(websocket)
if websocket.client_state != WebSocketState.DISCONNECTED: if websocket.client_state != WebSocketState.DISCONNECTED:
await websocket.close() await websocket.close()
@router.on_event("startup") @router.on_event("startup")
async def startup_event(): async def startup_event():
"""Запуск таска для трансляции сетевых данных при старте приложения. """Запуск таска для трансляции сетевых данных при старте приложения.
@ -126,13 +126,15 @@ async def startup_event():
""" """
asyncio.create_task(broadcast_data()) asyncio.create_task(broadcast_data())
async def broadcast_data(translations: Translations = Depends(get_translations("api_v1"))):
async def broadcast_data():
"""Таск для периодической трансляции сетевых данных. """Таск для периодической трансляции сетевых данных.
Выполняет сканирование сети и отправляет данные всем подключённым клиентам Выполняет сканирование сети и отправляет данные всем подключённым клиентам
с интервалом в 2 секунды. с интервалом в 2 секунды.
Args:
translations: Объект переводов для текущего языка.
Raises: Raises:
Exception: Если произошла ошибка при получении данных или трансляции. Exception: Если произошла ошибка при получении данных или трансляции.
""" """
@ -140,13 +142,11 @@ async def broadcast_data():
try: try:
network_data = await get_network_data() network_data = await get_network_data()
if "activation" not in network_data: if "activation" not in network_data:
logger.error( logger.error(translations.gettext("Ошибка: Ключ 'activation' отсутствует в полученных данных сети"))
"Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
)
await asyncio.sleep(2) await asyncio.sleep(2)
continue continue
await manager.broadcast(network_data) await manager.broadcast(network_data)
except Exception as e: except Exception as e:
logger.error(f"Ошибка в broadcast_data:{e}") logger.error(f"Error in broadcast_data: {e}")
await asyncio.sleep(2) await asyncio.sleep(2)

39
SRV/babel.cfg Normal file
View file

@ -0,0 +1,39 @@
[python: api/v1/**.py]
encoding = utf-8
extract_messages = _
domain = api_v1
[python: services/**.py]
encoding = utf-8
extract_messages = _
domain = services
[python: config/**.py]
encoding = utf-8
extract_messages = _
domain = config
[python: core/**.py]
encoding = utf-8
extract_messages = _
domain = core
[python: models/**.py]
encoding = utf-8
extract_messages = _
domain = models
[python: storage/**.py]
encoding = utf-8
extract_messages = _
domain = storage
[python: utils/**.py]
encoding = utf-8
extract_messages = _
domain = utils
[python: *.py]
encoding = utf-8
extract_messages = _
domain = main

218
SRV/locales/api_v1.pot Normal file
View file

@ -0,0 +1,218 @@
# 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-08 14:53+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
msgid "Данные узлов подсети успешно очищены"
msgstr ""
#: api/v1/settings.py:39
msgid "Не удалось очистить данные узлов подсети"
msgstr ""
#: api/v1/settings.py:42 api/v1/settings.py:67 api/v1/settings.py:92
#, python-brace-format
msgid "Ошибка очистки: {error}"
msgstr ""
#: api/v1/settings.py:62
msgid "Данные точек доступа Wi-Fi успешно очищены"
msgstr ""
#: api/v1/settings.py:64
msgid "Не удалось очистить данные точек доступа Wi-Fi"
msgstr ""
#: api/v1/settings.py:87
msgid "Все сетевые данные успешно очищены"
msgstr ""
#: api/v1/settings.py:89
msgid "Не удалось очистить все сетевые данные"
msgstr ""
#: api/v1/settings.py:113 api/v1/settings.py:137
msgid "Файл не найден"
msgstr ""
#: api/v1/subnet.py:36
#, python-brace-format
msgid "Ошибка сканирования: {error}"
msgstr ""
#: api/v1/subnet.py:59
#, python-brace-format
msgid "Ошибка сканирования Wi-Fi: {error}"
msgstr ""
#: api/v1/subnet.py:87
#, python-brace-format
msgid "Заметка '{note}' добавлена для устройства {mac}"
msgstr ""
#: api/v1/subnet.py:91 api/v1/subnet.py:186
#, python-brace-format
msgid "Ошибка добавления заметки: {error}"
msgstr ""
#: api/v1/subnet.py:119
#, python-brace-format
msgid "Заметка '{note}' обновлена для устройства {mac}"
msgstr ""
#: api/v1/subnet.py:123 api/v1/subnet.py:218
#, python-brace-format
msgid "Ошибка обновления заметки: {error}"
msgstr ""
#: api/v1/subnet.py:150
#, python-brace-format
msgid "Заметка удалена для устройства {mac}"
msgstr ""
#: api/v1/subnet.py:154 api/v1/subnet.py:249
#, python-brace-format
msgid "Ошибка удаления заметки: {error}"
msgstr ""
#: api/v1/subnet.py:182
#, python-brace-format
msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
msgstr ""
#: api/v1/subnet.py:214
#, python-brace-format
msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
msgstr ""
#: api/v1/subnet.py:245
#, python-brace-format
msgid "Заметка удалена для сети с SSID {ssid}"
msgstr ""
#: api/v1/subnet.py:273
#, python-brace-format
msgid "Успешно подключено к {ssid}"
msgstr ""
#: api/v1/subnet.py:277
#, python-brace-format
msgid "Ошибка подключения к Wi-Fi: {error}"
msgstr ""
#: api/v1/subnet.py:300
#, python-brace-format
msgid "Успешно отключено от {ssid}"
msgstr ""
#: api/v1/subnet.py:304
#, python-brace-format
msgid "Ошибка отключения от Wi-Fi: {error}"
msgstr ""
#: api/v1/subnet.py:328
msgid "connected"
msgstr ""
#: api/v1/subnet.py:332
#, python-brace-format
msgid "Ошибка проверки статуса Wi-Fi: {error}"
msgstr ""
#: api/v1/websocket.py:145
msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,219 @@
# English translations for PROJECT.
# Copyright (C) 2025 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-08 14:53+0500\n"
"PO-Revision-Date: 2025-07-08 14:54+0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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 "Failed to register user"
#: api/v1/auth.py:75
msgid "Не удалось авторизовать пользователя"
msgstr "Failed to authorize user"
#: api/v1/auth.py:103
msgid "bearer"
msgstr "bearer"
#: api/v1/auth.py:109
msgid "Не удалось получить данные пользователя"
msgstr "Failed to retrieve user data"
#: api/v1/auth.py:136
msgid "Не удалось выйти из системы"
msgstr "Failed to log out"
#: api/v1/auth.py:162
msgid "Файл users.json не существует"
msgstr "The users.json file does not exist"
#: api/v1/auth.py:172
msgid "Файл users.json существует и содержит пользователей"
msgstr "The users.json file exists and contains users"
#: api/v1/auth.py:179
msgid "Файл users.json существует, но не содержит пользователей"
msgstr "The users.json file exists but contains no users"
#: api/v1/auth.py:183
msgid "Ошибка при проверке файла"
msgstr "Error checking file"
#: api/v1/ip.py:36 api/v1/ip.py:73 api/v1/ip.py:110
#, python-brace-format
msgid "Ошибка проверки IP конфигурации: {error}"
msgstr "IP configuration check error: {error}"
#: api/v1/ip.py:64 api/v1/ip.py:101
#, python-brace-format
msgid "Настройки сетевого профиля для {connection_id} применены"
msgstr "Network profile settings for {connection_id} applied"
#: api/v1/network.py:62
#, python-brace-format
msgid "Неверное имя контейнера: {container_name}"
msgstr "Invalid container name: {container_name}"
#: api/v1/network.py:129
msgid "Не удалось получить сетевые данные"
msgstr "Failed to retrieve network data"
#: api/v1/network.py:182
msgid ""
"Отсутствуют обязательные поля: container_id, container_x, container_y, "
"isOpen"
msgstr "Missing required fields: container_id, container_x, container_y, isOpen"
#: api/v1/network.py:194
msgid "Неверный тип данных в запросе"
msgstr "Invalid data type in request"
#: api/v1/network.py:199
msgid "Позиция и состояние контейнера обновлены"
msgstr "Container position and state updated"
#: api/v1/network.py:203
#, python-brace-format
msgid "Не удалось обновить положение контейнера: {error}"
msgstr "Failed to update container position: {error}"
#: api/v1/network.py:225
msgid "Не удалось получить имя хоста"
msgstr "Failed to retrieve hostname"
#: api/v1/settings.py:37
msgid "Данные узлов подсети успешно очищены"
msgstr "Subnet node data cleared successfully"
#: api/v1/settings.py:39
msgid "Не удалось очистить данные узлов подсети"
msgstr "Failed to clear subnet node data"
#: api/v1/settings.py:42 api/v1/settings.py:67 api/v1/settings.py:92
#, python-brace-format
msgid "Ошибка очистки: {error}"
msgstr "Cleanup error: {error}"
#: api/v1/settings.py:62
msgid "Данные точек доступа Wi-Fi успешно очищены"
msgstr "Wi-Fi access point data cleared successfully"
#: api/v1/settings.py:64
msgid "Не удалось очистить данные точек доступа Wi-Fi"
msgstr "Failed to clear Wi-Fi access point data"
#: api/v1/settings.py:87
msgid "Все сетевые данные успешно очищены"
msgstr "All network data cleared successfully"
#: api/v1/settings.py:89
msgid "Не удалось очистить все сетевые данные"
msgstr "Failed to clear all network data"
#: api/v1/settings.py:113 api/v1/settings.py:137
msgid "Файл не найден"
msgstr "File not found"
#: api/v1/subnet.py:36
#, python-brace-format
msgid "Ошибка сканирования: {error}"
msgstr "Scan error: {error}"
#: api/v1/subnet.py:59
#, python-brace-format
msgid "Ошибка сканирования Wi-Fi: {error}"
msgstr "Wi-Fi scan error: {error}"
#: api/v1/subnet.py:87
#, python-brace-format
msgid "Заметка '{note}' добавлена для устройства {mac}"
msgstr "Note '{note}' added for device {mac}"
#: api/v1/subnet.py:91 api/v1/subnet.py:186
#, python-brace-format
msgid "Ошибка добавления заметки: {error}"
msgstr "Error adding note: {error}"
#: api/v1/subnet.py:119
#, python-brace-format
msgid "Заметка '{note}' обновлена для устройства {mac}"
msgstr "Note '{note}' updated for device {mac}"
#: api/v1/subnet.py:123 api/v1/subnet.py:218
#, python-brace-format
msgid "Ошибка обновления заметки: {error}"
msgstr "Error updating note: {error}"
#: api/v1/subnet.py:150
#, python-brace-format
msgid "Заметка удалена для устройства {mac}"
msgstr "Note deleted for device {mac}"
#: api/v1/subnet.py:154 api/v1/subnet.py:249
#, python-brace-format
msgid "Ошибка удаления заметки: {error}"
msgstr "Error deleting note: {error}"
#: api/v1/subnet.py:182
#, python-brace-format
msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
msgstr "Note '{note}' added for network with SSID {ssid}"
#: api/v1/subnet.py:214
#, python-brace-format
msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
msgstr "Note '{note}' updated for network with SSID {ssid}"
#: api/v1/subnet.py:245
#, python-brace-format
msgid "Заметка удалена для сети с SSID {ssid}"
msgstr "Note deleted for network with SSID {ssid}"
#: api/v1/subnet.py:273
#, python-brace-format
msgid "Успешно подключено к {ssid}"
msgstr "Successfully connected to {ssid}"
#: api/v1/subnet.py:277
#, python-brace-format
msgid "Ошибка подключения к Wi-Fi: {error}"
msgstr "Wi-Fi connection error: {error}"
#: api/v1/subnet.py:300
#, python-brace-format
msgid "Успешно отключено от {ssid}"
msgstr "Successfully disconnected from {ssid}"
#: api/v1/subnet.py:304
#, python-brace-format
msgid "Ошибка отключения от Wi-Fi: {error}"
msgstr "Wi-Fi disconnection error: {error}"
#: api/v1/subnet.py:328
msgid "connected"
msgstr "connected"
#: api/v1/subnet.py:332
#, python-brace-format
msgid "Ошибка проверки статуса Wi-Fi: {error}"
msgstr "Wi-Fi status check error: {error}"
#: api/v1/websocket.py:145
msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
msgstr "Error: 'activation' key missing in received network data"

Binary file not shown.

View file

@ -0,0 +1,220 @@
# Russian translations for PROJECT.
# Copyright (C) 2025 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-08 14:53+0500\n"
"PO-Revision-Date: 2025-07-08 14:54+0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ru\n"
"Language-Team: ru <LL@li.org>\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\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 "bearer"
#: api/v1/auth.py:109
msgid "Не удалось получить данные пользователя"
msgstr "Не удалось получить данные пользователя"
#: api/v1/auth.py:136
msgid "Не удалось выйти из системы"
msgstr "Не удалось выйти из системы"
#: api/v1/auth.py:162
msgid "Файл users.json не существует"
msgstr "Файл users.json не существует"
#: api/v1/auth.py:172
msgid "Файл users.json существует и содержит пользователей"
msgstr "Файл users.json существует и содержит пользователей"
#: api/v1/auth.py:179
msgid "Файл users.json существует, но не содержит пользователей"
msgstr "Файл users.json существует, но не содержит пользователей"
#: 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 "Ошибка проверки IP конфигурации: {error}"
#: api/v1/ip.py:64 api/v1/ip.py:101
#, python-brace-format
msgid "Настройки сетевого профиля для {connection_id} применены"
msgstr "Настройки сетевого профиля для {connection_id} применены"
#: api/v1/network.py:62
#, python-brace-format
msgid "Неверное имя контейнера: {container_name}"
msgstr "Неверное имя контейнера: {container_name}"
#: api/v1/network.py:129
msgid "Не удалось получить сетевые данные"
msgstr "Не удалось получить сетевые данные"
#: api/v1/network.py:182
msgid ""
"Отсутствуют обязательные поля: container_id, container_x, container_y, "
"isOpen"
msgstr ""
"Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen"
#: 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 "Не удалось обновить положение контейнера: {error}"
#: api/v1/network.py:225
msgid "Не удалось получить имя хоста"
msgstr "Не удалось получить имя хоста"
#: api/v1/settings.py:37
msgid "Данные узлов подсети успешно очищены"
msgstr "Данные узлов подсети успешно очищены"
#: api/v1/settings.py:39
msgid "Не удалось очистить данные узлов подсети"
msgstr "Не удалось очистить данные узлов подсети"
#: api/v1/settings.py:42 api/v1/settings.py:67 api/v1/settings.py:92
#, python-brace-format
msgid "Ошибка очистки: {error}"
msgstr "Ошибка очистки: {error}"
#: api/v1/settings.py:62
msgid "Данные точек доступа Wi-Fi успешно очищены"
msgstr "Данные точек доступа Wi-Fi успешно очищены"
#: api/v1/settings.py:64
msgid "Не удалось очистить данные точек доступа Wi-Fi"
msgstr "Не удалось очистить данные точек доступа Wi-Fi"
#: api/v1/settings.py:87
msgid "Все сетевые данные успешно очищены"
msgstr "Все сетевые данные успешно очищены"
#: api/v1/settings.py:89
msgid "Не удалось очистить все сетевые данные"
msgstr "Не удалось очистить все сетевые данные"
#: api/v1/settings.py:113 api/v1/settings.py:137
msgid "Файл не найден"
msgstr "Файл не найден"
#: api/v1/subnet.py:36
#, python-brace-format
msgid "Ошибка сканирования: {error}"
msgstr "Ошибка сканирования: {error}"
#: api/v1/subnet.py:59
#, python-brace-format
msgid "Ошибка сканирования Wi-Fi: {error}"
msgstr "Ошибка сканирования Wi-Fi: {error}"
#: api/v1/subnet.py:87
#, python-brace-format
msgid "Заметка '{note}' добавлена для устройства {mac}"
msgstr "Заметка '{note}' добавлена для устройства {mac}"
#: api/v1/subnet.py:91 api/v1/subnet.py:186
#, python-brace-format
msgid "Ошибка добавления заметки: {error}"
msgstr "Ошибка добавления заметки: {error}"
#: api/v1/subnet.py:119
#, python-brace-format
msgid "Заметка '{note}' обновлена для устройства {mac}"
msgstr "Заметка '{note}' обновлена для устройства {mac}"
#: api/v1/subnet.py:123 api/v1/subnet.py:218
#, python-brace-format
msgid "Ошибка обновления заметки: {error}"
msgstr "Ошибка обновления заметки: {error}"
#: api/v1/subnet.py:150
#, python-brace-format
msgid "Заметка удалена для устройства {mac}"
msgstr "Заметка удалена для устройства {mac}"
#: api/v1/subnet.py:154 api/v1/subnet.py:249
#, python-brace-format
msgid "Ошибка удаления заметки: {error}"
msgstr "Ошибка удаления заметки: {error}"
#: api/v1/subnet.py:182
#, python-brace-format
msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
msgstr "Заметка '{note}' добавлена для сети с SSID {ssid}"
#: api/v1/subnet.py:214
#, python-brace-format
msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
msgstr "Заметка '{note}' обновлена для сети с SSID {ssid}"
#: api/v1/subnet.py:245
#, python-brace-format
msgid "Заметка удалена для сети с SSID {ssid}"
msgstr "Заметка удалена для сети с SSID {ssid}"
#: api/v1/subnet.py:273
#, python-brace-format
msgid "Успешно подключено к {ssid}"
msgstr "Успешно подключено к {ssid}"
#: api/v1/subnet.py:277
#, python-brace-format
msgid "Ошибка подключения к Wi-Fi: {error}"
msgstr "Ошибка подключения к Wi-Fi: {error}"
#: api/v1/subnet.py:300
#, python-brace-format
msgid "Успешно отключено от {ssid}"
msgstr "Успешно отключено от {ssid}"
#: api/v1/subnet.py:304
#, python-brace-format
msgid "Ошибка отключения от Wi-Fi: {error}"
msgstr "Ошибка отключения от Wi-Fi: {error}"
#: api/v1/subnet.py:328
msgid "connected"
msgstr "connected"
#: api/v1/subnet.py:332
#, python-brace-format
msgid "Ошибка проверки статуса Wi-Fi: {error}"
msgstr "Ошибка проверки статуса Wi-Fi: {error}"
#: api/v1/websocket.py:145
msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
msgstr "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"

17
SRV/poetry.lock generated
View file

@ -201,6 +201,21 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
[[package]]
name = "babel"
version = "2.17.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
{file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
]
[package.extras]
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
[[package]] [[package]]
name = "backports-tarfile" name = "backports-tarfile"
version = "1.2.0" version = "1.2.0"
@ -3404,4 +3419,4 @@ cffi = ["cffi (>=1.11)"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "7139a70d2eb0c9e91e792256388371d98f71538cbbdd3722b0fc33d980f3a210" content-hash = "c65001dcf1f7f0b4f5a9c169a5d4546975f5d0e2d34b3024f1cb1ea9f7fe464b"

View file

@ -38,6 +38,7 @@ mac-vendor-lookup = "^0.1.12"
pywifi = "^1.1.12" pywifi = "^1.1.12"
manuf = "^1.1.5" manuf = "^1.1.5"
poetry-plugin-export = "^1.9.0" poetry-plugin-export = "^1.9.0"
babel = "^2.17.0"
[build-system] [build-system]

0
SRV/utils/__init__.py Normal file
View file

36
SRV/utils/i18n.py Normal file
View file

@ -0,0 +1,36 @@
from fastapi import Request
from babel.support import Translations
import logging
logger = logging.getLogger(__name__)
def get_translations(request: Request) -> Translations:
"""
Загружает переводы для текущего запроса на основе заголовка Accept-Language
или параметра запроса 'locale'. По умолчанию используется русский язык.
Args:
request (Request): Объект запроса FastAPI.
Returns:
Translations: Объект переводов для текущей локали.
"""
# Проверяем параметр запроса 'locale', если он есть
locale = request.query_params.get("locale")
if not locale:
# Если параметр отсутствует, используем заголовок Accept-Language
locale = request.headers.get("Accept-Language", "ru").split(",")[0]
try:
translations = Translations.load("locales", [locale], domain="messages")
logger.debug(f"Загружены переводы для локали: {locale}")
return translations
except Exception as e:
logger.error(f"Не удалось загрузить переводы для локали {locale}: {str(e)}")
# Возвращаем переводы по умолчанию (русский)
return Translations.load("locales", ["ru"], domain="messages")
# Определяем функцию `_` для удобства использования в коде
_ = lambda s: get_translations(Request).gettext(s)