repair translations
This commit is contained in:
parent
98581132dd
commit
84adc5d941
18 changed files with 304 additions and 122 deletions
|
|
@ -16,7 +16,7 @@ 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, translations: Translations = Depends(get_translations())):
|
async def register(user: UserCreate, translations: Translations = Depends(get_translations)):
|
||||||
"""Регистрирует нового пользователя.
|
"""Регистрирует нового пользователя.
|
||||||
|
|
||||||
Создаёт нового пользователя на основе предоставленных данных и возвращает
|
Создаёт нового пользователя на основе предоставленных данных и возвращает
|
||||||
|
|
@ -44,7 +44,7 @@ async def register(user: UserCreate, translations: Translations = Depends(get_tr
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось зарегистрировать пользователя"))
|
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, translations: Translations = Depends(get_translations())):
|
async def login(user: UserLogin, request: Request, translations: Translations = Depends(get_translations)):
|
||||||
"""Авторизует пользователя и создаёт сессию.
|
"""Авторизует пользователя и создаёт сессию.
|
||||||
|
|
||||||
Проверяет учетные данные пользователя и возвращает токен и session_id для
|
Проверяет учетные данные пользователя и возвращает токен и session_id для
|
||||||
|
|
@ -75,7 +75,7 @@ async def login(user: UserLogin, request: Request, translations: Translations =
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось авторизовать пользователя"))
|
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось авторизовать пользователя"))
|
||||||
|
|
||||||
@router.get("/me")
|
@router.get("/me")
|
||||||
async def get_current_user(request: Request, translations: Translations = Depends(get_translations())):
|
async def get_current_user(request: Request, translations: Translations = Depends(get_translations)):
|
||||||
"""Возвращает данные текущего пользователя и токен.
|
"""Возвращает данные текущего пользователя и токен.
|
||||||
|
|
||||||
Проверяет активную сессию по client_id и возвращает информацию о пользователе
|
Проверяет активную сессию по client_id и возвращает информацию о пользователе
|
||||||
|
|
@ -109,7 +109,7 @@ async def get_current_user(request: Request, translations: Translations = Depend
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить данные пользователя"))
|
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить данные пользователя"))
|
||||||
|
|
||||||
@router.get("/logout")
|
@router.get("/logout")
|
||||||
async def logout(request: Request, translations: Translations = Depends(get_translations())):
|
async def logout(request: Request, translations: Translations = Depends(get_translations)):
|
||||||
"""Выход из системы, удаление сессии.
|
"""Выход из системы, удаление сессии.
|
||||||
|
|
||||||
Удаляет активную сессию пользователя по client_id.
|
Удаляет активную сессию пользователя по client_id.
|
||||||
|
|
@ -136,7 +136,7 @@ async def logout(request: Request, translations: Translations = Depends(get_tran
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось выйти из системы"))
|
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось выйти из системы"))
|
||||||
|
|
||||||
@router.get("/check_json")
|
@router.get("/check_json")
|
||||||
async def check_users_file(translations: Translations = Depends(get_translations())):
|
async def check_users_file(translations: Translations = Depends(get_translations)):
|
||||||
"""Проверяет, существует ли файл users.json и содержит ли он пользователей.
|
"""Проверяет, существует ли файл users.json и содержит ли он пользователей.
|
||||||
|
|
||||||
Возвращает информацию о состоянии файла и наличии пользователей.
|
Возвращает информацию о состоянии файла и наличии пользователей.
|
||||||
|
|
@ -180,4 +180,4 @@ async def check_users_file(translations: Translations = Depends(get_translations
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при проверке файла: {type(e).__name__} - {str(e)}")
|
logger.error(f"Ошибка при проверке файла: {type(e).__name__} - {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка при проверке файла"))
|
raise HTTPException(status_code=500, detail=translations.gettext("Ошибка при проверке файла"))
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ 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(...), translations: Translations = Depends(get_translations())):
|
async def check_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations)):
|
||||||
"""Проверяет доступность IP-адреса перед его применением.
|
"""Проверяет доступность IP-адреса перед его применением.
|
||||||
|
|
||||||
Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
|
Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
|
||||||
|
|
@ -37,7 +37,7 @@ async def check_ip_config(data: Dict[str, Any] = Body(...), translations: Transl
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/apply_ip")
|
@router.post("/apply_ip")
|
||||||
async def apply_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations())):
|
async def apply_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations)):
|
||||||
"""Применяет проверенные IP-настройки к указанному сетевому профилю.
|
"""Применяет проверенные IP-настройки к указанному сетевому профилю.
|
||||||
|
|
||||||
Обновляет настройки соединения через NetworkManager, отключает и
|
Обновляет настройки соединения через NetworkManager, отключает и
|
||||||
|
|
@ -74,7 +74,7 @@ async def apply_ip_config(data: Dict[str, Any] = Body(...), translations: Transl
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/save_ip")
|
@router.post("/save_ip")
|
||||||
async def save_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations())):
|
async def save_ip_config(data: Dict[str, Any] = Body(...), translations: Translations = Depends(get_translations)):
|
||||||
"""Применяет проверенные IP-настройки для отката сетевого профиля.
|
"""Применяет проверенные IP-настройки для отката сетевого профиля.
|
||||||
|
|
||||||
Используется для сохранения и отката настроек сетевого профиля к предыдущему
|
Используется для сохранения и отката настроек сетевого профиля к предыдущему
|
||||||
|
|
@ -108,4 +108,4 @@ async def save_ip_config(data: Dict[str, Any] = Body(...), translations: Transla
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
|
detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from models.network import (
|
||||||
NetworkInterface,
|
NetworkInterface,
|
||||||
WiFiDevice,
|
WiFiDevice,
|
||||||
)
|
)
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import get_translations, _, _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -44,7 +44,7 @@ class ContainerPositionManager:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_position_and_state(self, container_name: str, translations: Translations = None) -> Tuple[int, int, bool]:
|
def get_position_and_state(self, container_name: str, translations: Translations) -> Tuple[int, int, bool]:
|
||||||
"""Получает текущие координаты и состояние указанного контейнера.
|
"""Получает текущие координаты и состояние указанного контейнера.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -57,7 +57,6 @@ class ContainerPositionManager:
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: Если указан неверный идентификатор контейнера.
|
ValueError: Если указан неверный идентификатор контейнера.
|
||||||
"""
|
"""
|
||||||
translations = translations or get_translations()()
|
|
||||||
container = self.positions.get(container_name)
|
container = self.positions.get(container_name)
|
||||||
if not container:
|
if not container:
|
||||||
raise ValueError(translations.gettext("Неверное имя контейнера: {container_name}").format(container_name=container_name))
|
raise ValueError(translations.gettext("Неверное имя контейнера: {container_name}").format(container_name=container_name))
|
||||||
|
|
@ -80,7 +79,7 @@ class ContainerPositionManager:
|
||||||
|
|
||||||
container_manager = ContainerPositionManager()
|
container_manager = ContainerPositionManager()
|
||||||
|
|
||||||
async def get_network_data(translations: Translations = None) -> Dict[str, Dict]:
|
async def get_network_data(translations: Translations = Depends(get_translations)) -> Dict[str, Dict]:
|
||||||
"""Возвращает текущие сетевые данные в виде словаря.
|
"""Возвращает текущие сетевые данные в виде словаря.
|
||||||
|
|
||||||
Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
|
Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
|
||||||
|
|
@ -97,15 +96,14 @@ async def get_network_data(translations: Translations = None) -> Dict[str, Dict]
|
||||||
Raises:
|
Raises:
|
||||||
HTTPException: Если произошла ошибка при получении данных (status_code=500).
|
HTTPException: Если произошла ошибка при получении данных (status_code=500).
|
||||||
"""
|
"""
|
||||||
translations = translations or get_translations()()
|
|
||||||
logger.debug("Calling 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()
|
||||||
|
|
||||||
activation_interfaces, deactivation_interfaces = _split_interfaces(network_data.interfaces)
|
activation_interfaces, deactivation_interfaces = _split_interfaces(network_data.interfaces)
|
||||||
|
|
||||||
activation_x, activation_y, activation_is_open = (container_manager.get_position_and_state("activation"))
|
activation_x, activation_y, activation_is_open = container_manager.get_position_and_state("activation", translations)
|
||||||
deactivation_x, deactivation_y, deactivation_is_open = (container_manager.get_position_and_state("deactivation"))
|
deactivation_x, deactivation_y, deactivation_is_open = container_manager.get_position_and_state("deactivation", translations)
|
||||||
|
|
||||||
activation = Activation(
|
activation = Activation(
|
||||||
id=1,
|
id=1,
|
||||||
|
|
@ -152,7 +150,7 @@ def _split_interfaces(
|
||||||
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(...), translations: Translations = None):
|
async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...), translations: Translations = Depends(get_translations)):
|
||||||
"""Обновляет позицию и состояние контейнера.
|
"""Обновляет позицию и состояние контейнера.
|
||||||
|
|
||||||
Принимает данные о новом положении и состоянии контейнера и обновляет их
|
Принимает данные о новом положении и состоянии контейнера и обновляет их
|
||||||
|
|
@ -175,7 +173,6 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
|
||||||
HTTPException: Если отсутствуют обязательные поля (status_code=400) или
|
HTTPException: Если отсутствуют обязательные поля (status_code=400) или
|
||||||
типы данных неверны.
|
типы данных неверны.
|
||||||
"""
|
"""
|
||||||
translations = translations or get_translations()()
|
|
||||||
try:
|
try:
|
||||||
required_keys = [
|
required_keys = [
|
||||||
"container_id", "container_x", "container_y", "isOpen"
|
"container_id", "container_x", "container_y", "isOpen"
|
||||||
|
|
@ -206,7 +203,7 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
|
||||||
raise HTTPException(status_code=400, detail=translations.gettext("Не удалось обновить положение контейнера: {error}").format(error=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(translations: Translations = Depends(get_translations())):
|
async def get_hostname(translations: Translations = Depends(get_translations)):
|
||||||
"""Возвращает имя хоста системы.
|
"""Возвращает имя хоста системы.
|
||||||
|
|
||||||
Выполняет запрос к сервису для получения имени хоста.
|
Выполняет запрос к сервису для получения имени хоста.
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ router = APIRouter()
|
||||||
settings_service = SettingsService()
|
settings_service = SettingsService()
|
||||||
|
|
||||||
@router.post("/clear_subnet")
|
@router.post("/clear_subnet")
|
||||||
def clear_subnet(subnet: Optional[str] = None, translations: Translations = Depends(get_translations())):
|
def clear_subnet(subnet: Optional[str] = None, translations: Translations = Depends(get_translations)):
|
||||||
"""Очищает данные узлов подсети, сохранённые в devices.json.
|
"""Очищает данные узлов подсети, сохранённые в devices.json.
|
||||||
|
|
||||||
Удаляет все записи об узлах подсети, оставляя файл пустым.
|
Удаляет все записи об узлах подсети, оставляя файл пустым.
|
||||||
|
|
@ -46,7 +46,7 @@ def clear_subnet(subnet: Optional[str] = None, translations: Translations = Depe
|
||||||
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.post("/clear_wifi")
|
@router.post("/clear_wifi")
|
||||||
def clear_wifi(translations: Translations = Depends(get_translations())):
|
def clear_wifi(translations: Translations = Depends(get_translations)):
|
||||||
"""Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
|
"""Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
|
||||||
|
|
||||||
Удаляет все записи о точках доступа, оставляя файл пустым.
|
Удаляет все записи о точках доступа, оставляя файл пустым.
|
||||||
|
|
@ -71,7 +71,7 @@ def clear_wifi(translations: Translations = Depends(get_translations())):
|
||||||
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.post("/clear_all_data")
|
@router.post("/clear_all_data")
|
||||||
def clear_all_data(translations: Translations = Depends(get_translations())):
|
def clear_all_data(translations: Translations = Depends(get_translations)):
|
||||||
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
|
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
|
||||||
|
|
||||||
Удаляет все записи из devices.json и wifi.json.
|
Удаляет все записи из devices.json и wifi.json.
|
||||||
|
|
@ -98,7 +98,7 @@ def clear_all_data(translations: Translations = Depends(get_translations())):
|
||||||
WIFI_PATH = "storage/wifi/wifi.json"
|
WIFI_PATH = "storage/wifi/wifi.json"
|
||||||
|
|
||||||
@router.post("/export_devices", response_class=FileResponse)
|
@router.post("/export_devices", response_class=FileResponse)
|
||||||
async def export_devices(request: SubnetScanRequest = Body(...), translations: Translations = Depends(get_translations())):
|
async def export_devices(request: SubnetScanRequest = Body(...), translations: Translations = Depends(get_translations)):
|
||||||
"""
|
"""
|
||||||
Экспортирует файл с данными устройств в формате JSON для указанной подсети.
|
Экспортирует файл с данными устройств в формате JSON для указанной подсети.
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ async def export_devices(request: SubnetScanRequest = Body(...), translations: T
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get("/export_wifi", response_class=FileResponse)
|
@router.get("/export_wifi", response_class=FileResponse)
|
||||||
async def export_wifi(translations: Translations = Depends(get_translations())):
|
async def export_wifi(translations: Translations = Depends(get_translations)):
|
||||||
"""
|
"""
|
||||||
Экспортирует файл с данными устройств в формате JSON.
|
Экспортирует файл с данными устройств в формате JSON.
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ async def export_wifi(translations: Translations = Depends(get_translations())):
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/set_language")
|
@router.post("/set_language")
|
||||||
def set_language(locale: str, response: Response, translations: Translations = Depends(get_translations())):
|
def set_language(locale: str, response: Response, translations: Translations = Depends(get_translations)):
|
||||||
"""
|
"""
|
||||||
Устанавливает куку locale для смены языка пользователя.
|
Устанавливает куку locale для смены языка пользователя.
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ def set_language(locale: str, response: Response, translations: Translations = D
|
||||||
return {"message": translations.gettext("Язык успешно установлен: {locale}").format(locale=locale)}
|
return {"message": translations.gettext("Язык успешно установлен: {locale}").format(locale=locale)}
|
||||||
|
|
||||||
@router.get("/get_system_language")
|
@router.get("/get_system_language")
|
||||||
async def get_system_language(request: Request, translations: Translations = Depends(get_translations())):
|
async def get_system_language(request: Request, translations: Translations = Depends(get_translations)):
|
||||||
"""Выводит текущий язык, установленный в системе на основе locale.
|
"""Выводит текущий язык, установленный в системе на основе locale.
|
||||||
|
|
||||||
Выполняет команду 'locale' в системе и извлекает параметры LANG
|
Выполняет команду 'locale' в системе и извлекает параметры LANG
|
||||||
|
|
@ -206,4 +206,4 @@ async def get_system_language(request: Request, translations: Translations = Dep
|
||||||
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось определить язык в системе"))
|
raise HTTPException(status_code=500, detail=translations.gettext("Не удалось определить язык в системе"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Unexpected error getting system language: {str(e)}")
|
logger.error(f"Unexpected error getting system language: {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)))
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ 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(...), subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())):
|
def scan_subnet(request: SubnetScanRequest = Body(...), subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
|
||||||
"""Сканирует локальную подсеть и возвращает данные об устройствах.
|
"""Сканирует локальную подсеть и возвращает данные об устройствах.
|
||||||
|
|
||||||
Выполняет сканирование указанной подсети для обнаружения активных устройств.
|
Выполняет сканирование указанной подсети для обнаружения активных устройств.
|
||||||
|
|
@ -44,7 +44,7 @@ def scan_subnet(request: SubnetScanRequest = Body(...), subnet_service: SubnetSe
|
||||||
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)):
|
||||||
"""Сканирует Wi-Fi точки доступа и возвращает данные о них.
|
"""Сканирует Wi-Fi точки доступа и возвращает данные о них.
|
||||||
|
|
||||||
Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
|
Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
|
||||||
|
|
@ -67,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, subnet_service: SubnetService = Depends(get_subnet_service), 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-адресу.
|
||||||
|
|
@ -101,7 +101,7 @@ def add_note(mac: str, request: NoteRequest, subnet_service: SubnetService = 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.put("/devices/{mac}/note")
|
@router.put("/devices/{mac}/note")
|
||||||
def update_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), 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-адресу.
|
||||||
|
|
||||||
Изменяет существующую заметку, привязанную к устройству.
|
Изменяет существующую заметку, привязанную к устройству.
|
||||||
|
|
@ -135,7 +135,7 @@ def update_note(mac: str, request: NoteRequest, subnet_service: SubnetService =
|
||||||
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, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations())):
|
def delete_note(mac: str, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
|
||||||
"""Удаляет заметку для устройства по MAC-адресу.
|
"""Удаляет заметку для устройства по MAC-адресу.
|
||||||
|
|
||||||
Удаляет привязанную заметку для указанного устройства.
|
Удаляет привязанную заметку для указанного устройства.
|
||||||
|
|
@ -168,7 +168,7 @@ def delete_note(mac: str, subnet_service: SubnetService = Depends(get_subnet_ser
|
||||||
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.post("/wifi/{ssid}/note")
|
@router.post("/wifi/{ssid}/note")
|
||||||
def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations())):
|
def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations)):
|
||||||
"""Добавляет заметку для сети Wi-Fi по SSID.
|
"""Добавляет заметку для сети Wi-Fi по SSID.
|
||||||
|
|
||||||
Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
|
Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
|
||||||
|
|
@ -202,7 +202,7 @@ def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations =
|
||||||
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("/wifi/{ssid}/note")
|
@router.put("/wifi/{ssid}/note")
|
||||||
def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations())):
|
def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations)):
|
||||||
"""Обновляет заметку для сети Wi-Fi по SSID.
|
"""Обновляет заметку для сети Wi-Fi по SSID.
|
||||||
|
|
||||||
Изменяет существующую заметку, привязанную к Wi-Fi сети.
|
Изменяет существующую заметку, привязанную к Wi-Fi сети.
|
||||||
|
|
@ -236,7 +236,7 @@ def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations
|
||||||
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("/wifi/{ssid}/note")
|
@router.delete("/wifi/{ssid}/note")
|
||||||
def delete_wifi_note(ssid: str, translations: Translations = Depends(get_translations())):
|
def delete_wifi_note(ssid: str, translations: Translations = Depends(get_translations)):
|
||||||
"""Удаляет заметку для сети Wi-Fi по SSID.
|
"""Удаляет заметку для сети Wi-Fi по SSID.
|
||||||
|
|
||||||
Удаляет привязанную заметку для указанной Wi-Fi сети.
|
Удаляет привязанную заметку для указанной Wi-Fi сети.
|
||||||
|
|
@ -269,7 +269,7 @@ def delete_wifi_note(ssid: str, translations: Translations = Depends(get_transla
|
||||||
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.post("/wifi/connect")
|
@router.post("/wifi/connect")
|
||||||
def connect_to_wifi(request: WifiConnectRequest, translations: Translations = Depends(get_translations())):
|
def connect_to_wifi(request: WifiConnectRequest, translations: Translations = Depends(get_translations)):
|
||||||
"""Подключается к Wi-Fi сети по SSID и паролю.
|
"""Подключается к Wi-Fi сети по SSID и паролю.
|
||||||
|
|
||||||
Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
|
Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
|
||||||
|
|
@ -297,7 +297,7 @@ def connect_to_wifi(request: WifiConnectRequest, translations: Translations = De
|
||||||
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("/wifi/disconnect")
|
@router.post("/wifi/disconnect")
|
||||||
def disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translations = Depends(get_translations())):
|
def disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translations = Depends(get_translations)):
|
||||||
"""Отключается от Wi-Fi сети по указанному SSID.
|
"""Отключается от Wi-Fi сети по указанному SSID.
|
||||||
|
|
||||||
Выполняет отключение от указанной Wi-Fi сети.
|
Выполняет отключение от указанной Wi-Fi сети.
|
||||||
|
|
@ -324,7 +324,7 @@ def disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translati
|
||||||
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.get("/wifi/status")
|
@router.get("/wifi/status")
|
||||||
def get_wifi_status(translations: Translations = Depends(get_translations())):
|
def get_wifi_status(translations: Translations = Depends(get_translations)):
|
||||||
"""Проверяет статус текущего подключения к Wi-Fi.
|
"""Проверяет статус текущего подключения к Wi-Fi.
|
||||||
|
|
||||||
Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
|
Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from typing import Set
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
|
||||||
from starlette.websockets import WebSocketState
|
from starlette.websockets import WebSocketState
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import get_translations_ws, _, _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
from .network import update_container_position, get_network_data
|
from .network import update_container_position, get_network_data
|
||||||
|
|
@ -78,7 +78,7 @@ class WebSocketManager:
|
||||||
manager = WebSocketManager()
|
manager = WebSocketManager()
|
||||||
|
|
||||||
@router.websocket("/api/v1")
|
@router.websocket("/api/v1")
|
||||||
async def websocket_endpoint(websocket: WebSocket, translations: Translations = Depends(get_translations())):
|
async def websocket_endpoint(websocket: WebSocket, translations: Translations = Depends(get_translations_ws)):
|
||||||
"""Обработчик WebSocket-соединения.
|
"""Обработчик WebSocket-соединения.
|
||||||
|
|
||||||
Управляет соединением клиента, принимает данные для обновления позиций
|
Управляет соединением клиента, принимает данные для обновления позиций
|
||||||
|
|
@ -99,10 +99,10 @@ async def websocket_endpoint(websocket: WebSocket, translations: Translations =
|
||||||
logger.info(f"Received data: {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, translations=translations)
|
||||||
|
|
||||||
# Внутренний вызов без DI, поэтому используем fallback-переводы
|
# Для внутреннего вызова используем переводы из WebSocket зависимостии
|
||||||
network_data = await get_network_data(translations=get_translations()(websocket))
|
network_data = await get_network_data(translations=translations)
|
||||||
logger.info(f"Current network data: {network_data}")
|
logger.info(f"Current network data: {network_data}")
|
||||||
|
|
||||||
await manager.broadcast(network_data)
|
await manager.broadcast(network_data)
|
||||||
|
|
@ -139,7 +139,7 @@ async def broadcast_data(translations: Translations = None):
|
||||||
Raises:
|
Raises:
|
||||||
Exception: Если произошла ошибка при получении данных или трансляции.
|
Exception: Если произошла ошибка при получении данных или трансляции.
|
||||||
"""
|
"""
|
||||||
translations = translations or get_translations()()
|
translations = translations or _load_translations("ru")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
network_data = await get_network_data(translations=translations)
|
network_data = await get_network_data(translations=translations)
|
||||||
|
|
|
||||||
130
SRV/logs/app.log
Normal file
130
SRV/logs/app.log
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
2025-11-17 12:33:54,542 - watchfiles.main - INFO - 4 changes detected
|
||||||
|
2025-11-17 12:33:54,553 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:54,553 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:54,553 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:54,554 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:54,554 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:54,554 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:54,894 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:55,245 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:55,595 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:55,946 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:56,297 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:56,594 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:56,594 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:56,594 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:56,594 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:56,595 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:56,595 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:56,648 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:56,999 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:57,350 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:57,701 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:58,052 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:58,403 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:58,638 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:58,638 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:58,639 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:58,639 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:58,639 - api.v1.network - ERROR - Error in get_network_data: name '_load_translations' is not defined
|
||||||
|
2025-11-17 12:33:58,639 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:33:58,754 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:59,105 - watchfiles.main - INFO - 2 changes detected
|
||||||
|
2025-11-17 12:33:59,571 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:33:59,922 - watchfiles.main - INFO - 4 changes detected
|
||||||
|
2025-11-17 12:34:00,273 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:00,624 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:00,975 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:01,326 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:01,677 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:02,028 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:02,379 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:02,730 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:03,081 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:03,432 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:03,782 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:04,133 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:04,484 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:34:04,835 - watchfiles.main - INFO - 1 change detected
|
||||||
|
2025-11-17 12:55:17,396 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:17,397 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:17,397 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:17,397 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:17,397 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:17,397 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:19,407 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:19,408 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:19,408 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:19,408 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:19,408 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:19,408 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:39,690 - api.v1.network - ERROR - Error in get_network_data: The name org.freedesktop.NetworkManager was not provided by any .service files
|
||||||
|
2025-11-17 12:55:39,690 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:39,691 - api.v1.network - ERROR - Error in get_network_data: The name org.freedesktop.NetworkManager was not provided by any .service files
|
||||||
|
2025-11-17 12:55:39,691 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:39,691 - api.v1.network - ERROR - Error in get_network_data: The name org.freedesktop.NetworkManager was not provided by any .service files
|
||||||
|
2025-11-17 12:55:39,691 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.network - ERROR - Error in get_network_data: 2 validation errors for WiFiDevice
|
||||||
|
mode.str
|
||||||
|
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/string_type
|
||||||
|
mode.int
|
||||||
|
Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
|
||||||
|
For further information visit https://errors.pydantic.dev/2.12/v/int_type
|
||||||
|
2025-11-17 12:55:41,721 - api.v1.websocket - ERROR - Error in broadcast_data: 500: Не удалось получить сетевые данные
|
||||||
|
2025-11-17 17:45:00,050 - asyncio - ERROR - Task was destroyed but it is pending!
|
||||||
|
task: <Task pending name='Task-4' coro=<broadcast_data() done, defined at /home/merelden/main/projects/gonets/SRV/api/v1/websocket.py:130> wait_for=<Future pending cb=[Task.task_wakeup()]>>
|
||||||
|
2025-11-17 17:45:00,050 - asyncio - ERROR - Task was destroyed but it is pending!
|
||||||
|
task: <Task pending name='Task-5' coro=<broadcast_data() done, defined at /home/merelden/main/projects/gonets/SRV/api/v1/websocket.py:130> wait_for=<Future pending cb=[Task.task_wakeup()]>>
|
||||||
18
SRV/main.py
18
SRV/main.py
|
|
@ -28,6 +28,24 @@ import uvicorn
|
||||||
from api.router import router
|
from api.router import router
|
||||||
from config.cors import CORSMiddlewareConfig
|
from config.cors import CORSMiddlewareConfig
|
||||||
from services.network_service import NetworkService
|
from services.network_service import NetworkService
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Логируем в файл logs/app.log и одновременно в stdout
|
||||||
|
LOG_DIR = Path(__file__).resolve().parent / "logs"
|
||||||
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
LOG_FILE = LOG_DIR / "app.log"
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.StreamHandler(),
|
||||||
|
logging.FileHandler(LOG_FILE, encoding="utf-8"),
|
||||||
|
],
|
||||||
|
force=True, # переопределяем базовую конфигурацию, заданную в импортируемых модулях
|
||||||
|
)
|
||||||
|
# Глушим шум от hot-reload watchdog
|
||||||
|
logging.getLogger("watchfiles").setLevel(logging.WARNING)
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="GONETS",
|
title="GONETS",
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ class WiFiDevice(NetworkInterface):
|
||||||
bitrate: Optional[int] = None
|
bitrate: Optional[int] = None
|
||||||
strength: Optional[int] = None
|
strength: Optional[int] = None
|
||||||
frequency: Optional[float] = None
|
frequency: Optional[float] = None
|
||||||
mode: Union[str, int] = None
|
mode: Optional[Union[str, int]] = None
|
||||||
|
|
||||||
|
|
||||||
class NetworkInterfacesContainer(BaseModel):
|
class NetworkInterfacesContainer(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import os
|
||||||
import logging
|
import logging
|
||||||
import netifaces
|
import netifaces
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import _, _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -54,7 +54,7 @@ class NetworkScanner:
|
||||||
|
|
||||||
Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес.
|
Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес.
|
||||||
"""
|
"""
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
self.interface = None
|
self.interface = None
|
||||||
self.ip_info = []
|
self.ip_info = []
|
||||||
self.networks = []
|
self.networks = []
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from typing import Optional
|
||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import _, _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -38,7 +38,7 @@ class AuthService:
|
||||||
def __init__(self, translations: Optional[Translations] = None):
|
def __init__(self, translations: Optional[Translations] = None):
|
||||||
self.storage = JsonStorage()
|
self.storage = JsonStorage()
|
||||||
self.session_storage = SessionStorage()
|
self.session_storage = SessionStorage()
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
|
|
||||||
def create_access_token(self, data: dict) -> str:
|
def create_access_token(self, data: dict) -> str:
|
||||||
"""Создаёт токен"""
|
"""Создаёт токен"""
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
from typing import Tuple, Optional
|
from typing import Tuple, Optional
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import _load_translations, _
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
|
@ -36,7 +36,7 @@ class WifiConnectionService:
|
||||||
Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя.
|
Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя.
|
||||||
"""
|
"""
|
||||||
self.interface = self._get_wifi_interface()
|
self.interface = self._get_wifi_interface()
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
|
|
||||||
def _get_wifi_interface(self) -> str:
|
def _get_wifi_interface(self) -> str:
|
||||||
"""Автоматически определяет Wi-Fi-интерфейс.
|
"""Автоматически определяет Wi-Fi-интерфейс.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from sdbus_async.networkmanager import (
|
||||||
import sdbus
|
import sdbus
|
||||||
from sdbus_async.networkmanager.settings import ConnectionProfile
|
from sdbus_async.networkmanager.settings import ConnectionProfile
|
||||||
from services.arp_service import NetworkScanner
|
from services.arp_service import NetworkScanner
|
||||||
from utils.i18n import get_translations
|
from utils.i18n import _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ class IPService:
|
||||||
self.bus = None
|
self.bus = None
|
||||||
self.bus = new_bus
|
self.bus = new_bus
|
||||||
|
|
||||||
async def check_ip_config(self, args: Dict[str, Any], translations: Translations = Depends(get_translations())) -> Dict[str, Any]:
|
async def check_ip_config(self, args: Dict[str, Any], translations: Translations = None) -> Dict[str, Any]:
|
||||||
"""Проверяет доступность IP-адреса перед его применением.
|
"""Проверяет доступность IP-адреса перед его применением.
|
||||||
|
|
||||||
Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
|
Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
|
||||||
|
|
@ -73,6 +73,7 @@ class IPService:
|
||||||
Raises:
|
Raises:
|
||||||
Exception: Если произошла ошибка при сканировании ARP.
|
Exception: Если произошла ошибка при сканировании ARP.
|
||||||
"""
|
"""
|
||||||
|
translations = translations or _load_translations("ru")
|
||||||
self._reset_bus()
|
self._reset_bus()
|
||||||
clients = self.arp_scanner.scan_network()
|
clients = self.arp_scanner.scan_network()
|
||||||
new_ip = args["ipv4_updates"]["ipv4.address-data"][0]["address"]
|
new_ip = args["ipv4_updates"]["ipv4.address-data"][0]["address"]
|
||||||
|
|
@ -86,7 +87,7 @@ class IPService:
|
||||||
"message": translations.gettext("Настройки сетевого профиля доступны для использования.")
|
"message": translations.gettext("Настройки сетевого профиля доступны для использования.")
|
||||||
}
|
}
|
||||||
|
|
||||||
async def apply_ip_config(self, args: Dict[str, Any], translations: Translations = Depends(get_translations())) -> None:
|
async def apply_ip_config(self, args: Dict[str, Any], translations: Translations = None) -> None:
|
||||||
"""Применяет проверенные настройки IP к указанному соединению.
|
"""Применяет проверенные настройки IP к указанному соединению.
|
||||||
|
|
||||||
Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
|
Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
|
||||||
|
|
@ -105,6 +106,7 @@ class IPService:
|
||||||
Note:
|
Note:
|
||||||
Требуются права для изменения настроек через NetworkManager.
|
Требуются права для изменения настроек через NetworkManager.
|
||||||
"""
|
"""
|
||||||
|
translations = translations or _load_translations("ru")
|
||||||
self._reset_bus()
|
self._reset_bus()
|
||||||
nm = NetworkManager()
|
nm = NetworkManager()
|
||||||
settings = NetworkManagerSettings()
|
settings = NetworkManagerSettings()
|
||||||
|
|
@ -140,4 +142,4 @@ class IPService:
|
||||||
print(f"Reconnected to {args['connection_id']}")
|
print(f"Reconnected to {args['connection_id']}")
|
||||||
return
|
return
|
||||||
|
|
||||||
raise RuntimeError(translations.gettext("Устройство для интерфейса {interface} не найдено").format(interface=interface_name))
|
raise RuntimeError(translations.gettext("Устройство для интерфейса {interface} не найдено").format(interface=interface_name))
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,20 @@ from models.network import (
|
||||||
WiFiDevice,
|
WiFiDevice,
|
||||||
)
|
)
|
||||||
import subprocess
|
import subprocess
|
||||||
from utils.i18n import get_translations, _
|
from utils.i18n import _, _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
|
def _extract_value(value):
|
||||||
|
"""Приводит значения D-Bus к примитивам Python."""
|
||||||
|
if isinstance(value, tuple) and len(value) == 2:
|
||||||
|
value = value[1]
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
try:
|
||||||
|
return value.decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
return value.decode("utf-8", errors="ignore")
|
||||||
|
return value
|
||||||
|
|
||||||
class NetworkService:
|
class NetworkService:
|
||||||
"""Сервис для взаимодействия с NetworkManager через D-Bus.
|
"""Сервис для взаимодействия с NetworkManager через D-Bus.
|
||||||
|
|
||||||
|
|
@ -51,7 +62,7 @@ class NetworkService:
|
||||||
self.system_bus = sd_bus_open_system()
|
self.system_bus = sd_bus_open_system()
|
||||||
self.nm = NetworkManager(self.system_bus)
|
self.nm = NetworkManager(self.system_bus)
|
||||||
self.nm_settings = NetworkManagerSettings(self.system_bus)
|
self.nm_settings = NetworkManagerSettings(self.system_bus)
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
|
|
||||||
async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str:
|
async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str:
|
||||||
"""Получает метод IP (например, "auto", "manual") для указанного устройства.
|
"""Получает метод IP (например, "auto", "manual") для указанного устройства.
|
||||||
|
|
@ -152,7 +163,7 @@ class NetworkService:
|
||||||
wifi_info = {
|
wifi_info = {
|
||||||
"ssid": None,
|
"ssid": None,
|
||||||
"bssid": None,
|
"bssid": None,
|
||||||
"bitrate": await wifi_device.bitrate,
|
"bitrate": _extract_value(await wifi_device.bitrate),
|
||||||
"strength": None,
|
"strength": None,
|
||||||
"frequency": None,
|
"frequency": None,
|
||||||
"mode": None,
|
"mode": None,
|
||||||
|
|
@ -160,11 +171,11 @@ class NetworkService:
|
||||||
if active_ap != "/":
|
if active_ap != "/":
|
||||||
ap = AccessPoint(active_ap, self.system_bus)
|
ap = AccessPoint(active_ap, self.system_bus)
|
||||||
wifi_info.update({
|
wifi_info.update({
|
||||||
"ssid": await ap.ssid,
|
"ssid": _extract_value(await ap.ssid),
|
||||||
"bssid": await ap.hw_address,
|
"bssid": _extract_value(await ap.hw_address),
|
||||||
"strength": await ap.strength,
|
"strength": _extract_value(await ap.strength),
|
||||||
"frequency": await ap.frequency,
|
"frequency": _extract_value(await ap.frequency),
|
||||||
"mode": await ap.mode,
|
"mode": _extract_value(await ap.mode),
|
||||||
})
|
})
|
||||||
return wifi_info
|
return wifi_info
|
||||||
|
|
||||||
|
|
@ -209,8 +220,8 @@ class NetworkService:
|
||||||
"device_mtu": str(await generic_device.mtu),
|
"device_mtu": str(await generic_device.mtu),
|
||||||
"ipv4": IPConfig(configurations=await self.get_ip_config(generic_device, "4")),
|
"ipv4": IPConfig(configurations=await self.get_ip_config(generic_device, "4")),
|
||||||
"ipv6": IPConfig(configurations=await self.get_ip_config(generic_device, "6")),
|
"ipv6": IPConfig(configurations=await self.get_ip_config(generic_device, "6")),
|
||||||
"x": initial_positions.get(interface_name, (100, 100))[0],
|
"container_x": initial_positions.get(interface_name, (100, 100))[0],
|
||||||
"y": initial_positions.get(interface_name, (100, 100))[1],
|
"container_y": initial_positions.get(interface_name, (100, 100))[1],
|
||||||
}
|
}
|
||||||
|
|
||||||
if device_type == "WIFI":
|
if device_type == "WIFI":
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from utils.i18n import get_translations
|
from utils.i18n import _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ class SettingsService:
|
||||||
def __init__(self, translations: Translations = None):
|
def __init__(self, translations: Translations = None):
|
||||||
self.subnet_base_path = "storage/subnets"
|
self.subnet_base_path = "storage/subnets"
|
||||||
self.wifi_file = "storage/wifi/wifi.json"
|
self.wifi_file = "storage/wifi/wifi.json"
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
|
|
||||||
def clear_subnet(self, subnet: Optional[str] = None) -> bool:
|
def clear_subnet(self, subnet: Optional[str] = None) -> bool:
|
||||||
"""Очищает данные узлов подсети в указанном файле или во всех файлах.
|
"""Очищает данные узлов подсети в указанном файле или во всех файлах.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_subnet_service(translations: Translations = Depends(get_translations())):
|
def get_subnet_service(translations: Translations = Depends(get_translations)):
|
||||||
"""Фабричная функция для создания экземпляра SubnetService.
|
"""Фабричная функция для создания экземпляра SubnetService.
|
||||||
|
|
||||||
Использует зависимости FastAPI для получения объекта переводов и возвращает
|
Использует зависимости FastAPI для получения объекта переводов и возвращает
|
||||||
|
|
@ -724,4 +724,4 @@ class SubnetService:
|
||||||
return True, ("success", {"mac": mac})
|
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, ("internal_error", {"error": str(e)})
|
return False, ("internal_error", {"error": str(e)})
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import pywifi
|
||||||
from models.wifi import WifiAccessPoint, WifiScanResult
|
from models.wifi import WifiAccessPoint, WifiScanResult
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
from utils.i18n import get_translations
|
from utils.i18n import _load_translations
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
|
|
@ -36,7 +36,7 @@ class WifiStorage:
|
||||||
def __init__(self, storage_path: str = "storage/wifi/wifi.json", translations: Translations = None):
|
def __init__(self, storage_path: str = "storage/wifi/wifi.json", translations: Translations = None):
|
||||||
self.storage_file = Path(storage_path)
|
self.storage_file = Path(storage_path)
|
||||||
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
|
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
self.translations = translations or get_translations()()
|
self.translations = translations or _load_translations("ru")
|
||||||
logger.info(f"Wi-Fi storage initialized at: {self.storage_file.absolute()}")
|
logger.info(f"Wi-Fi storage initialized at: {self.storage_file.absolute()}")
|
||||||
|
|
||||||
def load_notes(self) -> Dict[str, Optional[str]]:
|
def load_notes(self) -> Dict[str, Optional[str]]:
|
||||||
|
|
@ -88,10 +88,10 @@ class WifiService:
|
||||||
self.mac_lookup = MacLookup()
|
self.mac_lookup = MacLookup()
|
||||||
self.wifi = pywifi.PyWiFi()
|
self.wifi = pywifi.PyWiFi()
|
||||||
self.interface = self._get_wifi_interface()
|
self.interface = self._get_wifi_interface()
|
||||||
validated_translations = translations or get_translations()()
|
validated_translations = translations or _load_translations("ru")
|
||||||
if not hasattr(validated_translations, "gettext"):
|
if not hasattr(validated_translations, "gettext"):
|
||||||
logger.warning("Translations object does not support gettext, using fallback")
|
logger.warning("Translations object does not support gettext, using fallback")
|
||||||
validated_translations = get_translations()()
|
validated_translations = _load_translations("ru")
|
||||||
self.translations = validated_translations
|
self.translations = validated_translations
|
||||||
self.storage = WifiStorage()
|
self.storage = WifiStorage()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,65 +2,90 @@
|
||||||
Утилиты локализации и зависимости переводов для FastAPI.
|
Утилиты локализации и зависимости переводов для FastAPI.
|
||||||
|
|
||||||
Содержимое модуля:
|
Содержимое модуля:
|
||||||
- `get_translations()`: фабрика зависимости FastAPI, считывающая локаль из
|
- `get_translations`: зависимость FastAPI, считывающая локаль из query/cookie/header.
|
||||||
query-параметра `locale`, cookie `locale` или заголовка `Accept-Language`
|
- `get_translations_ws`: аналог для WebSocket.
|
||||||
(по приоритету), с дефолтом `ru`. Возвращает функцию-зависимость, которая
|
- `_load_translations`: низкоуровневая загрузка словаря переводов с fallback.
|
||||||
подгружает `babel.support.Translations` из каталога `SRV/locales`.
|
- `_()`: обёртка для gettext с дефолтом ru.
|
||||||
- `_()`: удобная обёртка для получения перевода строки с использованием
|
|
||||||
текущей локали (или дефолтного `ru`, если контекст запроса не передан).
|
|
||||||
|
|
||||||
Особенности:
|
|
||||||
- При отсутствии словаря перевода для выбранной локали выполняется fallback на
|
|
||||||
русскую локаль, что гарантирует наличие строк интерфейса.
|
|
||||||
- Локаль определяется только по первому языку из `Accept-Language` (без
|
|
||||||
региональных суффиксов) для упрощения.
|
|
||||||
- Модуль ведёт отладочное логирование выбора и загрузки локали.
|
|
||||||
"""
|
"""
|
||||||
from fastapi import Request, WebSocket
|
from fastapi import Request, WebSocket
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
LOCALES_DIR = Path(__file__).resolve().parent.parent / "locales"
|
|
||||||
|
def _candidate_locale_dirs() -> Iterable[Path]:
|
||||||
|
"""Возвращает возможные каталоги с переводами."""
|
||||||
|
configured = os.getenv("GONETS_LOCALES_PATH")
|
||||||
|
if configured:
|
||||||
|
configured_path = Path(configured).expanduser()
|
||||||
|
if configured_path.exists():
|
||||||
|
yield configured_path
|
||||||
|
srv_root = Path(__file__).resolve().parent.parent
|
||||||
|
for candidate in (
|
||||||
|
srv_root / "locales",
|
||||||
|
srv_root.parent / "locales", # на случай, если каталоги вынесены на уровень выше
|
||||||
|
Path("/usr/share/gonets/locales"),
|
||||||
|
Path("/usr/local/share/gonets/locales"),
|
||||||
|
):
|
||||||
|
if candidate.exists():
|
||||||
|
yield candidate
|
||||||
|
|
||||||
|
LOCALE_DIRS = list(dict.fromkeys(_candidate_locale_dirs()))
|
||||||
|
if not LOCALE_DIRS:
|
||||||
|
logger.warning("Locales directory not found; translations will use NullTranslations")
|
||||||
|
|
||||||
def _load_translations(locale: str) -> Translations:
|
def _load_translations(locale: str) -> Translations:
|
||||||
"""Подгружает словарь переводов с fallback на ru."""
|
"""Подгружает словарь переводов с fallback на ru."""
|
||||||
try:
|
last_error: Optional[str] = None
|
||||||
translations = Translations.load(str(LOCALES_DIR), [locale], "messages")
|
for directory in LOCALE_DIRS or [None]:
|
||||||
if not translations._catalog:
|
try:
|
||||||
logger.warning(f"Translations for locale {locale} not found, falling back to ru")
|
if directory is None:
|
||||||
translations = Translations.load(str(LOCALES_DIR), ["ru"], "messages")
|
raise FileNotFoundError("Locales directory list is empty")
|
||||||
return translations
|
translations = Translations.load(str(directory), [locale], "messages")
|
||||||
except Exception as e: # pragma: no cover - логируем и возвращаем ru
|
if translations._catalog:
|
||||||
logger.error(f"Failed to load translations for locale {locale}: {str(e)}")
|
return translations
|
||||||
return Translations.load(str(LOCALES_DIR), ["ru"], "messages")
|
logger.warning(
|
||||||
|
"Translations catalog empty for locale %s in %s; trying fallback ru",
|
||||||
|
locale,
|
||||||
|
directory,
|
||||||
|
)
|
||||||
|
ru_translations = Translations.load(str(directory), ["ru"], "messages")
|
||||||
|
if ru_translations._catalog:
|
||||||
|
return ru_translations
|
||||||
|
except Exception as exc: # pragma: no cover - логируем и возвращаем ru
|
||||||
|
last_error = str(exc)
|
||||||
|
continue
|
||||||
|
logger.error(
|
||||||
|
"Failed to load translations for locale %s from dirs %s: %s. Using NullTranslations",
|
||||||
|
locale,
|
||||||
|
LOCALE_DIRS,
|
||||||
|
last_error,
|
||||||
|
)
|
||||||
|
return Translations()
|
||||||
|
|
||||||
def get_translations() -> callable:
|
def _resolve_locale_from_request(request: Request) -> str:
|
||||||
"""
|
locale = request.query_params.get("locale")
|
||||||
Создаёт зависимость для загрузки переводов на основе заголовка Accept-Language
|
if not locale:
|
||||||
или параметра запроса 'locale'. По умолчанию используется русский язык.
|
locale = request.cookies.get("locale")
|
||||||
"""
|
if not locale:
|
||||||
def translations_dependency(request: Optional[Union[Request, WebSocket]] = None) -> Translations:
|
locale = request.headers.get("Accept-Language", "ru").split(",")[0].split("-")[0]
|
||||||
if request is None:
|
return locale or "ru"
|
||||||
logger.warning("Translations dependency called without request/websocket; fallback to ru")
|
|
||||||
return _load_translations("ru")
|
|
||||||
|
|
||||||
# Проверяем параметр запроса 'locale', если он есть
|
def get_translations(request: Request) -> Translations:
|
||||||
locale = None
|
"""Зависимость FastAPI для HTTP-эндпоинтов."""
|
||||||
if hasattr(request, "query_params"):
|
locale = _resolve_locale_from_request(request)
|
||||||
locale = request.query_params.get("locale")
|
logger.debug(f"Determined locale (http): {locale}")
|
||||||
if not locale and hasattr(request, "cookies"):
|
return _load_translations(locale)
|
||||||
locale = request.cookies.get("locale")
|
|
||||||
if not locale and hasattr(request, "headers"):
|
|
||||||
locale = request.headers.get("Accept-Language", "ru").split(",")[0].split("-")[0]
|
|
||||||
|
|
||||||
locale = locale or "ru"
|
def get_translations_ws(websocket: WebSocket) -> Translations:
|
||||||
logger.debug(f"Determined locale: {locale}")
|
"""Зависимость FastAPI для WebSocket-эндпоинтов."""
|
||||||
return _load_translations(locale)
|
# WebSocket не имеет cookies/headers в виде HTTPRequest, но есть headers
|
||||||
|
header_locale = websocket.headers.get("accept-language", "ru").split(",")[0].split("-")[0] if websocket.headers else "ru"
|
||||||
return translations_dependency
|
logger.debug(f"Determined locale (ws): {header_locale}")
|
||||||
|
return _load_translations(header_locale or "ru")
|
||||||
|
|
||||||
def _(msgid: str, request: Request = None) -> str:
|
def _(msgid: str, request: Request = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
@ -74,10 +99,9 @@ def _(msgid: str, request: Request = None) -> str:
|
||||||
str: Переведенная строка.
|
str: Переведенная строка.
|
||||||
"""
|
"""
|
||||||
if request is None:
|
if request is None:
|
||||||
# Язык, который будет отображаться по умолчанию
|
|
||||||
translations = _load_translations("ru")
|
translations = _load_translations("ru")
|
||||||
logger.debug(f"Using default ru translations for msgid: {msgid}")
|
logger.debug(f"Using default ru translations for msgid: {msgid}")
|
||||||
else:
|
else:
|
||||||
translations = get_translations()(request)
|
translations = get_translations(request)
|
||||||
logger.debug(f"Using translations for locale {request.cookies.get('locale', 'ru')} for msgid: {msgid}")
|
logger.debug(f"Using translations for locale {request.cookies.get('locale', 'ru')} for msgid: {msgid}")
|
||||||
return translations.gettext(msgid)
|
return translations.gettext(msgid)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue