MAC: {device.mac}
@@ -101,7 +100,7 @@ const DeviceCard: React.FC = ({ device, status }) => {
Имя:
- {device.hostname || (nameSetting === 'ip' ? device.ip : 'Неизвестно')}
+ {device.hostname || 'Неизвестно'}
diff --git a/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx b/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
index a1bbfca..55ca7e3 100644
--- a/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
+++ b/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
@@ -17,10 +17,10 @@ const Button = styled.button.withConfig({
color: white;
font-size: ${(props) =>
props.size === "small"
- ? "16px"
+ ? "14px"
: props.size === "big"
- ? "18px"
- : "16px"}; // По умолчанию
+ ? "16px"
+ : "14px"}; // По умолчанию
cursor: pointer;
display: flex;
justify-content: center;
diff --git a/FRONT/BUILD/resources/app/src/components/UI/tables/TableScanner.tsx b/FRONT/BUILD/resources/app/src/components/UI/tables/TableScanner.tsx
new file mode 100644
index 0000000..e1b8c6f
--- /dev/null
+++ b/FRONT/BUILD/resources/app/src/components/UI/tables/TableScanner.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import { DeviceUser } from '../../../store/useDeviceStore';
+
+interface TableScannerProps {
+ devices: DeviceUser[];
+}
+import styled from 'styled-components';
+import {IconComponent} from "../../../const/buttons/icons";
+import {buttonNames} from "../../../const/buttons/names";
+import {ModalMap} from "../../modalwindows/windowsType";
+import {useModalContext} from "../../../store";
+
+const TableContainer = styled.div`
+ width: 100%;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
+ background: ${({ theme }) => theme.primary};
+ margin: 24px 0;
+`;
+
+const StyledTable = styled.table`
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+`;
+
+const TableHeaderCell = styled.th<{ sortable?: boolean }>`
+ padding: 16px 24px;
+ text-align: left;
+ font-weight: 600;
+ font-size: 14px;
+ color: ${({ theme }) => theme.textSecondary};
+ background: ${({ theme }) => theme.primary};
+ border-bottom: 1px solid ${({ theme }) => theme.borderColor};
+ transition: all 0.2s ease;
+`;
+
+const TableRow = styled.tr<{ clickable?: boolean }>`
+ transition: all 0.2s ease;
+ &:hover {
+ background-color: ${({ theme }) => theme.background};
+ }
+`;
+
+const TableCell = styled.td`
+ padding: 16px 24px;
+ font-size: 14px;
+ color: ${({ theme }) => theme.text};
+ border-bottom: 1px solid ${({ theme }) => theme.borderColor};
+ transition: all 0.2s ease;
+`;
+
+const StatusBadge = styled.span<{ status:string }>`
+ width: 10px;
+ height: 30px;
+ background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.offlineColor)};
+ display: inline-block;
+ border-radius:4px;
+`
+
+const TableScanner = ({devices}:TableScannerProps) => {
+ const { openModal } = useModalContext();
+
+ const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
+ e.stopPropagation();
+ openModal(ModalMap['6'], {
+ address: device.mac,
+ currentNote: device.note || ''
+ });
+ };
+
+ return (
+
+
+
+
+
+ Имя
+ Производитель
+ IP адрес
+ Открытые порты
+ Последняя активность
+ Заметка
+
+
+
+
+ {devices.map((item, i) => (
+
+
+ {item.hostname || 'Неизвестен'}
+ {item.vendor}
+ {item.ip}
+ {item.open_ports.join(', ') || '-'}
+ {item.last_seen}
+ {item.note || '-'}
+
+ openModalNote(e, item)}>
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default TableScanner;
\ No newline at end of file
diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx
index 8dd7174..d04af92 100644
--- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx
+++ b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx
@@ -3,13 +3,12 @@ import { Handle, Position } from '@xyflow/react';
import { Ip } from './style';
import { Row, IpTitle, IpContent } from './style';
import Input from '../../../../UI/inputs/input';
-import {useIpStore, useModalContext, usePannableStore} from '../../../../../store';
+import { useModalContext, usePannableStore } from '../../../../../store';
import { ModalMap } from '../../../../modalwindows/windowsType';
import { validateIPAddress, validateSameSubnet } from '../../../../../utils/nodeController/validator';
import {changeIp} from '../../../../../const/types/types'
import { IconComponent } from "../../../../../const/buttons/icons";
import { buttonNames } from "../../../../../const/buttons/names";
-import {NetworkService} from "../../../../../api/request";
interface IIp {
ipType: string
@@ -46,7 +45,6 @@ export const IpNode = ({ data }: CustomNodeProps) => {
const { toggleEditingMode } = usePannableStore();
const [isInput, setIsInput] = useState(false)
const { openModal } = useModalContext();
- const {ipAddress} = useIpStore();
const [ipData, setIpData] = useState(
{
@@ -148,20 +146,7 @@ export const IpNode = ({ data }: CustomNodeProps) => {
setIsInput(false)
};
- const switchMethod = async ()=>{
- const methodDCHP ={
- "connection_id": ipData.connection,
- "ipv4_updates": {
- "ipv4.method": "auto",
- }
- }
- try {
- await NetworkService.saveIp(ipAddress, methodDCHP);
- window.location.reload();
- }catch (err){
- console.error(err)
- }
- }
+
return (
<>
@@ -185,10 +170,7 @@ export const IpNode = ({ data }: CustomNodeProps) => {
) : (
-
- {typeMap[data.ipData.ipType] ==='Static' && }
-
-
+
)}
diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx
index 2b2a334..2e2b998 100644
--- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx
+++ b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx
@@ -120,7 +120,7 @@ export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => {
);
useDevicesStore.setState({ devices: updatedDevices });
}
-
+
onClose()
}
} catch (err) {
diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx
index b6dbac9..7edf179 100644
--- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx
+++ b/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx
@@ -7,7 +7,6 @@ import {NoteDevice} from "./modalContainers/NoteDevice";
import { NoteWifi } from "./modalContainers/NoteWifi";
import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
-import {Settings} from "../settings/Settings";
type Props = {
isOpen: boolean,
@@ -75,11 +74,6 @@ export default function WindowComponent({ isOpen, type, data }: Props) {
{type === ModalMap['5'] && (
)}
- {type === ModalMap['10'] && (
-
- )}
}
>
diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts b/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts
index 2e588c2..2b75bd3 100644
--- a/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts
+++ b/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts
@@ -13,7 +13,7 @@ export const ModalWindow = styled.div`
height: 100%;
`
-export const ModalContainer = styled.div<{height?: string, width?:string}>`
+export const ModalContainer = styled.div<{height?: string}>`
border-radius: 12px;
background: ${(props)=>props.theme.secondary};
color: ${(props=>props.theme.textColor)};
@@ -25,7 +25,7 @@ export const ModalContainer = styled.div<{height?: string, width?:string}>`
justify-content: space-between;
gap: 15px;
height: ${(props) => props.height ? props.height : '230px'};
- width: ${(props) => props.width ? props.width : '520px'};
+ width: 520px;
`
export const ModalTitle = styled.div`
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx
index 59a4c39..57207b9 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx
@@ -2,10 +2,8 @@
import { MainButton } from "../../../UI/buttons/mainButton"
import { workSpaceNames } from "../../../../const/workspace/names"
import { useWorkSpaceStore } from "../../../../store"
-import styled, {useTheme} from "styled-components"
+import styled from "styled-components"
import ThemeButton from "../../../UI/buttons/toggletheme"
-import {buttonNames} from "../../../../const/buttons/names";
-import {IconComponent} from "../../../../const/buttons/icons";
type Props = {
from: 'reg' | 'helloWindow' | 'workSpace'
@@ -31,41 +29,23 @@ const Content = styled.div`
justify-content: space-between;
height: 100%;
box-sizing: border-box;
- padding: 40px 50px;
- background: ${(props => props.theme.primary)};
+ padding: 25px 50px;
+ background: ${(props => props.theme.secondary)};
border-radius: 18px;
- position: relative;
`
const Listing = styled.div`
display: flex;
flex-direction: column;
- background-color: ${(props => props.theme.secondary)};
- border-radius: 10px;
- padding: 24px 20px;
- p{
- font-weight: bold;
- font-size: 24px;
- }
+ gap: 15px;
+
`
-const List = styled.ul`
+const List = styled.div`
display: flex;
flex-direction: column;
- margin: 10px 0;
- padding: 0;
- list-style-type: none;
- font-size: 18px;
- gap: 4px;
- & li:before {
- color: ${(props => props.theme.accent)};
- content: "•";
- font-size: 28px;
- width: .8em;
- display: inline-block;
- position: relative;
- top: 2px;
- }
+ gap: 15px;
+ margin-left: 10px;
`
const Title = styled.div`
@@ -75,55 +55,43 @@ const Title = styled.div`
const SubTitle = styled.div`
display: flex;
flex-direction: column;
- gap: 24px;
- background-color:rgba(255, 248, 225, 0.6);
- border-left: #ff9800 solid 4px;
- border-radius: 0 8px 8px 0;
- padding: 20px 12px;
-
- a{
- text-decoration: none;
- color: ${(props => props.theme.errorColor)};
- font-weight: 500;
- font-size: 18px;
- width: max-content;
- }
+ gap: 10px;
+
p{
font-size: 18px;
- color: ${(props=>props.theme.textColor)};
- }
- .icon{
- width: 32px;
- height: 32px;
- fill: #ff9800;
}
+
`
const Links = styled.div`
display: flex;
- justify-content: start;
+ flex-direction: column;
gap: 20px;
`
-const LinkBlock = styled.a`
- text-decoration: none;
- color: ${(props => props.theme.accent)};;
+const LinkColumn = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+`
+
+
+const LinkBlock = styled.div`
+ display: flex;
+ gap: 5px;
`
const Copyright = styled.div`
width: 100%;
- text-align: start;
- color: ${(props => props.theme.offlineColor)};
+ text-align: center;
+ margin-top: 50px;
`
const ButtonContainer = styled.div`
- position: absolute;
- top: 12px;
- right: 14px;
-`
-const AboutLine = styled.hr`
- border: ${(props => props.theme.hoverBtn)} 1px solid;
width: 100%;
+ display: flex;
+ justifi-content: flex-start;
+ align-items: center;
`
const MainTitle = styled.div`
@@ -131,33 +99,24 @@ const MainTitle = styled.div`
display: flex;
gap: 15px;
align-items: center;
- justify-content: space-between;
- padding-top: 10px;
+ justify-content: center;
+ padding-top: 50px;
box-sizing: border-box;
img{
- width: 50px;
- height: 50px;
- }
- .version{
- text-wrap: nowrap;
- background-color: ${(props => props.theme.hoverBtn)};
- color: ${(props => props.theme.borderColor)};
- border-radius: 20px;
- font-size: 18px;
- padding: 10px 18px;
- line-height: 1;
- }
- .title{
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 24px;
+ width: 40px;
+ height: 40px;
}
`
+const Text = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+`
export default function About({ from, onBack }: Props) {
const { setWorkModule } = useWorkSpaceStore()
+
const getBackHandler = () => {
switch (from) {
case 'reg':
@@ -179,63 +138,68 @@ export default function About({ from, onBack }: Props) {
-
-
-
О приложении
-
- v 0.0.3
+
+
О приложении
+
-
-
- ГоNets — минимально работоспособный кроссплатформенный продукт (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.
-
+
-
- Возможности:
-
- Визуализация активных сетевых ресурсов
- Настройка параметров локальных и удалённых сетевых ресурсов
- Управление взаимодействием ресурсов с Клиентом ГoNets
-
-
+
+ Приложение ГоNets является минимально работоспособным кроссплатформенным продуктом (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.
+
-
-
-
-
- Программное обеспечение исключительно для ознакомления и домашнего использования.
- Перейти на ГоNets-PRO
-
-
-
- Домашняя страницa
-
+
+ Данное приложение умеет:
+
+ - Отображать в графическом виде активные сетевые ресурсы
+ - Настраивать сетевые параметры локального и удаленных сетевых ресурсов
+ - Отображать и изменять взаимодействие сетевых ресурсов с установленным Клиентом ГоNets
+
+
-
- Документация
-
+
+ Программное обеспечение исключительно для ознакомления и домашнего использования.
+ Для коммерческого использования приобретите версию ГоNets-PRO (Подробная информация на странице продукта)
+
-
- Лицензия
-
-
+
+
+ Ссылка на домашнюю страницу:
+ https://gossys.dev/pages/projects/gonets
+
+
+
+ Инструкции и информация:
+ https://wiki.gossys.dev/ru/gonets
+
+
+
+ Распространяется под лицензией GosysPUB текущей версии.
+
+ Полный текст актуальной лицензии:
+ https://wiki.gossys.dev/ru/lic/GosysPUB
+
+
+
+
+
+
+ Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.
+
+
+
-
- Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.
-
{showBackButton && backHandler && (
-
-
)}
{from == 'reg' &&
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx
index 9c865bf..1075a09 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx
@@ -1,9 +1,7 @@
-import React, {useEffect, useState} from 'react';
+import React from 'react';
import styled from 'styled-components';
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
import {DeviceUser} from "../../../../../../store/useDeviceStore";
-import {usePaginateSetting} from "../../../../../../hooks/useSettings";
-import Pagination from "../../../../../UI/Pagination";
const ScanBtn = styled.button`
position: relative;
@@ -30,36 +28,10 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
const activeDevices = devices.filter(device => device.status === 'online');
const inactiveDevices = devices.filter(device => device.status !== 'online');
- // Пагинация
- const paginateSetting = usePaginateSetting();
- const [currentPage, setCurrentPage] = useState(1);
- const [devicesPerPage, setDevicesPerPage] = useState(paginateSetting);
-
- useEffect(() => {
- setDevicesPerPage(paginateSetting);
- }, [paginateSetting]);
-
- const indexOfLastDevice = currentPage * devicesPerPage;
- const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;
- const currentActiveDevices = activeDevices.slice(indexOfFirstDevice, indexOfLastDevice);
- const currentInactiveDevices = inactiveDevices.slice(indexOfFirstDevice, indexOfLastDevice);
- const totalPages = Math.ceil(devices.length / devicesPerPage);
-
- const handlePrevPage = () => {
- if(currentPage > 1) setCurrentPage(currentPage - 1);
- }
- const handleNextPage = () => {
- if(currentPage < totalPages) setCurrentPage(currentPage + 1);
- }
- const onPageChange = (page: number) => {
- setCurrentPage(page);
- }
-
-
return (
<>
- {devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
+ {devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
{devices.length > 0 ? (
@@ -70,8 +42,8 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
}}>
{/* Колонка активных устройств */}
-
Активные узлы ({activeDevices.length})
- {currentActiveDevices.map((device, index) => (
+
Активные устройства ({activeDevices.length})
+ {activeDevices.map((device, index) => (
{/* Колонка неактивных устройств */}
-
Неактивные узлы ({inactiveDevices.length})
- {currentInactiveDevices.map((device, index) => (
+ Неактивные устройства ({inactiveDevices.length})
+ {inactiveDevices.map((device, index) => (
) : scanInitiated ? (
- Узлы не найдены
+ Устройства не найдены
) : null}
- {totalPages > 1 && (
-
- )}
>
);
};
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx
index 5a3bc4f..ebe71a9 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import styled from 'styled-components';
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
import {DeviceUser} from "../../../../../../store/useDeviceStore";
-import TableScanner from "./TableScanner";
+import TableScanner from "../../../../../UI/tables/TableScanner";
const ScanBtn = styled.button`
position: relative;
@@ -20,7 +20,7 @@ const ScanBtn = styled.button`
interface ListUsersProps {
loading: boolean;
- handleScan: (replay?: boolean) => void;
+ handleScan: ()=>void;
devices: DeviceUser[];
scanInitiated: boolean;
}
@@ -29,14 +29,14 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
return (
<>
- handleScan()} disabled={loading}>
- {devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'}
+
+ {devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
{devices.length > 0 ? (
) : scanInitiated ? (
- Узлы не найдены
+ Устройства не найдены
) : null}
>
);
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx
index 8a098b8..cb816ca 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx
@@ -1,12 +1,11 @@
import { useIpStore } from "../../../../../store";
import styled, { keyframes } from "styled-components";
-import {useEffect, useState} from "react";
+import { useState } from "react";
import { NetworkService } from "../../../../../api/request";
import ListUsers from "./modules/ListUsers";
import TableUsers from "./modules/TableUsers";
import DropdownType from "../../../../UI/Dropdown/DropdownType";
import useNetworkStore from "../../../../../store/useDeviceStore";
-import {useIntervalBoolean, useIntervalTime} from "../../../../../hooks/useSettings";
type Option ={
value: number,
@@ -78,7 +77,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
span {
position: absolute;
display: inline-block;
- background: ${(props) => props.theme.scannerColor};
+ background: ${(props) => props.theme.accent};
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@@ -137,11 +136,9 @@ export default function UserScanner() {
const [scanInitiated, setScanInitiated] = useState(devices.length > 0);
// Сканирование
- const handleScan = async (replay:boolean) => {
+ const handleScan = async () => {
try {
- if(!replay){
- setLoading(true);
- }
+ setLoading(true);
setScanInitiated(true);
const res = await NetworkService.scanUser(ipAddress);
console.log(res);
@@ -154,23 +151,6 @@ export default function UserScanner() {
}
};
- // Интервально сканирование
- const intervalToggle = useIntervalBoolean();
- const intervalTime = useIntervalTime();
- useEffect(() => {
- let intervalId: NodeJS.Timeout | null = null;
-
- if (intervalToggle) {
- intervalId = setInterval(()=>handleScan(true), intervalTime * 60 * 1000);
- }
-
- return () => {
- if (intervalId) {
- clearInterval(intervalId);
- }
- };
- }, [intervalToggle, intervalTime, handleScan]);
-
// Способ отображения
const viewOptions: Option[] = [
{ value: 1, label: 'Таблица' },
@@ -183,7 +163,7 @@ export default function UserScanner() {
return (
- {(!scanInitiated || loading) && (Сканирование узлов подсети )}
+ {(!scanInitiated || loading) && (Сканирование пользователей в сети )}
@@ -197,12 +177,12 @@ export default function UserScanner() {
{loading ? (
- Сканирование узлов
+ Сканирование сети
) : selectedView.value == 1 ? (
- handleScan(false)} devices={devices} scanInitiated={scanInitiated} />
+
):(
- handleScan(false)} devices={devices} scanInitiated={scanInitiated} />
+
)}
);
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx
index 25000eb..b24e367 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx
@@ -136,8 +136,7 @@ const ScanBtn = styled.button<{scanInitiated: boolean}>`
`
export function WifiScanner() {
- //const { ipAddress } = useIpStore();
- const ipAddress = '192.168.1.147'
+ const { ipAddress } = useIpStore();
const [loading, setLoading] = useState(false);
const [scanInitiated, setScanInitiated] = useState(false);
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
@@ -162,19 +161,17 @@ export function WifiScanner() {
return (
- Сканирование точек доступа Wi-Fi сети
+ Сканирование Wi-Fi сети
- {wifiNetworks.map((network, index) => (
+ {wifiNetworks.map((network) => (
))}
@@ -189,11 +186,11 @@ export function WifiScanner() {
{loading ? (
- Сканирование точек доступа
+ Сканирование сети
) : (
0 }>
- {wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать точки доступа'}
+ {wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
)}
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx
index 54fee2b..eccd601 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx
@@ -31,45 +31,43 @@ const Dot = styled.div<{ top: string; left: string }>`
}
`;
-const getPositionInRing = (index: number, total: number, ringSize: number) => {
- const angle = (index * (2 * Math.PI / total)) + (Math.PI / total * (index % 2));
- const innerRadius = ringSize * 0.4;
- const outerRadius = ringSize * 0.5;
- const radius = innerRadius + (outerRadius - innerRadius) * (0.3 + 0.7 * (index % 10) / 10);
+const WifiDot = ({ network, onSelect }: { network: WifiNetworkScanner, onSelect: (n: WifiNetworkScanner) => void }) => {
+ const getPositionInRing = (bssid: string, ringSize: number) => {
+ const hash = bssid.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
+ const randomAngle = (hash % 360) * (Math.PI / 180);
+ const innerRadius = ringSize * 0.4;
+ const outerRadius = ringSize * 0.5;
+ const randomValue = (hash % 1000) / 1000;
+ const randomRadius = Math.sqrt(innerRadius * innerRadius + randomValue * (outerRadius * outerRadius - innerRadius * innerRadius));
- return {
- x: Math.cos(angle) * radius,
- y: Math.sin(angle) * radius
+ return {
+ x: Math.cos(randomAngle) * randomRadius,
+ y: Math.sin(randomAngle) * randomRadius
+ };
};
-};
-const WifiDot = ({ network, index, total, onSelect }: {
- network: WifiNetworkScanner,
- index: number,
- total: number,
- onSelect: (n: WifiNetworkScanner) => void
-}) => {
- const getPosition = (signal: number, index: number, total: number) => {
+ const getPosition = (signal: number, bssid: string) => {
let strength: 1 | 2 | 3;
if (signal >= -50) {
strength = 3;
} else if (signal >= -70) {
strength = 2;
+ } else if (signal >= -90) {
+ strength = 1;
} else {
strength = 1;
}
-
const ringSizes = { 1: 550, 2: 400, 3: 250 };
const ringSize = ringSizes[strength];
- const offset = getPositionInRing(index, total, ringSize);
+ const offset = getPositionInRing(bssid, ringSize);
return {
- top: `calc(50% + ${offset.y}px)`,
- left: `calc(50% + ${offset.x}px)`
+ top: `calc(50% - ${offset.y}px)`,
+ left: `calc(50% - ${offset.x}px)`
};
};
- const position = getPosition(network.signal, index, total);
+ const position = getPosition(network.signal, network.bssid);
return (
onSelect(network)}>
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx
index 1996ddd..d0325ef 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx
@@ -76,7 +76,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
setHasInited(false)
initializationAttempt.current = 0;
- const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
+ const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
let connectionTimeout;
setSocket(newSocket);
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx
index 1f49b9c..9fd05a2 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx
@@ -81,11 +81,6 @@ export const ToolBar = () => {
/>
-
-
- openModal(ModalMap['10'])} />
-
-
)
diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx
index 8b919f4..dd5ca2b 100644
--- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx
+++ b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx
@@ -115,7 +115,7 @@ export const DevicesBar = () => {
const checkWebSocketAvailability = async (ip: string): Promise => {
return new Promise((resolve) => {
- const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
+ const testSocket = new WebSocket(`ws://${ip}:8001/api/v1`);
const timeout = setTimeout(() => {
testSocket.close();
resolve(false);
diff --git a/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx b/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx
index 12bf8d5..301b1d6 100644
--- a/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx
+++ b/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx
@@ -9,7 +9,6 @@ import settings from '@iconify/icons-ic/baseline-settings';
import info from '@iconify/icons-ic/baseline-info';
import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down';
import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right';
-import arrowLeft from '@iconify/icons-ic/baseline-keyboard-arrow-left';
import edit from '@iconify/icons-ic/baseline-edit';
import checkCircle from '@iconify/icons-ic/round-check-circle';
import cancel from '@iconify/icons-ic/baseline-cancel';
@@ -20,8 +19,6 @@ import refresh from '@iconify/icons-ic/baseline-refresh';
import close from '@iconify/icons-ic/round-close';
import wifiFind from '@iconify/icons-ic/baseline-wifi-find';
import noteEdit from '@iconify/icons-ph/note-pencil';
-import swapHoriz from '@iconify/icons-ic/baseline-swap-horiz';
-import check from '@iconify/icons-ic/baseline-check';
export const ICONS = {
[buttonNames[1]]: exitToApp,
@@ -40,9 +37,6 @@ export const ICONS = {
[buttonNames[14]]: userSearch,
[buttonNames[15]]: wifiFind,
[buttonNames[16]]: noteEdit,
- [buttonNames[17]]: arrowLeft,
- [buttonNames[18]]: swapHoriz,
- [buttonNames[19]]: check,
} as const;
type IconName = keyof typeof ICONS;
diff --git a/FRONT/BUILD/resources/app/src/const/buttons/names.ts b/FRONT/BUILD/resources/app/src/const/buttons/names.ts
index b6c7505..54c1d40 100644
--- a/FRONT/BUILD/resources/app/src/const/buttons/names.ts
+++ b/FRONT/BUILD/resources/app/src/const/buttons/names.ts
@@ -16,8 +16,5 @@ export const buttonNames: names ={
13: 'noAdmin',
14: 'userSearch',
15: 'wifiFind',
- 16: 'noteEdit',
- 17: 'arrowLeft',
- 18: 'swapHoriz',
- 19: 'check'
+ 16: 'noteEdit'
}
\ No newline at end of file
diff --git a/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx b/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx
index b5560ce..1d80d6b 100644
--- a/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx
+++ b/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx
@@ -3,7 +3,7 @@ import { toolBarNames } from "./names"
import { useTheme } from "styled-components"
import { Icon } from '@iconify/react';
-import desktop from '@iconify/icons-ic/baseline-menu';
+import desktop from '@iconify/icons-ic/round-desktop-windows';
export const ICONS = {
[toolBarNames[1]]: desktop,
diff --git a/FRONT/BUILD/resources/app/src/const/theme/theme.ts b/FRONT/BUILD/resources/app/src/const/theme/theme.ts
index dbf1cb3..aab3658 100644
--- a/FRONT/BUILD/resources/app/src/const/theme/theme.ts
+++ b/FRONT/BUILD/resources/app/src/const/theme/theme.ts
@@ -24,7 +24,7 @@ export const dark = {
background: '#3A3A3A',
textColor: '#FFFFFF',
borderColor: '#b6b6b6',
- scannerColor: '#00E5FF',
+ scannerColor: '#0E9C9D',
offlineColor: '#656565',
hoverBtn: '#4a4a4a',
};
\ No newline at end of file
diff --git a/FRONT/DEV/.gitignore b/FRONT/DEV/.gitignore
new file mode 100644
index 0000000..b2d59d1
--- /dev/null
+++ b/FRONT/DEV/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/dist
\ No newline at end of file
diff --git a/FRONT/DEV/assets/icon.png b/FRONT/DEV/assets/icon.png
new file mode 100644
index 0000000..b39bc87
Binary files /dev/null and b/FRONT/DEV/assets/icon.png differ
diff --git a/FRONT/DEV/eslint.config.js b/FRONT/DEV/eslint.config.js
new file mode 100644
index 0000000..092408a
--- /dev/null
+++ b/FRONT/DEV/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/FRONT/DEV/index.html b/FRONT/DEV/index.html
new file mode 100644
index 0000000..7e0fab5
--- /dev/null
+++ b/FRONT/DEV/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ GoNets
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/index.tsx b/FRONT/DEV/index.tsx
new file mode 100644
index 0000000..eb6f456
--- /dev/null
+++ b/FRONT/DEV/index.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { HashRouter } from 'react-router-dom';
+import { useIpStore } from './src/store';
+import { useEffect } from 'react';
+import { useCheckAdmin } from './src/utils/checkAdmin';
+
+import {
+ ModalProvider,
+ useModalContext,
+ ThemeProvider
+} from './src/store';
+
+import WindowComponent from './src/components/modalwindows/modalWindow';
+import Router from './src/router';
+import { I18nProvider } from "./src/utils/i18n/provider";
+import { useLanguage } from "./src/hooks/useSettings";
+
+const AppWithProviders = () => {
+ const currentLanguage = useLanguage();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const MainComponent = () => {
+ const checkAdmin = useCheckAdmin();
+ const { ipAddress } = useIpStore();
+
+ useEffect(() => {
+ const checkAdminStatus = async () => {
+ await checkAdmin();
+ };
+ checkAdminStatus();
+ }, [ipAddress]);
+
+ const { isOpen, modalType, modalData } = useModalContext();
+ return (
+ <>
+
+
+ >
+ );
+};
+
+const container = document.getElementById('root');
+if (!container) {
+ throw new Error('Root element with id "root" not found');
+}
+
+const root = createRoot(container);
+root.render( );
\ No newline at end of file
diff --git a/FRONT/DEV/main.js b/FRONT/DEV/main.js
new file mode 100644
index 0000000..df09e91
--- /dev/null
+++ b/FRONT/DEV/main.js
@@ -0,0 +1,61 @@
+const { app, BrowserWindow, session, Menu, ipcMain } = require('electron');
+const path = require('path');
+
+ipcMain.on('quit-app', () => {
+ app.quit();
+});
+
+app.commandLine.appendSwitch('ignore-certificate-errors');
+
+function createWindow() {
+ const win = new BrowserWindow({
+ icon: path.join(__dirname, 'assets/icon.png'),
+ webPreferences: {
+ preload: path.join(__dirname, 'preload.js'),
+ contextIsolation: true,
+ },
+ show: false
+ });
+
+ win.maximize();
+ win.show();
+
+ if (process.env.NODE_ENV === 'development') {
+ win.loadURL('https://localhost:8001');
+ win.webContents.openDevTools();
+ } else {
+ win.loadFile(path.join(__dirname, 'dist/index.html'));
+ }
+}
+
+app.whenReady().then(() => {
+ session.defaultSession.setCertificateVerifyProc((request, callback) => {
+ // Разрешаем для:
+ // 1. localhost
+ // 2. Всех IP в локальной сети (192.168.x.x, 10.x.x.x и т.д.)
+ // 3. Конкретного домена (если есть)
+ if (
+ request.hostname === 'localhost' ||
+ request.hostname === '127.0.0.1' ||
+ request.hostname === '0.0.0.0' ||
+ /^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(request.hostname)
+ ) {
+ return callback(0);
+ }
+ callback(-2);
+ });
+ Menu.setApplicationMenu(null);
+ createWindow();
+});
+
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') {
+ app.quit();
+ }
+});
+
+app.on('activate', () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createWindow();
+ }
+});
\ No newline at end of file
diff --git a/FRONT/DEV/package-lock.json b/FRONT/DEV/package-lock.json
new file mode 100644
index 0000000..f219eda
--- /dev/null
+++ b/FRONT/DEV/package-lock.json
@@ -0,0 +1,4966 @@
+{
+ "name": "gonets",
+ "version": "0.0.2",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "gonets",
+ "version": "0.0.2",
+ "dependencies": {
+ "@iconify/icons-ic": "^1.2.13",
+ "@iconify/icons-ph": "^1.2.5",
+ "@iconify/react": "^5.2.0",
+ "@reduxjs/toolkit": "^2.6.0",
+ "@xyflow/react": "^12.3.5",
+ "axios": "^1.7.9",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-flow-renderer": "^10.3.17",
+ "react-redux": "^9.2.0",
+ "react-router-dom": "^7.0.2",
+ "styled-components": "^6.1.13",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@iconify-json/ic": "^1.2.2",
+ "@types/react": "^18.0.28",
+ "@types/react-dom": "^18.0.11",
+ "@vitejs/plugin-basic-ssl": "^2.0.0",
+ "@vitejs/plugin-react": "^4.4.1",
+ "electron": "^39.2.3",
+ "electron-packager": "^17.1.2",
+ "esbuild-wasm": "^0.17.12",
+ "typescript": "^4.9.5",
+ "vite": "^6.3.4"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.8",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
+ "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.10",
+ "@babel/helper-compilation-targets": "^7.26.5",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.10",
+ "@babel/parser": "^7.26.10",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.10",
+ "@babel/types": "^7.26.10",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
+ "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.26.8",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
+ "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
+ "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.26.9",
+ "@babel/types": "^7.26.10"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
+ "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
+ "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
+ "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@electron/asar": {
+ "version": "3.2.18",
+ "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.18.tgz",
+ "integrity": "sha512-2XyvMe3N3Nrs8cV39IKELRHTYUWFKrmqqSY1U+GMlc0jvqjIVnoxhNd2H4JolWQncbJi1DCvb5TNxZuI2fEjWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^5.0.0",
+ "glob": "^7.1.6",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "asar": "bin/asar.js"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
+ "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/@electron/notarize": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.4.tgz",
+ "integrity": "sha512-W5GQhJEosFNafewnS28d3bpQ37/s91CDWqxVchHfmv2dQSTWpOzNlUVQwYzC1ay5bChRV/A9BTL68yj0Pa+TSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "fs-extra": "^9.0.1"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/notarize/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.2.tgz",
+ "integrity": "sha512-KqVlm9WMWq19lBpCXQoThC/Koaiji2zotUDYwZDaZlZZym+FXY9mQW8wN6sUQ93nkVc42f3TQ1S/XN9S1kjM5Q==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "compare-version": "^0.1.2",
+ "debug": "^4.3.4",
+ "fs-extra": "^10.0.0",
+ "isbinaryfile": "^4.0.8",
+ "minimist": "^1.2.6",
+ "plist": "^3.0.5"
+ },
+ "bin": {
+ "electron-osx-flat": "bin/electron-osx-flat.js",
+ "electron-osx-sign": "bin/electron-osx-sign.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/osx-sign/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@electron/universal": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz",
+ "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "@malept/cross-spawn-promise": "^1.1.0",
+ "debug": "^4.3.1",
+ "dir-compare": "^3.0.0",
+ "fs-extra": "^9.0.1",
+ "minimatch": "^3.0.4",
+ "plist": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/@electron/universal/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
+ "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz",
+ "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz",
+ "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz",
+ "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz",
+ "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz",
+ "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz",
+ "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz",
+ "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz",
+ "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz",
+ "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz",
+ "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz",
+ "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz",
+ "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz",
+ "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz",
+ "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz",
+ "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz",
+ "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz",
+ "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz",
+ "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz",
+ "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz",
+ "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz",
+ "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@iconify-json/ic": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@iconify-json/ic/-/ic-1.2.2.tgz",
+ "integrity": "sha512-QmjwS3lYiOmVWgTCEOTFyGODaR/+689+ajep/VsrCcsUN0Gdle5PmIcibDsdmRyrOsW/E77G41UUijdbjQUofw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/icons-ic": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/@iconify/icons-ic/-/icons-ic-1.2.13.tgz",
+ "integrity": "sha512-TphrhwOvgd7CTmUhz6jgXF+SPnMtvTm03bZsAYbny2kwq4zlkhr9e16YEyPGMvKhjtTqNooA3iZ9Wa+pZ8moXQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/icons-ph": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@iconify/icons-ph/-/icons-ph-1.2.5.tgz",
+ "integrity": "sha512-ewrnpL0u3MAP0wLDnXb/39W9W71ObyY8DNig6rYj7NEwcndWWlrGHt1RxEGYE3eqILl/IUcNIQQF1CXZAhwr/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@iconify/react/-/react-5.2.0.tgz",
+ "integrity": "sha512-7Sdjrqq3fkkQNks9SY3adGC37NQTHsBJL2PRKlQd455PoDi9s+Es9AUTY+vGLFOYs5yO9w9yCE42pmxCwG26WA==",
+ "license": "MIT",
+ "dependencies": {
+ "@iconify/types": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/cyberalien"
+ },
+ "peerDependencies": {
+ "react": ">=16"
+ }
+ },
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@malept/cross-spawn-promise": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
+ "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
+ "integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
+ "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
+ "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
+ "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
+ "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
+ "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
+ "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
+ "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
+ "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
+ "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
+ "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
+ "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
+ "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
+ "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
+ "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
+ "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
+ "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
+ "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
+ "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
+ "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
+ "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
+ "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
+ "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
+ "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.14",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
+ "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz",
+ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.13",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
+ "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
+ "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/resize-observer-browser": {
+ "version": "0.1.11",
+ "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz",
+ "integrity": "sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+ "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-basic-ssl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz",
+ "integrity": "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^6.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
+ "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+ "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@xyflow/react": {
+ "version": "12.3.5",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.3.5.tgz",
+ "integrity": "sha512-wAYqpicdrVo1rxCu0X3M9s3YIF45Agqfabw0IBryTGqjWvr2NyfciI8gIP4MB+NKpWWN5kxZ9tiZ9u8lwC7iAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@xyflow/system": "0.0.46",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@xyflow/react/node_modules/zustand": {
+ "version": "4.5.6",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz",
+ "integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@xyflow/system": {
+ "version": "0.0.46",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.46.tgz",
+ "integrity": "sha512-bmFXvboVdiydIFZmDCjrbBCYgB0d5pYdkcZPWbAxGmhMRUZ+kW3CksYgYxWabrw51rwpWitLEadvLrivG0mVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-drag": "^3.0.7",
+ "@types/d3-selection": "^3.0.10",
+ "@types/d3-transition": "^3.0.8",
+ "@types/d3-zoom": "^3.0.8",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/author-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz",
+ "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
+ "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001716",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
+ "integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/compare-version": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
+ "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn-windows-exe": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz",
+ "integrity": "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/malept"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/subscription/pkg/npm-cross-spawn-windows-exe?utm_medium=referral&utm_source=npm_fund"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@malept/cross-spawn-promise": "^1.1.0",
+ "is-wsl": "^2.2.0",
+ "which": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/dir-compare": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz",
+ "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal": "^1.0.0",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron": {
+ "version": "39.2.3",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.3.tgz",
+ "integrity": "sha512-j7k7/bj3cNA29ty54FzEMRUoqirE+RBQPhPFP+XDuM93a1l2WcDPiYumxKWz+iKcXxBJLFdMIAlvtLTB/RfCkg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@electron/get": "^2.0.0",
+ "@types/node": "^22.7.7",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js"
+ },
+ "engines": {
+ "node": ">= 12.20.55"
+ }
+ },
+ "node_modules/electron-packager": {
+ "version": "17.1.2",
+ "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-17.1.2.tgz",
+ "integrity": "sha512-XofXdikjYI7MVBcnXeoOvRR+yFFFHOLs3J7PF5KYQweigtgLshcH4W660PsvHr4lYZ03JBpLyEcUB8DzHZ+BNw==",
+ "deprecated": "Please use @electron/packager moving forward. There is no API change, just a package name change",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@electron/asar": "^3.2.1",
+ "@electron/get": "^2.0.0",
+ "@electron/notarize": "^1.2.3",
+ "@electron/osx-sign": "^1.0.5",
+ "@electron/universal": "^1.3.2",
+ "cross-spawn-windows-exe": "^1.2.0",
+ "debug": "^4.0.1",
+ "extract-zip": "^2.0.0",
+ "filenamify": "^4.1.0",
+ "fs-extra": "^11.1.0",
+ "galactus": "^1.0.0",
+ "get-package-info": "^1.0.0",
+ "junk": "^3.1.0",
+ "parse-author": "^2.0.0",
+ "plist": "^3.0.0",
+ "rcedit": "^3.0.1",
+ "resolve": "^1.1.6",
+ "semver": "^7.1.3",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "electron-packager": "bin/electron-packager.js"
+ },
+ "engines": {
+ "node": ">= 14.17.5"
+ },
+ "funding": {
+ "url": "https://github.com/electron/electron-packager?sponsor=1"
+ }
+ },
+ "node_modules/electron-packager/node_modules/fs-extra": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+ "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/electron-packager/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/electron-packager/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/electron-packager/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.145",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz",
+ "integrity": "sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
+ "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.3",
+ "@esbuild/android-arm": "0.25.3",
+ "@esbuild/android-arm64": "0.25.3",
+ "@esbuild/android-x64": "0.25.3",
+ "@esbuild/darwin-arm64": "0.25.3",
+ "@esbuild/darwin-x64": "0.25.3",
+ "@esbuild/freebsd-arm64": "0.25.3",
+ "@esbuild/freebsd-x64": "0.25.3",
+ "@esbuild/linux-arm": "0.25.3",
+ "@esbuild/linux-arm64": "0.25.3",
+ "@esbuild/linux-ia32": "0.25.3",
+ "@esbuild/linux-loong64": "0.25.3",
+ "@esbuild/linux-mips64el": "0.25.3",
+ "@esbuild/linux-ppc64": "0.25.3",
+ "@esbuild/linux-riscv64": "0.25.3",
+ "@esbuild/linux-s390x": "0.25.3",
+ "@esbuild/linux-x64": "0.25.3",
+ "@esbuild/netbsd-arm64": "0.25.3",
+ "@esbuild/netbsd-x64": "0.25.3",
+ "@esbuild/openbsd-arm64": "0.25.3",
+ "@esbuild/openbsd-x64": "0.25.3",
+ "@esbuild/sunos-x64": "0.25.3",
+ "@esbuild/win32-arm64": "0.25.3",
+ "@esbuild/win32-ia32": "0.25.3",
+ "@esbuild/win32-x64": "0.25.3"
+ }
+ },
+ "node_modules/esbuild-wasm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.19.tgz",
+ "integrity": "sha512-X9UQEMJMZXwlGCfqcBmJ1jEa+KrLfd+gCBypO/TSzo5hZvbVwFqpxj1YCuX54ptTF75wxmrgorR4RL40AKtLVg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/flora-colossus": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz",
+ "integrity": "sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "fs-extra": "^10.1.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/flora-colossus/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/galactus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz",
+ "integrity": "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "flora-colossus": "^2.0.0",
+ "fs-extra": "^10.1.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/galactus/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/galactus/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/galactus/node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-info": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz",
+ "integrity": "sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.1.1",
+ "debug": "^2.2.0",
+ "lodash.get": "^4.0.0",
+ "read-pkg-up": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 4.0"
+ }
+ },
+ "node_modules/get-package-info/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/get-package-info/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/global-agent/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/junk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
+ "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
+ "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/parse-author": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz",
+ "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "author-regex": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "error-ex": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/plist": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
+ "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.8",
+ "base64-js": "^1.5.1",
+ "xmlbuilder": "^15.1.1"
+ },
+ "engines": {
+ "node": ">=10.4.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rcedit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz",
+ "integrity": "sha512-WRlRdY1qZbu1L11DklT07KuHfRk42l0NFFJdaExELEu4fEQ982bP5Z6OWGPj/wLLIuKRQDCxZJGAwoFsxhZhNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn-windows-exe": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-flow-renderer": {
+ "version": "10.3.17",
+ "resolved": "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.17.tgz",
+ "integrity": "sha512-bywiqVErlh5kCDqw3x0an5Ur3mT9j9CwJsDwmhmz4i1IgYM1a0SPqqEhClvjX+s5pU4nHjmVaGXWK96pwsiGcQ==",
+ "deprecated": "react-flow-renderer has been renamed to reactflow, please use this package from now on https://reactflow.dev/docs/guides/migrate-to-v11/",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.9",
+ "@types/d3": "^7.4.0",
+ "@types/resize-observer-browser": "^0.1.7",
+ "classcat": "^5.0.3",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0",
+ "zustand": "^3.7.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "16 || 17 || 18",
+ "react-dom": "16 || 17 || 18"
+ }
+ },
+ "node_modules/react-flow-renderer/node_modules/zustand": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
+ "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-redux/node_modules/use-sync-external-store": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.5.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz",
+ "integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.5.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.2.tgz",
+ "integrity": "sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.5.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
+ "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.7"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.40.1",
+ "@rollup/rollup-android-arm64": "4.40.1",
+ "@rollup/rollup-darwin-arm64": "4.40.1",
+ "@rollup/rollup-darwin-x64": "4.40.1",
+ "@rollup/rollup-freebsd-arm64": "4.40.1",
+ "@rollup/rollup-freebsd-x64": "4.40.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.40.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.40.1",
+ "@rollup/rollup-linux-arm64-musl": "4.40.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.40.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.40.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.40.1",
+ "@rollup/rollup-linux-x64-gnu": "4.40.1",
+ "@rollup/rollup-linux-x64-musl": "4.40.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.40.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.40.1",
+ "@rollup/rollup-win32-x64-msvc": "4.40.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true,
+ "license": "CC-BY-3.0"
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.21",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
+ "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-outer/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/styled-components": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz",
+ "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.38",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/trim-repeated/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "license": "0BSD"
+ },
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
+ "license": "ISC"
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+ "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xmlbuilder": {
+ "version": "15.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+ "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
+ "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/FRONT/DEV/package.json b/FRONT/DEV/package.json
new file mode 100644
index 0000000..4ddb719
--- /dev/null
+++ b/FRONT/DEV/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "gonets",
+ "version": "0.0.2",
+ "main": "main.js",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "start": "electron .",
+ "package": "vite build && electron-packager . --out=dist --overwrite --icon=assets/icon"
+ },
+ "dependencies": {
+ "@iconify/icons-ic": "^1.2.13",
+ "@iconify/icons-ph": "^1.2.5",
+ "@iconify/react": "^5.2.0",
+ "@reduxjs/toolkit": "^2.6.0",
+ "@xyflow/react": "^12.3.5",
+ "axios": "^1.7.9",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-flow-renderer": "^10.3.17",
+ "react-redux": "^9.2.0",
+ "react-router-dom": "^7.0.2",
+ "styled-components": "^6.1.13",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@iconify-json/ic": "^1.2.2",
+ "@types/react": "^18.0.28",
+ "@types/react-dom": "^18.0.11",
+ "@vitejs/plugin-basic-ssl": "^2.0.0",
+ "@vitejs/plugin-react": "^4.4.1",
+ "electron": "^39.2.3",
+ "electron-packager": "^17.1.2",
+ "esbuild-wasm": "^0.17.12",
+ "typescript": "^4.9.5",
+ "vite": "^6.3.4"
+ }
+}
diff --git a/FRONT/DEV/preload.js b/FRONT/DEV/preload.js
new file mode 100644
index 0000000..1ceb420
--- /dev/null
+++ b/FRONT/DEV/preload.js
@@ -0,0 +1,11 @@
+const { contextBridge, ipcRenderer } = require('electron');
+
+contextBridge.exposeInMainWorld('electronAPI', {
+ send: (channel, data) => ipcRenderer.send(channel, data),
+ on: (channel, func) => ipcRenderer.on(channel, (event, ...args) => func(...args)),
+ quitApp: () => ipcRenderer.send('quit-app'),
+
+ showSaveDialog: (options) => ipcRenderer.invoke('show-save-dialog', options),
+ writeFile: (path, data) => ipcRenderer.invoke('write-file', { path, data }),
+ onThemeChange: (callback) => ipcRenderer.on('theme-update', (event, theme) => callback(theme))
+});
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/back1.svg b/FRONT/DEV/public/assets/images/back1.svg
new file mode 100644
index 0000000..6c1ae68
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/back1.svg
@@ -0,0 +1,836 @@
+
+
+ Created with Pixso.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/bigLogo.svg b/FRONT/DEV/public/assets/images/bigLogo.svg
new file mode 100644
index 0000000..fbc2512
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/bigLogo.svg
@@ -0,0 +1,20 @@
+
+
+ Created with Pixso.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FRONT/DEV/public/assets/images/bukvi.svg b/FRONT/DEV/public/assets/images/bukvi.svg
new file mode 100644
index 0000000..88ba39c
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/bukvi.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FRONT/DEV/public/assets/images/cancel.svg b/FRONT/DEV/public/assets/images/cancel.svg
new file mode 100644
index 0000000..a39ab7f
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/cancel.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/check.svg b/FRONT/DEV/public/assets/images/check.svg
new file mode 100644
index 0000000..a91a6c0
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/check.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/edit.svg b/FRONT/DEV/public/assets/images/edit.svg
new file mode 100644
index 0000000..f3f1b8e
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/edit.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/ethernet.svg b/FRONT/DEV/public/assets/images/ethernet.svg
new file mode 100644
index 0000000..ef4cdcd
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/ethernet.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/gossys.png b/FRONT/DEV/public/assets/images/gossys.png
new file mode 100644
index 0000000..5b01b0e
Binary files /dev/null and b/FRONT/DEV/public/assets/images/gossys.png differ
diff --git a/FRONT/DEV/public/assets/images/logo.svg b/FRONT/DEV/public/assets/images/logo.svg
new file mode 100644
index 0000000..4f3a4dd
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/logo.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FRONT/DEV/public/assets/images/signal.svg b/FRONT/DEV/public/assets/images/signal.svg
new file mode 100644
index 0000000..a589c67
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/signal.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/vite.svg b/FRONT/DEV/public/assets/images/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/assets/images/vpn.svg b/FRONT/DEV/public/assets/images/vpn.svg
new file mode 100644
index 0000000..8999c36
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/vpn.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/FRONT/DEV/public/assets/images/wifi.svg b/FRONT/DEV/public/assets/images/wifi.svg
new file mode 100644
index 0000000..dad2dd2
--- /dev/null
+++ b/FRONT/DEV/public/assets/images/wifi.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FRONT/DEV/public/locales/en/common.json b/FRONT/DEV/public/locales/en/common.json
new file mode 100644
index 0000000..966fec1
--- /dev/null
+++ b/FRONT/DEV/public/locales/en/common.json
@@ -0,0 +1,186 @@
+{
+ "userScanner-title": "Subnet Node Scanning",
+ "userScanner-visible": "Display Method",
+ "userScanner-loading": "Scanning nodes",
+ "userScanner-refresh": "Refresh List",
+ "userScanner-scan": "Scan Nodes",
+ "userScanner-notFound": "Nodes not found",
+ "tableScanner-online": "Online",
+ "tableScanner-name": "Name",
+ "tableScanner-vendor": "Vendor",
+ "tableScanner-ip": "IP Address",
+ "tableScanner-ports": "Open Ports",
+ "tableScanner-activity": "Last Activity",
+ "tableScanner-note": "Note",
+ "tableScanner-action": "Action",
+ "tableScanner-unknown": "Unknown",
+ "tableScanner-noData": "No data to display",
+ "listUsers-activeNodes": "Active Nodes",
+ "listUsers-inactiveNodes": "Inactive Nodes",
+ "ipInput-placeholder": "Enter IP",
+ "ipInput-error": "Enter a valid IP address",
+ "userScanner-visible_table": "Table",
+ "userScanner-visible_list": "List",
+
+ "wifiScanner-title": "Wi-Fi Access Point Scanning",
+ "wifiScanner-scanning": "Scanning access points",
+ "wifiScanner-scanError": "Network scanning error",
+ "wifiScanner-refresh": "Refresh List",
+ "wifiScanner-scan": "Scan Access Points",
+ "wifiModal-bssid": "BSSID",
+ "wifiModal-vendor": "Vendor",
+ "wifiModal-signal": "Signal",
+ "wifiModal-channel": "Channel",
+ "wifiModal-encryption": "Encryption",
+ "wifiModal-frequency": "Frequency",
+ "wifiModal-hidden": "Hidden Network",
+ "wifiModal-note": "Note",
+ "wifiModal-connect": "Connect",
+ "wifiModal-disconnect": "Disconnect",
+ "wifiModal-unknown": "Unknown",
+ "wifiModal-none": "None",
+ "common-yes": "Yes",
+ "common-no": "No",
+
+ "addNode": "Add Node",
+ "enterNodeAddress": "Enter node address...",
+ "connectionLost": "Connection Lost",
+ "serverDisconnected": "Server connection interrupted",
+ "checkAddressAndRetry": "Please check the address availability and try again",
+ "cancel": "Cancel",
+ "close": "Close",
+ "save": "Save",
+ "retry": "Retry",
+ "connect": "Connect",
+ "submit": "Submit",
+ "common-back": "Back",
+ "error": "Error",
+ "networkConnection": "Network Connection",
+ "enterPassword": "Enter password...",
+ "connectingAttempt": "Attempting to connect to the network",
+ "connectionError": "Network connection error",
+ "connectionErrorLog": "Connection error: ",
+ "customizeTable-title": "Subnet Node Table Customization",
+
+ "wifiDisconnect-title": "Wi-Fi Network Disconnection",
+ "wifiDisconnect-confirm": "Confirm Disconnection",
+ "wifiDisconnect-message1": "You are about to disconnect from the network - ",
+ "wifiDisconnect-message2": " All active connections will be interrupted.",
+ "wifiDisconnect-disconnect": "Disconnect",
+ "wifiDisconnect-progress": "Disconnecting from the network",
+
+ "ipChange-title": "IP Address Change",
+ "ipChange-checkingIp": "Checking IP address validity",
+ "ipChange-generalInfo": "General Information",
+ "ipChange-interface": "Interface",
+ "ipChange-ipInfo": "IP Information",
+ "ipChange-currentIp": "Current IP",
+ "ipChange-currentGateway": "Current Gateway",
+ "ipChange-currentDns": "Current DNS",
+ "ipChange-newIp": "New IP",
+ "ipChange-newGateway": "New Gateway",
+ "ipChange-newDns": "New DNS",
+ "ipChange-checkError": "An error occurred while checking validity",
+ "ipChange-adminRequired": "To change network settings, you must log in as an administrator",
+
+ "note-edit": "Edit Note",
+ "note-add": "Add Note",
+ "note-enter": "Enter note...",
+
+ "auth-successLogin": "Successful Login",
+ "auth-successLogout": "Successful Logout",
+ "auth-getAccess": "Obtaining Access Rights",
+ "auth-enableAdmin": "Enable Administrative Access",
+ "auth-enterAdminPass": "Enter administrator password...",
+ "auth-wrongPass": "Incorrect operator password",
+ "auth-switchToLimited": "Switch to Limited Access",
+ "auth-limitedModeDesc": "In this mode, administrator privileges are restricted.",
+ "auth-restrictAccess": "Restrict Access",
+ "auth-closeHint": "This window can be closed, or it will disappear on its own",
+
+ "operatorConfig-title": "Node Operator Configuration",
+ "operatorConfig-instruction": "Before starting, you need to create an application operator password",
+ "operatorConfig-passwordPlaceholder": "Enter password...",
+ "operatorConfig-repeatPasswordPlaceholder": "Repeat password...",
+ "operatorConfig-passwordMismatch": "Passwords do not match!",
+ "operatorConfig-submit": "Submit",
+
+ "about-logoAlt": "Gossys Logo",
+ "about-title": "About the Application",
+ "about-description": "GoNets is a minimally viable cross-platform product (MVP) for demonstrating our approach to managing PC settings in a corporate environment.",
+ "about-featuresTitle": "Features",
+ "about-feature1": "Visualization of active network resources",
+ "about-feature2": "Configuration of local and remote network resource parameters",
+ "about-feature3": "Management of resource interaction with the GoNets Client",
+ "about-disclaimer": "The software is for demonstration and home use only.",
+ "about-goToPro": "Go to GoNets-PRO",
+ "about-homepage": "Homepage",
+ "about-documentation": "Documentation",
+ "about-license": "License",
+ "about-copyright": "Developed and maintained by Hybrid Office Systems LLC, 2024-2025. All rights reserved.",
+
+ "main-logoAlt": "Application Logo",
+ "main-networkSettings": "Network Settings",
+ "main-about": "About the Program",
+
+ "nodes-availableNodes": "Available Nodes",
+ "nodes-rootAddress": "Root Address",
+ "nodes-name": "Name",
+ "nodes-loading": "Loading",
+ "nodes-additionalNodes": "Additional Nodes",
+ "nodes-node": "Node",
+ "nodes-address": "Address",
+ "nodes-connectionError": "Failed to connect to the server at the address ",
+
+ "settings-nodesSection": "Subnet Nodes",
+ "settings-intervalScanning": "Interval Scanning",
+ "settings-scanInterval": "Node Scan Interval (min)",
+ "settings-displayNames": "Display Names When Missing",
+ "settings-ipAddress": "IP Address",
+ "settings-unknown": "Unknown",
+ "settings-rowsPerPage": "Rows Per Table",
+ "settings-displaySettings": "Nodes Display",
+ "settings-configure": "Configure",
+ "settings-clearSessionData": "Clear Session Data",
+ "settings-clear": "Clear",
+ "settings-clearFailed": "Data was not deleted: ",
+ "settings-exportData": "Export Nodes Data",
+ "settings-export": "Export",
+ "settings-saveNodesTitle": "Save Nodes Data",
+ "settings-exportSuccess": "Data exported successfully!",
+ "settings-exportError": "Export error: ",
+ "settings-status": "Online",
+ "settings-name": "Name",
+ "settings-vendor": "Manufacturer",
+ "settings-openPorts": "Open Ports",
+ "settings-lastActivity": "Last Activity",
+ "settings-note": "Note",
+ "settings-action": "Action",
+
+ "settings-title": "Settings",
+ "settings-resetSettings": "Reset Settings",
+ "settings-resetConfirm": "Are you sure you want to reset all settings to default values?",
+ "settings-cancel": "Cancel",
+ "settings-save": "Save",
+ "settings-fixErrors": "Fix errors before saving",
+ "settings-fillRequired": "Fill in all required fields",
+ "settings-dataNotDeleted": "Data was not deleted: ",
+ "settings-saveNetworksTitle": "Save Networks Data",
+ "settings-fieldRequired": "Field cannot be empty",
+ "settings-enterNumber": "Please enter a number",
+ "settings-valueTooSmall": "Value cannot be less than ",
+ "settings-valueTooLarge": "Value cannot be greater than ",
+
+ "settings-sections-subnetNodes": "Subnet Nodes",
+ "settings-sections-accessPoints": "Access Points",
+ "settings-sections-appearance": "Appearance",
+
+ "settings-exportNetworkData": "Export Access Points Data",
+ "settings-colorTheme": "Color Theme",
+ "settings-light": "Light",
+ "settings-dark": "Dark",
+ "settings-system": "As in System",
+ "settings-interfaceLanguage": "Interface Language",
+ "settings-russian": "Russian",
+ "settings-english": "English"
+}
diff --git a/FRONT/DEV/public/locales/ru/common.json b/FRONT/DEV/public/locales/ru/common.json
new file mode 100644
index 0000000..144e1d4
--- /dev/null
+++ b/FRONT/DEV/public/locales/ru/common.json
@@ -0,0 +1,192 @@
+ {
+ "userScanner-title": "Сканирование узлов подсети",
+ "userScanner-visible": "Способ отображения",
+ "userScanner-loading": "Cканирование узлов",
+ "userScanner-refresh": "Обновить список",
+ "userScanner-scan": "Сканировать узлы",
+ "userScanner-notFound": "Узлы не найдены",
+ "tableScanner-online": "Онлайн",
+ "tableScanner-name": "Имя",
+ "tableScanner-vendor": "Производитель",
+ "tableScanner-ip": "IP адрес",
+ "tableScanner-ports": "Открытые порты",
+ "tableScanner-activity": "Последняя активность",
+ "tableScanner-note": "Заметка",
+ "tableScanner-action": "Действие",
+ "tableScanner-unknown": "Неизвестно",
+ "tableScanner-noData": "Нет данных для отображения",
+ "listUsers-activeNodes": "Активные узлы",
+ "listUsers-inactiveNodes": "Неактивные узлы",
+ "ipInput-placeholder": "Введите IP",
+ "ipInput-error": "Введите корректный IP-адрес",
+ "userScanner-visible_table": "Таблица",
+ "userScanner-visible_list": "Список",
+
+ "wifiScanner-title": "Сканирование точек доступа Wi-Fi",
+ "wifiScanner-scanning": "Сканирование точек доступа",
+ "wifiScanner-scanError": "Ошибка сканирования сети",
+ "wifiScanner-refresh": "Обновить список",
+ "wifiScanner-scan": "Сканировать точки доступа",
+ "wifiModal-bssid": "BSSID",
+ "wifiModal-vendor": "Производитель",
+ "wifiModal-signal": "Сигнал",
+ "wifiModal-channel": "Канал",
+ "wifiModal-encryption": "Шифрование",
+ "wifiModal-frequency": "Частота",
+ "wifiModal-hidden": "Скрытая сеть",
+ "wifiModal-note": "Заметка",
+ "wifiModal-connect": "Подключиться",
+ "wifiModal-disconnect": "Отключиться",
+ "wifiModal-unknown": "Неизвестно",
+ "wifiModal-none": "Отсутствует",
+ "common-yes": "Да",
+ "common-no": "Нет",
+
+ "addNode": "Добавление узла",
+ "enterNodeAddress": "Введите адрес узла...",
+ "connectionLost": "Соединение прервано",
+ "serverDisconnected": "Произошёл разрыв с сервером",
+ "checkAddressAndRetry": "Пожалуйста, проверьте доступ к адресу и попробуйте снова",
+ "cancel": "Отменить",
+ "close": "Закрыть",
+ "save": "Сохранить",
+ "retry": "Повторить",
+ "connect": "Подключиться",
+ "common-back": "Назад",
+ "submit": "Подтвердить",
+ "error": "Ошибка",
+ "networkConnection": "Подключение к сети",
+ "enterPassword": "Введите пароль...",
+ "connectingAttempt": "Попытка подключения к сети",
+ "connectionError": "Ошибка подключения к сети",
+ "connectionErrorLog": "Ошибка подключения: ",
+ "customizeTable-title": "Кастомизация таблицы узлов подсети",
+
+ "wifiDisconnect-title": "Отключение от Wi-Fi сети",
+ "wifiDisconnect-confirm": "Подтверждение отключения",
+ "wifiDisconnect-message1": "Вы собираетесь отключиться от сети - ",
+ "wifiDisconnect-message2": " Все активные подключения будут прерваны.",
+ "wifiDisconnect-disconnect": "Отключиться",
+ "wifiDisconnect-progress": "Отключение от сети",
+
+ "ipChange-title": "Изменение IP-Адреса",
+ "ipChange-checkingIp": "Идет проверка корректности IP адреса",
+ "ipChange-generalInfo": "Общие сведения",
+ "ipChange-interface": "Интерфейс",
+ "ipChange-ipInfo": "Сведения об IP",
+ "ipChange-currentIp": "Текущий ip",
+ "ipChange-currentGateway": "Текущий шлюз",
+ "ipChange-currentDns": "Текущий DNS",
+ "ipChange-newIp": "Новый ip",
+ "ipChange-newGateway": "Новый шлюз",
+ "ipChange-newDns": "Новый DNS",
+ "ipChange-checkError": "Произошла ошибка проверки корректности",
+ "ipChange-adminRequired": "Для изменения сетевых настроек необходимо авторизироваться под администратором",
+
+ "note-edit": "Редактирование заметки",
+ "note-add": "Добавление заметки",
+ "note-enter": "Введите заметку...",
+
+ "auth-successLogin": "Успешный вход",
+ "auth-successLogout": "Успешный выход",
+ "auth-getAccess": "Получение прав доступа",
+ "auth-enableAdmin": "Включить административный доступ",
+ "auth-enterAdminPass": "Введите пароль администратора...",
+ "auth-wrongPass": "Неверный пароль оператора",
+ "auth-switchToLimited": "Переключиться на ограниченный доступ",
+ "auth-limitedModeDesc": "В данном режиме доступа ограничиваются полномочия администратора.",
+ "auth-restrictAccess": "Ограничить доступ",
+ "auth-closeHint": "Это окно можно закрыть, либо оно пропадёт само",
+
+ "operatorConfig-title": "Настройка оператора узла",
+ "operatorConfig-instruction": "Перед началом работы необходимо создать пароль оператора приложения",
+ "operatorConfig-passwordPlaceholder": "Введите пароль...",
+ "operatorConfig-repeatPasswordPlaceholder": "Повторите пароль...",
+ "operatorConfig-passwordMismatch": "Пароли не совпадают!",
+ "operatorConfig-submit": "Отправить",
+
+ "about-logoAlt": "Логотип Gossys",
+ "about-title": "О приложении",
+ "about-description": "ГоNets - минимально работоспособный кроссплатформенный продукт (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.",
+ "about-featuresTitle": "Возможности",
+ "about-feature1": "Визуализация активных сетевых ресурсов",
+ "about-feature2": "Настройка параметров локальных и удалённых сетевых ресурсов",
+ "about-feature3": "Управление взаимодействием ресурсов с Клиентом ГoNets",
+ "about-disclaimer": "Программное обеспечение исключительно для ознакомления и домашнего использования.",
+ "about-goToPro": "Перейти на ГоNets-PRO",
+ "about-homepage": "Домашняя страница",
+ "about-documentation": "Документация",
+ "about-license": "Лицензия",
+ "about-copyright": "Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.",
+
+ "main-logoAlt": "Логотип приложения",
+ "main-networkSettings": "Настройки сети",
+ "main-about": "О программе",
+
+ "nodes-availableNodes": "Доступные узлы",
+ "nodes-rootAddress": "Корневой адрес",
+ "nodes-name": "Имя",
+ "nodes-loading": "Загрузка",
+ "nodes-additionalNodes": "Дополнительные узлы",
+ "nodes-node": "Узел",
+ "nodes-address": "Адрес",
+ "nodes-connectionError": "Не удалось подключиться к серверу по адресу ",
+
+ "settings-nodesSection": "Узлы подсети",
+ "settings-intervalScanning": "Интервальное сканирование",
+ "settings-scanInterval": "Интервал сканирования узлов (мин)",
+ "settings-displayNames": "Отображаемые имена при отсутствии",
+ "settings-ipAddress": "IP адрес",
+ "settings-unknown": "Неизвестно",
+ "settings-rowsPerPage": "Количество строк в таблице",
+ "settings-displaySettings": "Отображение узлов",
+ "settings-configure": "Настроить",
+ "settings-clearSessionData": "Очистить данные сессии",
+ "settings-clear": "Очистить",
+ "settings-clearFailed": "Данные не были удалены: ",
+ "settings-exportData": "Экспорт данных узлов",
+ "settings-export": "Экспортировать",
+ "settings-saveNodesTitle": "Сохранить данные узлов",
+ "settings-exportSuccess": "Данные успешно экспортированы!",
+ "settings-exportError": "Ошибка экспорта: ",
+ "settings-status": "Онлайн",
+ "settings-name": "Имя",
+ "settings-vendor": "Производитель",
+ "settings-openPorts": "Открытые порты",
+ "settings-lastActivity": "Последняя активность",
+ "settings-note": "Заметка",
+ "settings-action": "Действие",
+
+ "settings-title": "Настройки",
+ "settings-resetSettings": "Сбросить настройки",
+ "settings-resetConfirm": "Вы уверены, что хотите сбросить все настройки к значениям по умолчанию?",
+ "settings-cancel": "Отменить",
+ "settings-save": "Сохранить",
+ "settings-fixErrors": "Исправьте ошибки перед сохранением",
+ "settings-fillRequired": "Заполните все обязательные поля",
+ "settings-dataNotDeleted": "Данные не были удалены: ",
+ "settings-saveNetworksTitle": "Сохранить данные сетей",
+ "settings-fieldRequired": "Поле не может быть пустым",
+ "settings-enterNumber": "Пожалуйста, введите число",
+ "settings-valueTooSmall": "Значение не может быть меньше ",
+ "settings-valueTooLarge": "Значение не может быть больше ",
+
+ "settings-sections-subnetNodes": "Узлы подсети",
+ "settings-sections-accessPoints": "Точки доступа",
+ "settings-sections-appearance": "Внешний вид",
+
+ "settings-exportNetworkData": "Экспорт данных точек доступа",
+ "settings-colorTheme": "Цветовая тема",
+ "settings-light": "Светлая",
+ "settings-dark": "Тёмная",
+ "settings-system": "Как в системе",
+ "settings-interfaceLanguage": "Язык интерфейса",
+ "settings-russian": "Русский",
+ "settings-english": "Английский"
+
+
+
+ }
+
+
+
diff --git a/FRONT/DEV/src/api/request.ts b/FRONT/DEV/src/api/request.ts
new file mode 100644
index 0000000..822930d
--- /dev/null
+++ b/FRONT/DEV/src/api/request.ts
@@ -0,0 +1,196 @@
+import axios, { AxiosResponse } from "axios";
+
+const axiosWithoutCredentials = axios.create({
+ baseURL: `http://localhost:8001`,
+})
+
+export class NetworkService {
+ static createAxiosInstance(ipAddress: string) {
+ return axios.create({
+ baseURL: `http://${ipAddress}:8001`,
+ });
+ }
+
+ static async checkIp(ipAddress: string, data: any): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post('/ip/check_ip', data);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async saveIp(ipAddress: string, data: any): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post('/ip/apply_ip', data);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async getHostname(): Promise{
+ try{
+ const response = await axiosWithoutCredentials.get('/network/get_hostname')
+ return response
+ } catch (error: any){
+ return error.response
+ }
+ }
+
+ static async createUser(ipAddress: string, data: any): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post('/auth/register', data);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async login(ipAddress: string, data: any): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post('/auth/login', data);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async logout(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.get('/auth/logout');
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async checkServerConf(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.get('/auth/check_json');
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async whoami(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.get('/auth/me');
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async scanUser(ipAddress: string, ipScanning: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post('/subnet/devices',{
+ subnet: ipScanning,
+ });
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ static async scanWifi(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.get('/subnet/wifi');
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async addNote(ipAddress: string, address:string, note: string,type:string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/subnet/${type}/${address}/note`, {
+ note: note
+ });
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async editNote(ipAddress: string, address:string, note: string,type:string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.put(`/subnet/${type}/${address}/note`, {
+ note: note
+ });
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async deleteNote(ipAddress: string, address:string, type:string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.delete(`/subnet/${type}/${address}/note`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async statusNetwork(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.get(`/subnet/wifi/status`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async connectNetwork(ipAddress: string, password:string, address: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/subnet/wifi/connect`, {
+ ssid: address,
+ password: password,
+ });
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async disconnectNetwork(ipAddress: string, ssid: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/subnet/wifi/disconnect`, {
+ ssid: ssid,
+ });
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+
+ // Settings
+ static async clearSubnet(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/settings/clear_subnet`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async clearNetwork(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/settings/clear_wifi`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async exportDevices(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/settings/export_devices`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+ static async exportNetwork(ipAddress: string): Promise {
+ try {
+ const axiosInstance = this.createAxiosInstance(ipAddress);
+ return await axiosInstance.post(`/settings/export_wifi`);
+ } catch (error: any) {
+ return error.response;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/assets/css/flow.css b/FRONT/DEV/src/assets/css/flow.css
new file mode 100644
index 0000000..0a79079
--- /dev/null
+++ b/FRONT/DEV/src/assets/css/flow.css
@@ -0,0 +1,108 @@
+.react-flow {
+ /* Custom Variables */
+ --xy-theme-selected: #F57DBD;
+ --xy-theme-hover: #C5C5C5;
+ --xy-theme-edge-hover: black;
+ --xy-theme-color-focus: #E8E8E8;
+
+ /* Built-in Variables see https://reactflow.dev/learn/customization/theming */
+ --xy-node-border-default: 1px solid #EDEDED;
+
+ /* --xy-node-boxshadow-default:
+ 0px 3.54px 4.55px 0px #00000005,
+ 0px 3.54px 4.55px 0px #0000000D,
+ 0px 0.51px 1.01px 0px #0000001A; */
+
+ --xy-node-border-radius-default: 8px;
+
+ --xy-handle-background-color-default: #ffffff;
+ --xy-handle-border-color-default: #AAAAAA;
+
+ --xy-edge-label-color-default: #505050;
+}
+
+.react-flow.dark {
+ --xy-node-boxshadow-default:
+ 0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.05), /* light shadow */
+ 0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.13), /* medium shadow */
+ 0px 0.51px 1.01px 0px rgba(255, 255, 255, 0.2); /* smallest shadow */
+ --xy-theme-color-focus: #535353;
+}
+
+/* Customizing Default Theming */
+
+.react-flow__node {
+ /* box-shadow: var(--xy-node-boxshadow-default);
+ border-radius: var(--xy-node-border-radius-default);
+ background-color: var(--xy-node-background-color-default); */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 12px;
+ flex-direction: column;
+ /* border: var(--xy-node-border-default);
+ color: var(--xy-node-color, var(--xy-node-color-default)); */
+ pointer-events: all;
+ user-select: none;
+}
+
+.react-flow__node.selectable:focus {
+ box-shadow: 0px 0px 0px 4px var(--xy-theme-color-focus);
+ border-color: #d9d9d9;
+}
+
+.react-flow__node.selectable:focus:active {
+ box-shadow: var(--xy-node-boxshadow-default);
+}
+
+
+.react-flow__node.selectable:hover,
+.react-flow__node.draggable:hover {
+ border-color: var(--xy-theme-hover);
+}
+
+.react-flow__node.selectable.selected {
+ border-color: var(--xy-theme-selected);
+ box-shadow: var(--xy-node-boxshadow-default);
+}
+
+.react-flow__node-group {
+ background-color: rgba(207, 182, 255, 0.4);
+ border-color: #9E86ED;
+}
+
+.react-flow__edge.selectable:hover .react-flow__edge-path,
+.react-flow__edge.selectable.selected .react-flow__edge-path {
+ stroke: var(--xy-theme-edge-hover);
+}
+
+.react-flow__handle {
+ width: 15px;
+ height: 15px;
+}
+
+.react-flow__handle.connectionindicator:hover {
+ pointer-events: all;
+ border-color: var(--xy-theme-edge-hover);
+ background-color: white;
+}
+
+.react-flow__handle.connectionindicator:focus ,
+.react-flow__handle.connectingfrom,
+.react-flow__handle.connectingto {
+ border-color: var(--xy-theme-edge-hover);
+}
+
+.react-flow__node-resizer {
+ border-radius: 0;
+ border: none;
+}
+
+.react-flow__resize-control.handle {
+ background-color: #ffffff;
+ border-color: #9E86ED;
+ border-radius: 0;
+ width: 5px;
+ height: 5px;
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/assets/css/index.css b/FRONT/DEV/src/assets/css/index.css
new file mode 100644
index 0000000..c597aa3
--- /dev/null
+++ b/FRONT/DEV/src/assets/css/index.css
@@ -0,0 +1,36 @@
+html,
+body {
+ margin: 0;
+ font-family: sans-serif;
+ box-sizing: border-box;
+ min-height: 100%;
+ height: 100%;
+}
+
+#root{
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+#app {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+button{
+ background-color: #006666;
+ border: none;
+ padding: 6px 10px;
+ border-radius: 8px;
+ color: white;
+ font-size: 14px;
+ cursor: pointer;
+
+ &:hover{
+ background-color: #255252;
+ }
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx b/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx
new file mode 100644
index 0000000..17e53c7
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx
@@ -0,0 +1,138 @@
+import React, {useState} from 'react';
+import styled, { css } from 'styled-components';
+import { DeviceUser } from "../../../store/useDeviceStore";
+import {buttonNames} from "../../../const/buttons/names";
+import {ModalMap} from "../../modalwindows/windowsType";
+import {IconComponent} from "../../../const/buttons/icons";
+import {useModalContext} from "../../../store";
+import {useNameSetting} from "../../../hooks/useSettings";
+
+interface DeviceCardProps {
+ device: DeviceUser;
+ status: 'online' | 'offline';
+}
+
+const CardWrapper = styled.div<{ status: 'online' | 'offline' }>`
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ margin-bottom: 16px;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
+ }
+`;
+
+const CardHeader = styled.div<{ status: 'online' | 'offline' }>`
+ background: ${({ status, theme }) =>
+ status === 'online' ? theme.successColor : theme.offlineColor};
+ color: white;
+ padding: 16px;
+ font-size: 18px;
+ font-weight: 500;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+
+const CardContent = styled.div<{ isExpanded: boolean }>`
+ max-height: 0;
+ opacity: 0;
+ padding: 0 16px;
+ transition: all 0.3s ease;
+ color: #333;
+ font-size: 14px;
+ display: grid;
+ gap: 12px;
+
+ ${({ isExpanded }) => isExpanded && css`
+ max-height: 500px;
+ opacity: 1;
+ padding: 16px;
+ `}
+`;
+
+const InfoRow = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+const Label = styled.span`
+ font-weight: 500;
+ color: #666;
+`;
+
+const Value = styled.span`
+ color: #333;
+`;
+
+
+const DeviceCard: React.FC = ({ device, status }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const { openModal } = useModalContext();
+ const nameSetting = useNameSetting();
+
+ const openModalNote = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ openModal(ModalMap['6'], {
+ address: device.mac,
+ currentNote: device.note || ''
+ });
+ };
+
+
+ return (
+ setIsExpanded(!isExpanded)}>
+
+
+ MAC: {device.mac}
+
+
+
+
+
+
+
+
+ Имя:
+ {device.hostname || (nameSetting === 'ip' ? device.ip : 'Неизвестно')}
+
+
+
+ Производитель:
+ {device.vendor || 'Неизвестно'}
+
+
+
+ IP адрес:
+ {device.ip}
+
+
+ {device.open_ports?.length > 0 && (
+
+ Открытые порты:
+ {device.open_ports.join(', ')}
+
+ )}
+
+
+ Последняя активность:
+ {device.last_seen}
+
+
+
+ Заметка:
+ {device.note || '-'}
+
+
+
+ );
+};
+
+export default DeviceCard;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx b/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx
new file mode 100644
index 0000000..d1e90c5
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx
@@ -0,0 +1,130 @@
+import React, {useEffect, useRef, useState} from 'react';
+import styled from "styled-components";
+import {buttonNames} from "../../../const/buttons/names";
+import {IconComponent} from "../../../const/buttons/icons";
+
+type Option = {
+ value: number,
+ label: string,
+}
+
+interface DropdownTypeProps {
+ options: Option[];
+ label?: string;
+ onChange?: (selectedItem: Option) => void;
+ value: Option | null;
+}
+
+const DropdownWrapper = styled.div`
+ position: absolute;
+ top: 18px;
+ right:20px;
+ display: inline-block;
+ background-color: ${({theme}) => theme.background};
+ padding: 12px 16px;
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ z-index: 10;
+`
+const Label = styled.p`
+ color: ${({theme}) => theme.textColor};
+ font-size: 16px;
+`
+
+const Dropdown = styled.div`
+ background-color: ${({theme}) => theme.primary};
+ border-radius: 15px;
+ overflow: hidden;
+ transition: all .3s ease;
+ cursor: pointer;
+ z-index: 10;
+ margin-top: 6px;
+`
+
+const DropdownHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ gap: 14px;
+ padding: 12px 24px;
+ font-size: 16px;
+ align-items: center;
+ height: 100%;
+ min-width: 200px;
+`
+
+const DropdownContent = styled.div<{$isOpen: boolean}>`
+ max-height: ${({$isOpen}) => $isOpen ? '500px' : '0'};
+ opacity: ${({$isOpen}) => $isOpen ? 1 : 0};
+ transition: all .3s ease;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+`
+
+const DropdownRow = styled.button`
+ padding: 12px 24px;
+ background-color: transparent;
+ text-align: start;
+ font-size: 16px;
+ border: none;
+ width: 100%;
+ color: ${({theme}) => theme.textColor};
+ transition: all .3s ease;
+ cursor: pointer;
+
+ &:hover {
+ background-color: ${({theme}) => theme.background};
+ }
+`
+
+const DropdownArrow = styled.div<{ $isOpen: boolean }>`
+ transition: transform 0.3s ease;
+ transform: rotate(${({ $isOpen }) => ($isOpen ? "90deg" : "0deg")});
+`;
+
+const DropdownType = ({options, value, onChange, label}: DropdownTypeProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ function toggleOpen() {
+ setIsOpen((prev) => !prev);
+ }
+
+ function handleSelect(option: Option) {
+ onChange?.(option);
+ setIsOpen(false);
+ }
+
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ }
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ return (
+
+ {label}
+
+
+ {value?.label || 'Select an option'}
+
+
+
+
+
+ {options.map((option, i) => (
+ handleSelect(option)}>
+ {option.label}
+
+ ))}
+
+
+
+ );
+};
+
+export default DropdownType;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/Pagination.tsx b/FRONT/DEV/src/components/UI/Pagination.tsx
new file mode 100644
index 0000000..08ed767
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/Pagination.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import styled from "styled-components";
+import {buttonNames} from "../../const/buttons/names";
+import {IconComponent} from "../../const/buttons/icons";
+
+const PaginateWrapper = styled.div`
+ width: 100%;
+ margin-top: 24px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+`
+const Page = styled.button<{ active?: boolean }>`
+ width: 36px;
+ height: 36px;
+ background: ${(props) => props.active ? props.theme.borderColor : props.theme.primary};
+ color: ${(props) => props.active ? props.theme.primary : props.theme.textColor};
+ border: 1px solid ${(props => props.theme.borderColor)};
+ font-size: 16px;
+ cursor: pointer;
+ border-radius: 4px;
+
+ transition: .4s ease;
+ &:hover {
+ background: ${(props) => props.active ? props.theme.textColor : props.theme.background};;
+ }
+`
+
+interface PaginationProps {
+ currentPage: number;
+ totalPages: number;
+ onPrevPage: () => void;
+ onNextPage: () => void;
+ onPageChange: (page: number) => void;
+}
+
+const Pagination = ({currentPage, totalPages, onPrevPage, onNextPage, onPageChange}: PaginationProps) => {
+
+ const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
+
+ return (
+
+
+
+
+ {pageNumbers.map((page, i) => (
+ onPageChange(page)}
+ active={currentPage === page}
+ >
+ {page}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default Pagination;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/animDot.tsx b/FRONT/DEV/src/components/UI/animDot.tsx
new file mode 100644
index 0000000..48bee7e
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/animDot.tsx
@@ -0,0 +1,29 @@
+import styled, { keyframes } from "styled-components";
+
+const blink = keyframes`
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 1; }
+`;
+
+interface DotProps {
+ $delay: number;
+}
+
+const Dot = styled.span`
+ animation: ${blink} 1s infinite;
+ animation-delay: ${({ $delay }) => $delay}s;
+ margin: 0;
+`;
+
+const Wrapper = styled.div`
+ display: flex;
+ gap: 4px;
+`;
+
+export const AnimDot = () => (
+
+ .
+ .
+ .
+
+);
diff --git a/FRONT/DEV/src/components/UI/buttons/mainButton.tsx b/FRONT/DEV/src/components/UI/buttons/mainButton.tsx
new file mode 100644
index 0000000..a1bbfca
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/buttons/mainButton.tsx
@@ -0,0 +1,75 @@
+import styled from "styled-components";
+import { useTheme } from "styled-components";
+import { IconComponent } from "../../../const/buttons/icons";
+
+const Button = styled.button.withConfig({
+ shouldForwardProp: (prop) => !['color', '$hoverсolor', 'size', '$iconpos'].includes(prop),
+ })<{ color: string; size?: string, $hoverсolor: string, $iconpos: string }>`
+ background-color: ${(props) => props.color};
+ border: none;
+ padding: ${(props) =>
+ props.size === "small"
+ ? "10px 28px"
+ : props.size === "big"
+ ? "10px 20px"
+ : "12px 20px"}; // По умолчанию
+ border-radius: 8px;
+ color: white;
+ font-size: ${(props) =>
+ props.size === "small"
+ ? "16px"
+ : props.size === "big"
+ ? "18px"
+ : "16px"}; // По умолчанию
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: ${(props) =>
+ props.$iconpos === 'right'
+ ? 'row-reverse'
+ : 'row'};
+ gap: 10px;
+ transition: all .3s ease;
+ &:hover{
+ background-color: ${(props) => props.$hoverсolor};
+ }
+`;
+
+type Props = {
+ color?: string;
+ hoverColor?: string
+ size?: "small" | "big";
+ title: string
+ onClick?: () => void;
+ type?: "button" | "submit" | "reset" | undefined
+ icon?: string
+ iconPosition?: 'left' | 'right'
+ iconSize?: {
+ width: string,
+ height: string
+ }
+ style?: any
+};
+
+export const MainButton = ({ color, size, title, hoverColor, type, icon, iconPosition, iconSize, onClick, style }: Props) => {
+ const theme = useTheme();
+ const defaultColor: string = theme.accent;
+ const defaultHoverColor: string = theme.accentHover
+
+
+ return (
+
+ {icon && }
+ {title}
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/buttons/quitButton.tsx b/FRONT/DEV/src/components/UI/buttons/quitButton.tsx
new file mode 100644
index 0000000..373655f
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/buttons/quitButton.tsx
@@ -0,0 +1,45 @@
+import { MainButton } from "./mainButton";
+import styled from "styled-components";
+import { buttonNames } from "../../../const/buttons/names";
+import { IconComponent as Icon } from '../../../const/buttons/icons';
+import { useWorkSpaceStore } from "../../../store";
+
+type Props = {
+ size: "small" | "big";
+};
+
+const Container = styled.div<{ size: "small" | "big" }>`
+ ${(props) => props.size === "small" && "width: 100%;"}
+`;
+
+const Button = styled.div`
+ width: 100%;
+ cursor: pointer;
+ color: ${(props) => props.theme.textColor};
+`;
+
+const IconContainer = styled.div`
+ padding: 10px;
+`;
+
+export const QuitButton = ({ size }: Props) => {
+ const handleExit = () => {
+ window.electron.quitApp();
+ };
+
+ const { setWorkModule } = useWorkSpaceStore()
+
+ return (
+ setWorkModule('helloWindow')}>
+ {size === "big" ? (
+
+ ) : (
+
+
+
+
+
+ )}
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/buttons/toggletheme.tsx b/FRONT/DEV/src/components/UI/buttons/toggletheme.tsx
new file mode 100644
index 0000000..f1ca231
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/buttons/toggletheme.tsx
@@ -0,0 +1,26 @@
+import styled, { useTheme } from "styled-components";
+import { IconComponent as Icon } from '../../../const/theme/icons';
+import { ThemName } from "../../../const/theme/names";
+
+const Button = styled.div`
+ width: 100%;
+ cursor: pointer;
+`
+
+const IconContainer = styled.div`
+ padding: 10px;
+ color: ${props => props.theme.text};
+`
+
+export default function ThemeButton() {
+ const theme = useTheme();
+ const name = localStorage.getItem('theme') as ThemName
+
+ return (
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/inputs/input.tsx b/FRONT/DEV/src/components/UI/inputs/input.tsx
new file mode 100644
index 0000000..dd2b9c9
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/inputs/input.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import styled from 'styled-components';
+
+type Props = {
+ placeholderText: string;
+ id: string;
+ value?: string;
+ onChange?: (value: string) => void;
+ onFileChange?: (event: React.ChangeEvent) => void;
+ type?: string;
+ multiple?: boolean;
+ style?: React.CSSProperties;
+ search?: boolean;
+ onFocus?: any
+ onBlur?: any
+ $size?: string
+}
+
+const InputContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex: 3;
+
+`;
+
+const StyledInput = styled.input`
+ padding: 8px 10px;
+ color: black;
+ border-radius: 8px;
+ border: 1px solid #A1A1A1;
+ background-color: transparent;
+ &:focus{
+ border: 1px solid #adadad;
+ }
+ width: 100%;
+ font-size: 18px;
+ color: ${props=>props.theme.textColor}
+`;
+
+const Input = React.forwardRef(({ placeholderText, id, value, onChange, onFileChange, type = "text", multiple, style, onFocus, onBlur }, ref) => {
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ if (onChange) {
+ onChange(value);
+ }
+ };
+
+ return (
+
+
+
+ );
+});
+
+export default Input;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/loader.tsx b/FRONT/DEV/src/components/UI/loader.tsx
new file mode 100644
index 0000000..c13bd3f
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/loader.tsx
@@ -0,0 +1,101 @@
+type Props = {
+ loaderText: string; //Тип передаваемых данных loaderText - string
+}
+
+import styled, { keyframes } from 'styled-components'
+import { AnimDot } from './animDot';
+
+
+//Константа которая устанавливает кейфвреймы для вращения
+const spinAnimation = keyframes`
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+`
+
+//Стиль экрана загрузки
+export const LoaderContainer = styled.div`
+ top: 0px;
+ z-index:998;
+ background-color: rgba(0,0,0, 0.7);
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ overflow-y: hidden;
+`
+
+
+//Стиль блока с элементами загрузки
+export const Loaderblock = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+
+ p{
+ font-size: 24px;
+ color: white;
+ }
+`
+//Стиль для блока с кольцом загрузки
+export const LoadingSpinner = styled.div`
+ position: relative;
+ width: 65px;
+ height: 65px;
+`
+
+//Стиль для кольца загрузки
+export const LoadingRing = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 65px;
+ height: 65px;
+ border-radius: 50%;
+ border: 12px solid white;
+`
+//Стиль для части кольца загрузки, которая будет вращаться
+export const LoadingPart = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 65px;
+ height: 65px;
+ border-radius: 50%;
+ border: 12px solid transparent;
+ border-top: 12px solid ${props => props.theme.accent};
+
+ // Добавление аниманции и использование кейфремов
+ animation: ${spinAnimation} 1s linear infinite;
+`
+
+const Text = styled.div`
+ display: flex;
+ gap: 3px;
+ font-size: 24px;
+ color: white;
+ margin-top: 30px;
+ margin-left: 25px;
+`
+
+export default function Loader({ loaderText }: Props) {
+ return (
+
+
+
+
+
+
+
+
+ {/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
+ {loaderText}
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/routerLoader.tsx b/FRONT/DEV/src/components/UI/routerLoader.tsx
new file mode 100644
index 0000000..9a788f4
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/routerLoader.tsx
@@ -0,0 +1,103 @@
+type Props = {
+ loaderText: string; //Тип передаваемых данных loaderText - string
+}
+
+import styled, { keyframes } from 'styled-components'
+import { AnimDot } from './animDot';
+import { ModalWindow } from '../modalwindows/style';
+
+
+//Константа которая устанавливает кейфвреймы для вращения
+const spinAnimation = keyframes`
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+`
+
+//Стиль экрана загрузки
+export const LoaderContainer = styled.div`
+ top: 0px;
+ z-index:998;
+ background-color: rgba(0,0,0, 0.7);
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ overflow-y: hidden;
+`
+
+
+//Стиль блока с элементами загрузки
+export const Loaderblock = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+
+ p{
+ font-size: 24px;
+ color: white;
+ }
+`
+//Стиль для блока с кольцом загрузки
+export const LoadingSpinner = styled.div`
+ position: relative;
+ width: 65px;
+ height: 65px;
+`
+
+//Стиль для кольца загрузки
+export const LoadingRing = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 65px;
+ height: 65px;
+ border-radius: 50%;
+ border: 12px solid white;
+`
+//Стиль для части кольца загрузки, которая будет вращаться
+export const LoadingPart = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 65px;
+ height: 65px;
+ border-radius: 50%;
+ border: 12px solid transparent;
+ border-top: 12px solid ${props => props.theme.accent};
+
+ // Добавление аниманции и использование кейфремов
+ animation: ${spinAnimation} 1s linear infinite;
+`
+
+const Text = styled.div`
+ display: flex;
+ gap: 3px;
+ font-size: 24px;
+ color: white;
+ margin-top: 30px;
+ margin-left: 25px;
+`
+
+export default function RouterLoader({ loaderText }: Props) {
+ return (
+
+
+
+
+
+
+
+ {/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
+ {loaderText}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/UI/signalSvg.tsx b/FRONT/DEV/src/components/UI/signalSvg.tsx
new file mode 100644
index 0000000..9590433
--- /dev/null
+++ b/FRONT/DEV/src/components/UI/signalSvg.tsx
@@ -0,0 +1,63 @@
+import React, { useEffect, useRef } from 'react';
+
+interface SignalSVGProps {
+ signal: number; // Принимаем signal как пропс
+ color: string;
+ size: string;
+}
+
+const SignalSVG: React.FC = ({ signal, color, size }) => {
+ const svgRef = useRef(null);
+
+ useEffect(() => {
+ // Устанавливаем цвета в зависимости от signal
+ const colors = {
+ '--st1-color': signal >= 1 ? color : 'black',
+ '--st2-color': signal >= 2 ? color : 'black',
+ '--st3-color': signal >= 3 ? color : 'black',
+ '--st4-color': signal >= 4 ? color : 'black',
+ '--st5-color': signal === 5 ? color : 'black',
+ };
+
+ // Применяем стили к SVG
+ if (svgRef.current) {
+ Object.entries(colors).forEach(([key, value]) => {
+ svgRef.current?.style.setProperty(key, value);
+ });
+ }
+ }, [signal]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SignalSVG;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx
new file mode 100644
index 0000000..94eb76c
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Circle } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ style:{
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const CircleNode = ({ data }: CustomNodeProps) => {
+ return (
+ {/* */}
+ {/* */}
+ {data.label}
+
+ {/* Узлы для подключения */}
+
+
+
+
+
+ {/* Для входящих подключений */}
+
+
+
+
+ {/*
*/}
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/style.ts
new file mode 100644
index 0000000..df61ace
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/circle/style.ts
@@ -0,0 +1,20 @@
+import styled from "styled-components";
+
+type Props={
+ width: string,
+ height: string,
+ bordertype: string,
+ bordercolor: string
+}
+
+
+export const Circle = styled.div`
+ background-color: white;
+ border-radius: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx
new file mode 100644
index 0000000..388e9fb
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Default } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ style:{
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const DefaultNode = ({ data }: CustomNodeProps) => {
+ return (
+
+ {data.label}
+
+ {/* Узлы для подключения */}
+ {/*
+
+
+ */}
+
+ {/* Для входящих подключений */}
+ {/* */}
+
+ {/*
+ */}
+ {/* */}
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/style.ts
new file mode 100644
index 0000000..62595bc
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/default/style.ts
@@ -0,0 +1,20 @@
+import styled from "styled-components";
+
+type Props={
+ width: string,
+ height: string,
+ bordertype: string,
+ bordercolor: string
+}
+
+export const Default = styled.div`
+ background-color: white;
+ border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ visibility: visible;
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx
new file mode 100644
index 0000000..49ab61c
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Diamond, InnerDiamond, Border } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ style:{
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const DiamondNode = ({ data }: CustomNodeProps) => {
+
+ const baseSize = Math.max(data.style.width, data.style.height);
+
+ return (
+ {/* */}
+
+
+ {data.label}
+
+
+
+
+ {/* Узлы для подключения */}
+
+
+
+
+
+ {/* Для входящих подключений */}
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/style.ts
new file mode 100644
index 0000000..5eb0ba0
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/diamond/style.ts
@@ -0,0 +1,32 @@
+import styled from "styled-components";
+
+interface StyledDefaultProps {
+ width: any;
+ height: any;
+}
+
+export const Diamond = styled.div`
+ position: relative;
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+`;
+
+export const InnerDiamond = styled.div`
+ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
+ width: ${(props) => props.width - 6}px;
+ height: ${(props) => props.height - 4}px;
+ background-color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`
+
+export const Border = styled.div<{ width: number; height: number, bordercolor: any }>`
+ clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+ background-color: ${(props) => props.bordercolor};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/index.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/index.ts
new file mode 100644
index 0000000..9827eef
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/index.ts
@@ -0,0 +1,5 @@
+export {DefaultNode} from './default/defaultNode'
+export {PillNode} from './pill/pillNode'
+export {ParalelorgamNode} from './paralelogram/paralelogramNode'
+export {CircleNode} from './circle/circleNode'
+export {DiamondNode} from './diamond/diamondNode'
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx
new file mode 100644
index 0000000..699f00c
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Paralelorgam } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ style:{
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const ParalelorgamNode = ({ data }: CustomNodeProps) => {
+ return (
+
+ {data.label}
+
+ {/* Узлы для подключения */}
+
+
+
+
+
+ {/* Для входящих подключений */}
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts
new file mode 100644
index 0000000..46791e4
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts
@@ -0,0 +1,43 @@
+import styled from "styled-components";
+
+type Props={
+ width: string,
+ height: string,
+ bordertype: string,
+ bordercolor: string
+}
+
+
+export const Paralelorgam = styled.div`
+ transform: skewX(-20deg);
+ background-color: white;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+
+ .react-flow__handle-top{
+ transform: skewX(20deg);
+ top: -5px;
+ }
+
+ .react-flow__handle-bottom{
+ transform: skewX(20deg);
+ bottom: -5px;
+ }
+
+ .react-flow__handle-right{
+ transform: skewX(20deg);
+ right: -5px;
+ top: 25px;
+ }
+
+ .react-flow__handle-left{
+ transform: skewX(20deg);
+ left: -5px;
+ top: 25px;
+ }
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx
new file mode 100644
index 0000000..7adf271
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Pill } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ style:{
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const PillNode = ({ data }: CustomNodeProps) => {
+ return (
+ {/* */}
+ {/* */}
+ {data.label}
+
+ {/* Узлы для подключения */}
+
+
+
+
+
+ {/* Для входящих подключений */}
+
+
+
+
+ {/*
*/}
+
+
+ );
+};
+
+export default PillNode;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/style.ts
new file mode 100644
index 0000000..d08fe21
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/baseForm/pill/style.ts
@@ -0,0 +1,19 @@
+import styled from "styled-components";
+
+type Props={
+ width: string,
+ height: string,
+ bordertype: string,
+ bordercolor: string
+}
+
+export const Pill = styled.div`
+ background-color: white;
+ border-radius: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
+ width: ${(props) => props.width}px;
+ height: ${(props) => props.height}px;
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/index.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/index.ts
new file mode 100644
index 0000000..64b1036
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/index.ts
@@ -0,0 +1,5 @@
+export {NodeGroupNode} from './networkGroup/nodeGroup'
+export {NetWorkNode} from './networkNode/networkNode'
+export {IpGroupeNode} from './ipGroup/ipGroup'
+export {IpNode} from './ipNode/ipNode'
+export {PortNode} from './portNode/portNode'
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx
new file mode 100644
index 0000000..750e612
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx
@@ -0,0 +1,18 @@
+
+import { IpGroup } from "./style";
+
+interface CustomNodeProps {
+ data: {
+ style: {
+ width: any,
+ height: any,
+ }
+ };
+}
+
+export const IpGroupeNode = ({ data }: CustomNodeProps) => {
+ return (
+
+ );
+};
+
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts
new file mode 100644
index 0000000..606ce1a
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts
@@ -0,0 +1,12 @@
+import styled from "styled-components"
+
+type Props={
+ $width: string,
+ $height?: any
+}
+
+
+export const IpGroup = styled.div`
+ width: ${(props) => props.$width}px;
+ height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx
new file mode 100644
index 0000000..8dd7174
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx
@@ -0,0 +1,263 @@
+import { useState } from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Ip } from './style';
+import { Row, IpTitle, IpContent } from './style';
+import Input from '../../../../UI/inputs/input';
+import {useIpStore, useModalContext, usePannableStore} from '../../../../../store';
+import { ModalMap } from '../../../../modalwindows/windowsType';
+import { validateIPAddress, validateSameSubnet } from '../../../../../utils/nodeController/validator';
+import {changeIp} from '../../../../../const/types/types'
+import { IconComponent } from "../../../../../const/buttons/icons";
+import { buttonNames } from "../../../../../const/buttons/names";
+import {NetworkService} from "../../../../../api/request";
+
+interface IIp {
+ ipType: string
+ ipAdress: string,
+ ipGate: string,
+ ipDns: string[]
+ ipVersion: string
+}
+
+type StateType = Record;
+const typeMap: StateType = {
+ 'auto': "DHCP",
+ 'manual': "Static"
+}
+
+interface CustomNodeProps {
+ data: {
+ ipData: IIp,
+ interfaceData: {
+ device: string,
+ connection: string
+ },
+ style: {
+ width: any,
+ height: any,
+ borderType: string,
+ borderColor: string,
+ }
+ route: boolean
+ }
+}
+
+export const IpNode = ({ data }: CustomNodeProps) => {
+ const { toggleEditingMode } = usePannableStore();
+ const [isInput, setIsInput] = useState(false)
+ const { openModal } = useModalContext();
+ const {ipAddress} = useIpStore();
+
+ const [ipData, setIpData] = useState(
+ {
+ currentIp: data.ipData.ipAdress,
+ newIp: data.ipData.ipAdress,
+ currentGate: data.ipData.ipGate,
+ newGate: data.ipData.ipGate,
+ currentDns: data.ipData.ipDns,
+ newDns: data.ipData.ipDns,
+ device: data.interfaceData.device,
+ connection: data.interfaceData.connection,
+ version: data.ipData.ipVersion,
+ prefix: 0
+ }
+ )
+
+ const handleFocus = () => {
+ toggleEditingMode();
+ };
+
+ const handleBlur = () => {
+ toggleEditingMode();
+ };
+
+ const handleClick = () => {
+ if (isInput) {
+ setIsInput(false)
+ setIpData(prevData => ({
+ ...prevData,
+ newIp: data.ipData.ipAdress,
+ newGate: data.ipData.ipGate,
+ newDns: data.ipData.ipDns
+ }));
+ }
+ else {
+ setIsInput(true)
+ }
+ }
+
+ const handleChange = (id: string, value: string) => {
+ if (id === 'newDns') {
+ const dnsArray = value.split(',').map(dns => dns.trim());
+ setIpData(prevData => ({
+ ...prevData,
+ [id]: dnsArray
+ }));
+ } else {
+ setIpData(prevData => ({
+ ...prevData,
+ [id]: value
+ }));
+ }
+ };
+
+ function getSubnetMask(ipAddress: string, ipVersion: string): number {
+ if (ipVersion === "6") {
+ return 64;
+ }
+
+ const firstOctet = parseInt(ipAddress.split(".")[0], 10);
+ if (firstOctet >= 0 && firstOctet <= 127) {
+ return 8;
+ } else if (firstOctet >= 128 && firstOctet <= 191) {
+ return 16;
+ } else if (firstOctet >= 192 && firstOctet <= 223) {
+ return 24;
+ } else {
+ return 24;
+ }
+ }
+
+
+ const handleConfirm = () => {
+ if (
+ !validateIPAddress(ipData.newIp) ||
+ !validateIPAddress(ipData.newGate) ||
+ !ipData.newDns.every(dns => validateIPAddress(dns))
+ ) {
+ alert('Один или несколько IP-адресов некорректны.');
+ return;
+ }
+
+ if (!validateSameSubnet(ipData.newIp, ipData.newGate, ipData.newDns)) {
+ alert('Адрес, шлюз и хотя бы один DNS должны находиться в одной подсети.');
+ return;
+ }
+
+ const calculatedPrefix = getSubnetMask(ipData.newIp, data.ipData.ipVersion);
+
+ setIpData(prevData => ({
+ ...prevData,
+ prefix: calculatedPrefix,
+ }));
+
+ openModal(ModalMap['1'], {
+ ...ipData,
+ prefix: calculatedPrefix,
+ });
+
+ setIsInput(false)
+ };
+ const switchMethod = async ()=>{
+ const methodDCHP ={
+ "connection_id": ipData.connection,
+ "ipv4_updates": {
+ "ipv4.method": "auto",
+ }
+ }
+ try {
+ await NetworkService.saveIp(ipAddress, methodDCHP);
+ window.location.reload();
+ }catch (err){
+ console.error(err)
+ }
+ }
+
+ return (
+ <>
+
+
+
+ {/*
+ Версия IP:
+ v{data.ipData.ipVersion}
+
*/}
+
+
+ Метод:
+ {typeMap[data.ipData.ipType]}
+
+
+ {isInput ? (
+
+
+
+
+
+ ) : (
+
+ {typeMap[data.ipData.ipType] ==='Static' && }
+
+
+ )}
+
+
+
+
+
+ IPv{data.ipData.ipVersion}-Addr:
+ {isInput ? (
+ handleChange('newIp', value)}
+ onBlur={handleBlur}
+ onFocus={handleFocus}
+ style={{fontSize: '12px', padding: '4px 10px'}}
+ />
+ ) : (
+ {data.ipData.ipAdress}
+ )}
+
+
+
+
+ IPv{data.ipData.ipVersion}-GW:
+ {isInput ? (
+ handleChange('newGate', value)}
+ onBlur={handleBlur}
+ onFocus={handleFocus}
+ style={{fontSize: '12px', padding: '4px 10px'}}
+ />
+ ) : (
+ {data.ipData.ipGate}
+ )}
+
+
+
+ IPv{data.ipData.ipVersion}-DNS:
+ {isInput ? (
+ handleChange('newDns', value)}
+ onBlur={handleBlur}
+ onFocus={handleFocus}
+ style={{fontSize: '12px', padding: '4px 10px'}}
+ />
+ ) : (
+ {data.ipData.ipDns.join(', ')}
+ )}
+
+
+ {data.route &&
+ <>
+
+
+ >
+ }
+
+
+ >
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts
new file mode 100644
index 0000000..3355417
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts
@@ -0,0 +1,74 @@
+import styled from "styled-components";
+
+type Props={
+ $width: string,
+ $height?: any
+ $bordertype: string,
+ $bordercolor: string
+}
+
+export const Ip = styled.div`
+ background-color: ${(props)=>props.theme.secondary};
+ display: flex;
+ flex-direction: column;
+ width: ${(props) => props.$width}px;
+ height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
+ // box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
+ border-radius: 8px;
+
+ p{
+ margin: 0;
+ }
+
+ img{
+ width: 15px;
+ height: 15px;
+ }
+`
+
+export const IpTitle = styled.div`
+ background: ${(props)=>props.theme.accent};
+ border-top-left-radius: 6px;
+ border-top-right-radius: 6px;
+ color: white;
+ display: flex;
+ justify-content: space-between;
+ padding: 10px;
+
+`
+
+export const IpContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ padding: 10px;
+
+ .row{
+ height: 24px;
+
+ .row-label{
+ flex: 1;
+ }
+
+ .row-text{
+ flex: 3;
+ }
+
+ p{
+
+ text-wrap: nowrap;
+ text-align: left;
+ }
+ }
+`
+
+export const Row = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 5px;
+
+ p{
+ margin: 0;
+ }
+`
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx
new file mode 100644
index 0000000..e7b3f66
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx
@@ -0,0 +1,64 @@
+import { useState } from "react";
+import { NodeGroup, Row, CollapseButton } from "./style";
+import { useGroupStore } from "../../../../../store";
+import { MainButton } from "../../../../UI/buttons/mainButton";
+import { IconComponent } from "../../../../../const/buttons/icons";
+import { buttonNames } from "../../../../../const/buttons/names";
+
+interface CustomNodeProps {
+ data: {
+ id: string;
+ label: string;
+ height: any
+ style: {
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ };
+ };
+}
+
+export const NodeGroupNode = ({ data }: CustomNodeProps) => {
+ const [click, setClick] = useState(false);
+ const { groups, setGroups } = useGroupStore();
+
+ const handleClick = () => {
+ const newClickState = !click;
+ setClick(newClickState);
+
+ const updatedNodes = groups.map((groups) =>
+ groups.id === data.id ? { ...groups, click: newClickState } : groups
+ );
+
+ if (!groups.some((groups) => groups.id === data.id)) {
+ updatedNodes.push({ id: data.id, click: newClickState });
+ }
+
+ setGroups(updatedNodes);
+ };
+
+ return (
+
+
+ {data.label}
+
+ {/* */}
+
+ {click ?(
+ 'Развернуть'
+ ):(
+ 'Свернуть'
+ )}
+
+ {/* */}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts
new file mode 100644
index 0000000..9311bd5
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts
@@ -0,0 +1,47 @@
+import styled from "styled-components"
+
+type Props={
+ $width?: string,
+ $height?: any
+ $bordertype?: string,
+ $bordercolor?: string
+ $backgroundcolor?: string
+}
+
+
+export const NodeGroup = styled.div`
+ border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
+ width: ${(props) => props.$width}px;
+ height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ visibility: visible;
+`
+
+export const Row = styled.div`
+ position: absolute;
+ top: -25px;
+ left: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: ${props=>props.$width}px;
+ gap: 5px;
+ padding-right: 10px;
+
+ p{
+ margin: 0;
+ }
+`
+
+export const CollapseButton = styled.p`
+ padding: unset;
+ background: none;
+ color: ${(props)=>props.theme.textColor};
+ margin-right: 15px !important;
+ &:hover{
+ background: none;
+ }
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx
new file mode 100644
index 0000000..0c0bfa4
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx
@@ -0,0 +1,110 @@
+import React, { useEffect, useState } from 'react';
+import { Handle, Position } from '@xyflow/react';
+import { Network, TitleContainer, Device, Status, Content, Row, MAC, LabelColumn } from './style';
+import SignalSVG from '../../../../UI/signalSvg';
+
+interface CustomNodeProps {
+ data: {
+ networkData: {
+ device: string
+ type: string
+ mac: string
+ mtu: number
+ state: string
+ connection: string
+ id: string,
+ strength: string
+ }
+ style: {
+ width: any,
+ height: any,
+ borderType: string,
+ borderColor: string,
+ backgroundColor: string
+ }
+ height: string
+ visible: true
+ };
+}
+
+export const NetWorkNode = ({ data }: CustomNodeProps) => {
+ const [signal, setSignal] = useState(1);
+
+ useEffect(() => {
+ const perc = Number(data.networkData.strength);
+
+ switch (true) {
+ case perc < 20:
+ setSignal(1);
+ break;
+ case perc < 40:
+ setSignal(2);
+ break;
+ case perc < 60:
+ setSignal(3);
+ break;
+ case perc < 80:
+ setSignal(4);
+ break;
+ default:
+ setSignal(5);
+ break;
+ }
+
+ }, [data.networkData.strength])
+
+ return (
+
+
+
+ {data.networkData.type == "WIFI" &&
+
+ }
+
+ {data.networkData.type == "ETHERNET" &&
+
+ }
+
+ {data.networkData.type == "TUN" &&
+
+ }
+
+ {data.networkData.device}
+
+ {data.networkData.type == 'WIFI' && data.networkData.state === "Подключено" ? (
+
+
+
+ ) : (
+
+ )
+ }
+
+
+
+ {data.visible &&
+
+
+ Конфигурация
+
+
+ MAC:
+ {data.networkData.mac}
+
+
+ MTU:
+ {data.networkData.mtu}
+
+
+
+
+ }
+
+ {data.networkData.connection !== 'unknown' &&
+
+ }
+
+ );
+};
+
+export default NetWorkNode
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts
new file mode 100644
index 0000000..f73753f
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts
@@ -0,0 +1,93 @@
+import styled from "styled-components";
+
+type Props = {
+ $width?: string,
+ $height?: any
+ $bordertype?: string,
+ $bordercolor?: string
+ $backgroundcolor?: string
+}
+
+export const Network = styled.div`
+ background-color: ${(props) => props.theme.primary};
+ border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
+ min-width: ${(props) => props.$width}px;
+ height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ visibility: visible;
+ overflow: hidden;
+ p{
+ margin: 0;
+ }
+`
+
+export const TitleContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: ${(props) => props.$backgroundcolor};
+ color: white;
+ padding: 15px;
+ height: ${(props) => props.$height ? props.$height : 'unset'};
+
+ img{
+ width: 20px
+ }
+`
+
+export const Device = styled.p`
+ font-size: 18px;
+`
+
+export const Status = styled.p`
+ font-size: 14px;
+
+
+ img{
+ width: 20px;
+ height: 20px;
+ }
+`
+
+export const Content = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ padding: 15px;
+`
+
+export const Row = styled.div`
+ display: flex;
+ gap: 5px;
+`
+
+export const MAC = styled.div`
+ display: flex;
+ justify-content: space-between;
+ background: ${(props) => props.theme.secondary};
+ border-radius: 8px;
+ // box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
+ width: 100%;
+
+ .networkNode-row{
+ padding: 10px;
+ }
+`
+
+export const LabelColumn = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ width: 100%;
+
+ .networkNode-label{
+ font-size: 10px;
+ width: 100%;
+ text-align: left;
+ margin-left: 15px;
+ }
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx
new file mode 100644
index 0000000..68939f7
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx
@@ -0,0 +1,48 @@
+import { Handle, Position } from '@xyflow/react';
+import { Port, Column } from './style';
+
+interface CustomNodeProps {
+ data: {
+ label: string;
+ port: string
+ id: string;
+ style: {
+ width: any;
+ height: any;
+ borderType: any;
+ borderColor: any;
+ }
+ };
+}
+
+export const PortNode = ({ data }: CustomNodeProps) => {
+ return (
+
+
+
+ {data.label}
+
+
+ {data.port &&
+ {data.port}
+ }
+
+
+
+ {data.port ? (
+ <>
+
+
+ >
+
+ ) : (
+ <>
+
+
+ >
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/style.ts b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/style.ts
new file mode 100644
index 0000000..17a1b0f
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/nodes/networkInterface/portNode/style.ts
@@ -0,0 +1,30 @@
+import styled from "styled-components"
+
+type Props={
+ $width: string,
+ $height?: any
+ $bordertype: string,
+ $bordercolor: string
+}
+
+export const Port = styled.div`
+ background-color: ${(props)=>props.theme.primary};
+ border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
+ width: ${(props) => props.$width}px;
+ height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ visibility: visible;
+`
+
+export const Column = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+
+ p{
+ margin: 0;
+ }
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/types/colors.ts b/FRONT/DEV/src/components/customeNodes/types/colors.ts
new file mode 100644
index 0000000..342c6f7
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/types/colors.ts
@@ -0,0 +1,11 @@
+type ColorType = Record;
+
+export const NodeColors: ColorType = {
+ '0': '#c0c0c0',
+ '1': '#ef6e7d',
+ '2': '#f19e4c',
+ '3': '#e9c44c',
+ '4': '#51ce82',
+ '5': '#4cd2d0',
+ '6': '#a085f3',
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/customeNodes/types/types.ts b/FRONT/DEV/src/components/customeNodes/types/types.ts
new file mode 100644
index 0000000..5b7ce87
--- /dev/null
+++ b/FRONT/DEV/src/components/customeNodes/types/types.ts
@@ -0,0 +1,16 @@
+
+import { DefaultNode, PillNode, ParalelorgamNode, DiamondNode, CircleNode } from '../nodes/baseForm'
+import { NodeGroupNode, NetWorkNode, IpGroupeNode, IpNode, PortNode } from '../nodes/networkInterface';
+
+export const nodeTypes = {
+ custom: DefaultNode,
+ pill: PillNode,
+ parallelogram: ParalelorgamNode,
+ diamond: DiamondNode,
+ circle: CircleNode,
+ nodegroup: NodeGroupNode,
+ portNode: PortNode,
+ network: NetWorkNode,
+ ipgroup: IpGroupeNode,
+ ip: IpNode,
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modal.tsx b/FRONT/DEV/src/components/modalwindows/modal.tsx
new file mode 100644
index 0000000..b030ace
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modal.tsx
@@ -0,0 +1,10 @@
+import { ModalWindow, ModalContainer } from "./style"
+import { modalNames } from "../../const/modals/names"
+
+export const WindowComponent = ()=>{
+ return(
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/ConnectNetwork.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/ConnectNetwork.tsx
new file mode 100644
index 0000000..484e995
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/ConnectNetwork.tsx
@@ -0,0 +1,87 @@
+// components/modalContainers/NoteDevice.tsx
+import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
+import { MainButton } from "../../UI/buttons/mainButton";
+import Input from "../../UI/inputs/input";
+import { useState } from "react";
+import {NetworkService} from "../../../api/request";
+import useNetworkStore from "../../../store/useDeviceStore";
+import RouterLoader from "../../UI/routerLoader";
+import {useTheme} from "styled-components";
+import {useIpStore} from "../../../store";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+interface Props {
+ address: string;
+ onClose: () => void;
+}
+
+
+
+export const ConnectNetwork = ({address, onClose}: Props) => {
+ const [password, setPassword] = useState('');
+ const theme = useTheme();
+ const {ipAddress} = useIpStore();
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const {t} = useTranslation('common');
+
+ const handleSave = async () => {
+ try {
+ setIsLoading(true);
+ setError('');
+ const res = await NetworkService.connectNetwork(ipAddress, password, address);
+ if (res.status === 200) {
+ onClose();
+ } else {
+ setError(res.data.detail);
+ }
+ } catch (error) {
+ console.error(t('connectionErrorLog'), error);
+ setError(t('connectionError'));
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+ {t('networkConnection')}
+
+
+
+ setPassword(value)}
+ />
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+
+
+
+ {isLoading && }
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/NoteDevice.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/NoteDevice.tsx
new file mode 100644
index 0000000..c3c1356
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/NoteDevice.tsx
@@ -0,0 +1,83 @@
+import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
+import { MainButton } from "../../UI/buttons/mainButton";
+import Input from "../../UI/inputs/input";
+import { useState } from "react";
+import {useIpStore} from "../../../store";
+import {NetworkService} from "../../../api/request";
+import useNetworkStore from "../../../store/useDeviceStore";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+interface Props {
+ address: string;
+ onClose: () => void;
+ currentNote?: string;
+ type: string
+}
+
+
+
+export const NoteDevice = ({ address, onClose, currentNote, type}: Props) => {
+ const [note, setNote] = useState(currentNote || '');
+ const { ipAddress } = useIpStore();
+ const { devices, setDevices } = useNetworkStore();
+ const {t} = useTranslation('common');
+
+ const handleSave = async () => {
+ try {
+ if (!currentNote && note.trim() !== '') {
+ await NetworkService.addNote(ipAddress, address, note, type);
+ }else if (currentNote && note.trim() !== '' && note !== currentNote) {
+ await NetworkService.editNote(ipAddress, address, note, type);
+ }else if (currentNote && note.trim() === '') {
+ await NetworkService.deleteNote(ipAddress, address, type);
+ }else {
+ onClose();
+ return;
+ }
+
+ const updatedDevices = devices.map(device =>
+ device.mac === address
+ ? { ...device, note: note.trim() }
+ : device
+ );
+ setDevices(updatedDevices);
+ onClose();
+
+ } catch (error) {
+ console.error('Error saving note:', error);
+ }
+ };
+
+ return (
+
+
+ {currentNote ? t('note-edit') : t('note-add')}
+
+
+
+ setNote(value)}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/NoteWifi.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/NoteWifi.tsx
new file mode 100644
index 0000000..4abaef6
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/NoteWifi.tsx
@@ -0,0 +1,84 @@
+// components/modalContainers/NoteDevice.tsx
+import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
+import { MainButton } from "../../UI/buttons/mainButton";
+import Input from "../../UI/inputs/input";
+import { useState } from "react";
+import {NetworkService} from "../../../api/request";
+import useNetworkStore from "../../../store/useDeviceStore";
+import {useIpStore} from "../../../store";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+interface Props {
+ address: string;
+ onClose: () => void;
+ currentNote?: string;
+ type: string
+}
+
+
+
+export const NoteWifi = ({ address, onClose, currentNote, type}: Props) => {
+ const [note, setNote] = useState(currentNote || '');
+ const { ipAddress } = useIpStore();
+ const { wifiNetworks, setWifiNetworks } = useNetworkStore();
+ const {t} = useTranslation('common');
+
+ const handleSave = async () => {
+ try {
+ if (!currentNote && note.trim() !== '') {
+ await NetworkService.addNote(ipAddress, address, note, type);
+ }else if (currentNote && note.trim() !== '' && note !== currentNote) {
+ await NetworkService.editNote(ipAddress, address, note, type);
+ }else if (currentNote && note.trim() === '') {
+ await NetworkService.deleteNote(ipAddress, address, type);
+ }else {
+ onClose();
+ return;
+ }
+ const updatedNetworks = wifiNetworks.map(network =>
+ network.ssid === address
+ ? { ...network, note: note.trim() }
+ : { ...network }
+ );
+ setWifiNetworks(updatedNetworks);
+ onClose();
+
+ } catch (error) {
+ console.error('Error saving note:', error);
+ }
+ };
+
+
+ return (
+
+
+ {currentNote ? t('note-edit') : t('note-add')}
+
+
+
+ setNote(value)}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/addDevice.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/addDevice.tsx
new file mode 100644
index 0000000..910d1a2
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/addDevice.tsx
@@ -0,0 +1,68 @@
+import Input from "../../UI/inputs/input"
+import { MainButton } from "../../UI/buttons/mainButton";
+import { useState } from "react";
+import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
+import { validateIPAddress } from "../../../utils/nodeController/validator";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+interface Props {
+ onSave?: (data: string) => void;
+ onClose: () => void;
+}
+
+export const AddDevice = ({ onSave, onClose }: Props) => {
+ const [ipAddress, setIpAddress] = useState('')
+ const [error, setError] = useState(false)
+ const {t} = useTranslation('common');
+
+ const handleSubmit = () => {
+ if (!validateIPAddress(ipAddress)) {
+ setError(true)
+ return;
+ }
+ // e.preventDefault(); // Предотвращаем перезагрузку страницы
+ if (onSave && ipAddress) {
+ setError(false)
+ onSave(ipAddress);
+ onClose();
+ }
+ };
+
+ return (
+
+
+ {t('addNode')}
+
+
+
+ setIpAddress(value)}
+ />
+ {error && (
+
+ {t('invalidAddress')}
+
+ )}
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/connectionError.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/connectionError.tsx
new file mode 100644
index 0000000..fa69a59
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/connectionError.tsx
@@ -0,0 +1,48 @@
+import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
+import { useModalContext } from "../../../store"
+import { useIpStore } from "../../../store"
+import { MainButton } from "../../UI/buttons/mainButton"
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+type Props={
+ message: string
+}
+
+export const ConnectionError = ({message}: Props)=>{
+ const {closeModal} = useModalContext()
+ const { setIpAddress } = useIpStore();
+ const {t} = useTranslation('common');
+
+ const close = ()=>{
+ setIpAddress('localhost')
+ closeModal()
+ }
+
+ return (
+
+
+ {t('connectionLost')}
+
+
+
+
+ {t('serverDisconnected')}
+ {t('checkAddressAndRetry')}
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/customizeNodes.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/customizeNodes.tsx
new file mode 100644
index 0000000..b8ab434
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/customizeNodes.tsx
@@ -0,0 +1,143 @@
+import React, {useEffect, useState} from 'react';
+import {ButtonContainer, ModalContainer, ModalContent, ModalTitle} from "../style";
+import {MainButton} from "../../UI/buttons/mainButton";
+import {ModalMap} from "../windowsType";
+import {useModalContext} from "../../../store";
+import styled from "styled-components";
+import { useSettingsStore } from '../../../store/settings/settingsStore';
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+const StyledTableList = styled.ul`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 0 40px;
+ list-style-type: none;
+`
+const StyledTableItem = styled.li`
+ width: 160px;
+ height: 60px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ background-color: ${props => props.theme.accent};
+ color: ${props => props.theme.primary};
+ border: 1px solid ${props => props.theme.primary};
+
+ &:first-child {
+ border-radius: 16px 0 0 16px;
+ }
+ &:last-child {
+ border-radius: 0 16px 16px 0;
+ }
+ &.off{
+ background-color: ${props => props.theme.offlineColor};
+ }
+`
+const CustomizeNodes = () => {
+ const { openModal } = useModalContext();
+ const pendingChanges = useSettingsStore(state => state.pendingChanges);
+ const updateSetting = useSettingsStore(state => state.updateSetting);
+ const getSetting = useSettingsStore(state => state.getSetting);
+ const initializeSettings = useSettingsStore(state => state.initializeSettings);
+ const {t} = useTranslation('common');
+ const [dragIndex, setDragIndex] = useState(null);
+
+ // Получаем текущие значения из хранилища
+ const initialColumns = getSetting('table-columns-order').currentValue as string[];
+ const disabledColumns = getSetting('table-columns-disabled').currentValue as number[];
+ const items = pendingChanges['table-columns-order'] ?? initialColumns;
+ const disabledItems = pendingChanges['table-columns-disabled'] ?? disabledColumns;
+
+ // Drag & Drop
+ const handleDragStart = (index: number) => {
+ setDragIndex(index);
+ };
+ const handleDragOver = (index: number) => {
+ if (dragIndex === null || index === dragIndex) return;
+
+ const newItems = [...items];
+ const [movedItem] = newItems.splice(dragIndex, 1);
+ newItems.splice(index, 0, movedItem);
+
+ updateSetting('table-columns-order', newItems);
+ setDragIndex(index);
+ };
+ const handleDrop = () => {
+ setDragIndex(null);
+ };
+
+ // Переключение состояния
+ const handleSwitch = (index: number) => {
+ const newDisabledItems = disabledItems.includes(index)
+ ? disabledItems.filter((i: number) => i !== index)
+ : [...disabledItems, index];
+
+ updateSetting('table-columns-disabled', newDisabledItems);
+ };
+
+ // Закрытие с сохранением
+ const handleSave = () => {
+ openModal(ModalMap['10']);
+ };
+
+
+ useEffect(() => {
+ initializeSettings();
+ }, [initializeSettings]);
+
+ const getItemText = (item: string) => {
+ if (item === 'Онлайн') return t('settings-status');
+ if (item === 'Имя') return t('settings-name');
+ if (item === 'Производитель') return t('settings-vendor');
+ if (item === 'IP адрес') return t('settings-ipAddress');
+ if (item === 'Открытые порты') return t('settings-openPorts');
+ if (item === 'Последняя активность') return t('settings-lastActivity');
+ if (item === 'Заметка') return t('settings-note');
+ if (item === 'Действие') return t('settings-action');
+ return item;
+ };
+
+ return (
+
+
+ {t('customizeTable-title')}
+
+
+
+
+ {items.map((item:string, index:number) => (
+ handleDragStart(index)}
+ onDragOver={(e) => {
+ e.preventDefault();
+ handleDragOver(index);
+ }}
+ onDragEnd={handleDrop}
+ onClick={() => handleSwitch(index)}
+ >
+ {getItemText(item)}
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default CustomizeNodes;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/disconnectConfirm.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/disconnectConfirm.tsx
new file mode 100644
index 0000000..e9d67bf
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/disconnectConfirm.tsx
@@ -0,0 +1,67 @@
+import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
+import { useModalContext } from "../../../store"
+import { useIpStore } from "../../../store"
+import { MainButton } from "../../UI/buttons/mainButton"
+import {useState} from "react";
+import {NetworkService} from "../../../api/request";
+import RouterLoader from "../../UI/routerLoader";
+import {useTheme} from "styled-components";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+type Props={
+ address: string,
+ onClose: () => void
+}
+
+export const DisconnectConfirm = ({address, onClose}: Props)=>{
+ const { ipAddress } = useIpStore();
+ const theme = useTheme()
+ const [isLoading, setIsLoading] = useState(false);
+ const {t} = useTranslation('common');
+
+ const handleDisconnect = async () => {
+ try {
+ setIsLoading(true);
+ const res = await NetworkService.disconnectNetwork(ipAddress, address);
+ if(res.status === 200) {
+ onClose()
+ }
+ } catch (error) {
+ console.error('Error connection:', error);
+ }finally {
+ setIsLoading(false);
+ }
+ };
+
+ return(
+
+
+ {t('wifiDisconnect-title')}
+
+
+
+
+ {t('wifiDisconnect-confirm')}
+ {t('wifiDisconnect-message1')} + {address}. {t('wifiDisconnect-message2')}
+ Вы собираетесь отключиться от сети "{address}". Все активные подключения будут прерваны.
+
+
+
+
+
+
+
+ {isLoading && }
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/index.ts b/FRONT/DEV/src/components/modalwindows/modalContainers/index.ts
new file mode 100644
index 0000000..ed95168
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/index.ts
@@ -0,0 +1,4 @@
+export {IpConfirm} from './ipConfirm'
+export {ConnectionError} from './connectionError'
+export {AddDevice} from './addDevice'
+export {RightsConfirm} from './rightsConfirm'
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx
new file mode 100644
index 0000000..e6bcbf5
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx
@@ -0,0 +1,223 @@
+import styled from "styled-components";
+import Loader from "../../UI/loader";
+import { useEffect, useState } from "react";
+import { changeIp } from '../../../const/types/types'
+import { NetworkService } from '../../../api/request'
+import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer, ErrorMessage } from "../style"
+import { useIpStore } from "../../../store";
+import { MainButton } from "../../UI/buttons/mainButton";
+import { useAdminStore } from "../../../store/adminStore";
+import { useDevicesStore } from "../../../store";
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+type Props = {
+ ipData: changeIp
+ onClose: () => void;
+ smoothClose: () => void;
+}
+
+const Information = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+`
+
+const Info = styled.div`
+ display: flex;
+ gap: 10px;
+`
+
+const DataContainer = styled.div`
+ display: flex;
+ gap: 35px;
+`
+
+const CurrentData = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+`
+
+const NewData = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+`
+
+export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => {
+ const [loader, setLoader] = useState(true)
+ const [newIP, setNewIp] = useState(null)
+ const [currentIp, setCurrentIP] = useState({})
+ const [checkStatus, setcheckStatus] = useState(false)
+ const [message, setMessgae] = useState('')
+ const { ipAddress, setIpAddress } = useIpStore();
+ const {isAdmin} = useAdminStore()
+ const { devices, addDevice, deleteDevice } = useDevicesStore();
+ const {t} = useTranslation('common');
+
+ useEffect(() => {
+ if (ipData && isAdmin) {
+ const data = {
+ "connection_id": ipData.connection,
+ "ipv4_updates": {
+ "ipv4.method": "manual",
+ "ipv4.address-data": [
+ {
+ "address": ipData.newIp,
+ "prefix": ipData.prefix
+ }
+ ],
+ "ipv4.gateway": ipData.newGate,
+ "ipv4.dns-data": ipData.newDns
+ }
+ };
+
+ const oldData = {
+ "connection_id": ipData.connection,
+ "ipv4_updates": {
+ "ipv4.method": "manual",
+ "ipv4.address-data": [
+ {
+ "address": ipData.currentIp,
+ "prefix": ipData.prefix
+ }
+ ],
+ "ipv4.gateway": ipData.currentGate,
+ "ipv4.dns-data": ipData.currentDns
+ }
+ }
+
+ setCurrentIP(oldData)
+
+ checkIp(data);
+ }
+ }, [ipData, isAdmin]);
+
+ const checkIp = async (data: any) => {
+ try {
+ const result = await NetworkService.checkIp(ipAddress, data);
+ setcheckStatus(result.data.available)
+ setMessgae(result.data.message)
+ if (result.data.available) {
+ setNewIp(data)
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ } finally {
+ setLoader(false);
+ }
+ };
+
+ const saveIp = async (data: any) => {
+ try {
+ const result = await NetworkService.saveIp(ipAddress, data);
+ if (result.status === 200) {
+ if(ipAddress!=='localhost'){
+ setIpAddress(ipData.newIp)
+
+ const updatedDevices = devices.map(device =>
+ device.address === ipData.currentIp
+ ? { ...device, address: ipData.newIp }
+ : device
+ );
+ useDevicesStore.setState({ devices: updatedDevices });
+ }
+
+ onClose()
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ } finally {
+ setLoader(false);
+ }
+ };
+
+ const cancelIp = async (data: any) => {
+ try {
+ const result = await NetworkService.saveIp(ipAddress, data);
+ if (result.status === 200) {
+ onClose()
+ }
+ } catch (err) {
+ console.error('Error:', err);
+ } finally {
+ setLoader(false);
+ }
+ };
+
+ return (
+ <>
+ {loader && isAdmin ? (
+
+ ) : (
+
+
+ {t('ipChange-title')}
+
+ {checkStatus ? (
+
+
+ {t('ipChange-generalInfo')}
+
+ {t('ipChange-interface')}: {ipData.device}
+ --{'>'}
+ {ipData.connection}
+
+ {t('ipChange-ipInfo')}
+
+
+ {t('ipChange-currentIp')}: {ipData.currentIp}
+ {t('ipChange-currentGateway')}: {ipData.currentGate}
+ {t('ipChange-currentDns')}: {ipData.currentDns}
+
+
+
+ {t('ipChange-newIp')}: {ipData.newIp}
+ {t('ipChange-newGateway')}: {ipData.newGate}
+ {t('ipChange-newDns')}: {ipData.newDns}
+
+
+
+
+
+ cancelIp(currentIp)}
+ />
+ saveIp(newIP)}
+ />
+
+
+ ) : (
+ <>
+
+
+ {isAdmin ? (
+ <>
+ {t('ipChange-checkError')}
+ {message}
+ >
+ ):(
+ {t('ipChange-adminRequired')}
+ )}
+
+
+
+
+
+
+ >
+ )}
+
+ )}
+ >
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/rightsConfirm.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/rightsConfirm.tsx
new file mode 100644
index 0000000..4cceef9
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalContainers/rightsConfirm.tsx
@@ -0,0 +1,132 @@
+import { useEffect, useState } from "react"
+import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
+import Input from "../../UI/inputs/input"
+import { MainButton } from "../../UI/buttons/mainButton"
+import { NetworkService } from "../../../api/request"
+import { useIpStore } from "../../../store"
+import { useAdminStore } from "../../../store/adminStore"
+import { useCheckAdmin } from "../../../utils/checkAdmin"
+
+import { buttonNames } from "../../../const/buttons/names"
+import { IconComponent } from "../../../const/buttons/icons"
+import {useTranslation} from "../../../utils/i18n/hooks";
+
+interface Props {
+ onClose: () => void;
+}
+
+export const RightsConfirm = ({ onClose }: Props) => {
+
+ const { ipAddress } = useIpStore();
+ const { isAdmin } = useAdminStore()
+ const checkAdmin = useCheckAdmin();
+ const [error, setError] = useState(false)
+ const {t} = useTranslation('common');
+ const [success, setSuccess] = useState({
+ message: '',
+ status: false
+ })
+
+ const [user, setUser] = useState({
+ username: 'operator',
+ password: ""
+ })
+
+ const handleChange = (id: string, value: string) => {
+ setUser(prevData => ({
+ ...prevData,
+ [id]: value
+ }));
+ }
+
+ const login = async (user: object) => {
+ try {
+ const response = await NetworkService.login(ipAddress, user);
+ if(response.status === 200){
+ await checkAdmin();
+ setError(false)
+ setSuccess(prevData => ({
+ ...prevData,
+ message: t('auth-successLogin'),
+ status: true
+ }));
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ onClose();
+ }else{
+ setError(true)
+ }
+ } catch (err) {
+ console.error(t('auth-error'), err);
+ }
+ }
+
+ const logout = async () => {
+ await NetworkService.logout(ipAddress);
+ await checkAdmin();
+ setSuccess(prevData => ({
+ ...prevData,
+ message: t('auth-successLogout'),
+ status: true
+ }));
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ onClose();
+ }
+
+ return (
+
+ {!success.status ? (
+ !isAdmin ? (
+ <>
+
+ {t('auth-getAccess')}
+
+
+
+ {t('auth-enableAdmin')}
+ handleChange('password', value)}
+ />
+
+ {error && (
+
+ {t('auth-wrongPass')}
+
+ )}
+
+
+
+
+ login(user)} />
+
+ >
+ ) : (
+ <>
+
+ {t('auth-switchToLimited')}
+
+
+
+ {t('auth-limitedModeDesc')}
+
+
+
+
+
+
+ >
+ )
+ ):(
+
+
+ {success.message}
+ {t('auth-closeHint')}
+
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/modalWindow.tsx b/FRONT/DEV/src/components/modalwindows/modalWindow.tsx
new file mode 100644
index 0000000..f745b4f
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/modalWindow.tsx
@@ -0,0 +1,91 @@
+import { useModalContext } from "../../store";
+import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalContainers";
+import { ModalMap } from "./windowsType";
+import Loader from "../UI/loader";
+import { ModalWindow } from "./style";
+import {NoteDevice} from "./modalContainers/NoteDevice";
+import { NoteWifi } from "./modalContainers/NoteWifi";
+import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
+import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
+import {Settings} from "../settings/Settings";
+import CustomizeNodes from "./modalContainers/customizeNodes";
+
+type Props = {
+ isOpen: boolean,
+ type: string
+ data: any;
+}
+
+export default function WindowComponent({ isOpen, type, data }: Props) {
+
+ const { closeModal, closeLoader } = useModalContext()
+
+ return (
+ <>
+ {isOpen &&
+
+ {type == ModalMap['1'] && data &&
+
+ }
+
+ {type == ModalMap['2'] &&
+
+ }
+
+ {type === ModalMap['3'] &&
+
+ }
+ {type === ModalMap['6'] && (
+
+ )}
+ {type === ModalMap['7'] && (
+
+ )}
+ {type === ModalMap['8'] && (
+
+ )}
+ {type === ModalMap['9'] && (
+
+ )}
+ {type === ModalMap['4'] && (
+
+ )}
+
+ {type === ModalMap['5'] && (
+
+ )}
+ {type === ModalMap['10'] && (
+
+ )}
+ {type === ModalMap['11'] && (
+
+ )}
+
+ }
+ >
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/style.ts b/FRONT/DEV/src/components/modalwindows/style.ts
new file mode 100644
index 0000000..f0a2639
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/style.ts
@@ -0,0 +1,71 @@
+import styled from "styled-components";
+
+export const ModalWindow = styled.div`
+ width: 100%;
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 9999;
+ background-color: rgba(0, 0, 0, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+`
+
+export const ModalContainer = styled.div<{height?: string, width?:string}>`
+ border-radius: 12px;
+ background: ${(props)=>props.theme.secondary};
+ color: ${(props=>props.theme.textColor)};
+ position: relative;
+ max-height: 100%;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 15px;
+ height: ${(props) => props.height ? props.height : '230px'};
+ width: ${(props) => props.width ? props.width : '520px'};
+`
+
+export const ModalTitle = styled.div`
+ padding: 20px;
+ border-bottom: 1px solid #9D9D9D;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ h3{
+ font-size: 18px;
+ }
+`
+
+export const ModalContent = styled.div`
+ padding: 0 20px;
+
+ h3{
+ font-size: 14px;
+ }
+
+ p{
+ font-size: 12px;
+ }
+`
+
+export const ModalText = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+`
+
+export const ErrorMessage = styled.p`
+ color: #d95454;
+`
+
+export const ButtonContainer = styled.div`
+ justify-content: flex-end;
+ display: flex;
+ gap: 10px;
+ padding: 10px 20px 20px;
+`
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/modalwindows/windowsType.ts b/FRONT/DEV/src/components/modalwindows/windowsType.ts
new file mode 100644
index 0000000..9193a6f
--- /dev/null
+++ b/FRONT/DEV/src/components/modalwindows/windowsType.ts
@@ -0,0 +1,15 @@
+export type ModalType = Record;
+
+export const ModalMap: ModalType = {
+ '1': 'ipConfirm',
+ '2': 'loader',
+ '3': 'connectionError',
+ '4': 'addDevice',
+ '5': 'sudo',
+ '6': 'noteDevice',
+ '7': 'noteWifi',
+ '8': 'connectNetwork',
+ '9': 'disconnectConfirm',
+ "10": 'settings',
+ '11': 'customNodes',
+}
diff --git a/FRONT/DEV/src/components/register.tsx b/FRONT/DEV/src/components/register.tsx
new file mode 100644
index 0000000..11b0b59
--- /dev/null
+++ b/FRONT/DEV/src/components/register.tsx
@@ -0,0 +1,200 @@
+import { ModalContainer, ModalTitle, ModalContent, ModalText } from "./modalwindows/style"
+import Input from "./UI/inputs/input"
+import { MainButton } from "./UI/buttons/mainButton"
+import styled from "styled-components"
+import { useEffect, useState } from "react"
+import { useIpStore } from "../store"
+import { NetworkService } from "../api/request"
+import { IconComponent } from "../const/buttons/icons"
+import { buttonNames } from "../const/buttons/names"
+import ThemeButton from "./UI/buttons/toggletheme"
+import About from "./workSpace/modules/appModules/about"
+import {useTranslation} from "../utils/i18n/hooks";
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ background: ${(props) => props.theme.background};
+`
+
+const Container = styled.form`
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ align-items: center;
+ background: ${(props) => props.theme.primary};
+ padding: 25px;
+ border-radius: 18px;
+ width: 350px;
+`
+
+const Title = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ align-items: center;
+ text-align: center;
+ font-size: 20px;
+`
+
+const Content = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ text-align: center;
+`
+
+const ButtonContainer = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: center;
+`
+
+const Menu = styled.div`
+ display: flex;
+ align-items: center;
+`
+
+const Button = styled.div`
+ width: 100%;
+ cursor: pointer;
+`
+
+const IconContainer = styled.div`
+ padding: 10px;
+ color: ${props => props.theme.text};
+`
+
+export const CreateUser = () => {
+
+ const { ipAddress, setIpAddress } = useIpStore();
+ const [repeatPassword, setRepeatPassword] = useState('');
+ const [passwordsMatch, setPasswordsMatch] = useState(true);
+ const [about, setAbout] = useState(false)
+ const {t} = useTranslation('common')
+
+ const [user, setUser] = useState({
+ username: 'operator',
+ password: '',
+ is_admin: true
+ })
+
+ const handleChange = (id: string, value: string) => {
+ if (id === 'repeatPassword') {
+ setRepeatPassword(value);
+ setPasswordsMatch(user.password === value);
+ } else {
+ setUser(prevData => ({
+ ...prevData,
+ [id]: value
+ }));
+ if (id === 'password') {
+ setPasswordsMatch(value === repeatPassword);
+ }
+ }
+ }
+
+ const handleSave = async (user: object) => {
+ try {
+ const result = await NetworkService.createUser(ipAddress, user);
+ if (result.status === 200) { window.location.reload() }
+ } catch (err) {
+ console.error('Error:', err);
+ }
+ }
+
+ const handleSubmit = () => {
+ if (passwordsMatch) {
+ handleSave(user);
+ }
+ };
+
+ const back = ()=>{
+ setIpAddress('localhost')
+ }
+
+ const toAbout = ()=>{
+ setAbout(true)
+ }
+ return (
+ <>
+ {!about ? (
+
+
+
+
+ {t('operatorConfig-title')}
+ {ipAddress != 'localhost' &&
+ {ipAddress}
+ }
+
+
+
+
+
{t('operatorConfig-instruction')}
+
+
+
+
+
+
+
+
+
+
+
+ {ipAddress != 'localhost' &&
+
+ }
+
+
+
+
+
+
+
+
+ ) : (
+ setAbout(false)} />
+ )}
+ >
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/settings/SettingItem.tsx b/FRONT/DEV/src/components/settings/SettingItem.tsx
new file mode 100644
index 0000000..69ed138
--- /dev/null
+++ b/FRONT/DEV/src/components/settings/SettingItem.tsx
@@ -0,0 +1,226 @@
+import {Setting} from "../../store/settings/settingsStore";
+import {useState} from "react";
+import styled, {useTheme} from "styled-components";
+import {IconComponent} from "../../const/buttons/icons";
+import {buttonNames} from "../../const/buttons/names";
+import SettingSelect from "./SettingSelect";
+import {MainButton} from "../UI/buttons/mainButton";
+import React from 'react';
+import {useModalContext} from "../../store";
+import {ModalMap} from "../modalwindows/windowsType";
+import {useTranslation} from "../../utils/i18n/hooks";
+
+const SettingBlock = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 24px;
+ margin-bottom: 18px;
+ font-size: 18px;
+ width: 90%;
+ input{
+ font-size: 16px;
+ outline: none;
+ background-color: ${props => props.theme.primary};
+ color: ${props => props.theme.textColor};
+ border: 1px solid ${props => props.theme.borderColor};
+ padding: 6px 8px;
+ border-radius: 4px;
+ }
+ input[type="number"]{
+ width: 80px;
+ }
+ input[type="number"]::-webkit-inner-spin-button,
+ input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ }
+`
+const StyledCheckbox = styled.input.attrs({ type: 'checkbox' })`
+ width: 22px !important;
+ height: 22px !important;
+ appearance: none;
+ -webkit-appearance: none;
+ position: relative;
+ cursor: pointer;
+ overflow: hidden;
+
+ &::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background: ${props => props.theme.primary};
+ top: 0;
+ left: 0;
+ }
+ &:checked::after {
+ background: ${props => props.theme.accent};
+ }
+
+`;
+const LabelCheckbox = styled.label`
+ position: relative;
+ cursor: pointer;
+ padding: 0;
+ svg{
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+`
+const InputWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: end;
+ gap: 6px;
+`
+const ErrorText = styled.div`
+ color: ${props => props.theme.errorColor};
+ font-size: 12px;
+`;
+
+interface SettingItemProps {
+ setting: Setting;
+ value: any;
+ onChange: (value: any) => void;
+ onValidationError?: (error: string | null) => void;
+}
+
+const SettingItem = React.memo(({ setting, value, onChange, onValidationError }: SettingItemProps) => {
+ const theme = useTheme();
+ const { t } = useTranslation('common');
+ const [error, setError] = useState(null);
+ const { openModal } = useModalContext();
+
+ const handleButtonAction = () => {
+ if (setting.id === 'customize-nodes') {
+ openModal(ModalMap['11']);
+ } else if (setting.action) {
+ // Создаем обертку для действия с переводами
+ const actionWithTranslations = async () => {
+ try {
+ // Временно заменяем alert на версию с переводами
+ const originalAlert = window.alert;
+ window.alert = (message: string) => {
+ // Применяем переводы к сообщениям
+ let translatedMessage = message;
+ if (message.startsWith('settings-dataNotDeleted')) {
+ translatedMessage = t('settings-dataNotDeleted') + message.replace('settings-dataNotDeleted', '');
+ } else if (message === 'settings-exportSuccess') {
+ translatedMessage = t('settings-exportSuccess');
+ } else if (message.startsWith('settings-exportError')) {
+ translatedMessage = t('settings-exportError') + message.replace('settings-exportError', '');
+ }
+ originalAlert(translatedMessage);
+ };
+
+ if (setting.action) {
+ await setting.action();
+ }
+
+ // Восстанавливаем оригинальный alert
+ window.alert = originalAlert;
+ } catch (error) {
+ console.error('Action error:', error);
+ }
+ };
+ actionWithTranslations();
+ }
+ };
+
+ // Изменение числа
+ const handleNumberChange = (e: React.ChangeEvent) => {
+ const inputValue = e.target.value;
+ let newError: string | null = null;
+
+ if (inputValue.trim() === '') {
+ newError = t('settings-fieldRequired');
+ onChange(null);
+ }
+ else if (isNaN(Number(inputValue))) {
+ newError = t('settings-enterNumber');
+ }
+ else {
+ const numValue = Number(inputValue);
+ if (setting.min !== undefined && numValue < setting.min) {
+ newError = t('settings-valueTooSmall') + setting.min;
+ }
+ else if (setting.max !== undefined && numValue > setting.max) {
+ newError = t('settings-valueTooLarge') + setting.max;
+ }
+
+ if (!newError) {
+ onChange(numValue);
+ }
+ }
+
+ setError(newError);
+ onValidationError?.(newError);
+ };
+
+ return (
+
+ {setting.label}
+
+ {setting.type === 'boolean' && (
+
+ onChange(e.target.checked)}
+ />
+
+
+ )}
+
+ {setting.type === 'select' && setting.options && (
+ onChange(value)}
+ />
+ )}
+
+ {setting.type === 'text' && (
+ onChange(e.target.value)}
+ />
+ )}
+
+ {setting.type === 'number' && (
+
+ {
+ if (!/[0-9]/.test(e.key) &&
+ !['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
+ e.preventDefault();
+ }
+ }}
+ className={error ? 'error' : ''}
+ />
+ {error && {error} }
+
+ )}
+
+ {setting.type === 'button' && (
+
+ )}
+ {setting.type === 'hidden' && null}
+
+ );
+});
+
+export { SettingItem };
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/settings/SettingSelect.tsx b/FRONT/DEV/src/components/settings/SettingSelect.tsx
new file mode 100644
index 0000000..d6ff465
--- /dev/null
+++ b/FRONT/DEV/src/components/settings/SettingSelect.tsx
@@ -0,0 +1,118 @@
+import React, { useState, useRef, useEffect } from 'react';
+import styled from 'styled-components';
+import { IconComponent } from '../../const/buttons/icons';
+import {buttonNames} from "../../const/buttons/names";
+
+interface SettingSelectProps {
+ value: any;
+ options: { value: any; label: string }[];
+ onChange: (value: any) => void;
+ disabled?: boolean;
+}
+
+const SelectWrapper = styled.div`
+ position: relative;
+ width: 100%;
+ max-width: 180px;
+`;
+
+const SelectHeader = styled.div<{ $isOpen: boolean; $disabled?: boolean }>`
+ padding: 6px 8px;
+ border: 1px solid ${props => props.theme.borderColor};
+ border-radius: 4px;
+ background-color: ${props => props.theme.primary};
+ cursor: ${props => props.$disabled ? 'not-allowed' : 'pointer'};
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ opacity: ${props => props.$disabled ? 0.6 : 1};
+ font-size: 16px;
+`;
+
+const SelectOptions = styled.div<{ $isOpen: boolean }>`
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ margin-top: 4px;
+ max-height: 200px;
+ overflow-y: auto;
+ background: ${props => props.theme.primary};
+ border: 1px solid ${props => props.theme.borderColor || '#ccc'};
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ z-index: 10;
+ display: ${props => props.$isOpen ? 'block' : 'none'};
+`;
+
+const OptionItem = styled.div<{ $isSelected: boolean }>`
+ padding: 6px 8px;
+ cursor: pointer;
+ background-color: ${props => props.$isSelected ? (props.theme.accent) : 'transparent'};
+ color: ${props => props.$isSelected ? (props.theme.primary) : 'inherit'};
+`;
+
+const ArrowIcon = styled.span<{ $isOpen: boolean }>`
+ transition: transform 0.2s;
+ transform: rotate(${props => props.$isOpen ? '90deg' : '0deg'});
+ display: flex;
+ align-items: center;
+`;
+
+const SettingSelect: React.FC = ({value, options, onChange, disabled = false
+ }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const selectRef = useRef(null);
+
+ const selectedOption = options.find(opt => opt.value === value);
+
+ const toggleDropdown = () => {
+ if (!disabled) {
+ setIsOpen(!isOpen);
+ }
+ };
+
+ const handleSelect = (optionValue: any) => {
+ onChange(optionValue);
+ setIsOpen(false);
+ };
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (selectRef.current && !selectRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ return (
+
+
+ {selectedOption?.label || 'Select...'}
+
+
+
+
+
+ {options.map((option) => (
+ handleSelect(option.value)}
+ >
+ {option.label}
+
+ ))}
+
+
+ );
+};
+
+export default SettingSelect;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/settings/Settings.tsx b/FRONT/DEV/src/components/settings/Settings.tsx
new file mode 100644
index 0000000..57edc9d
--- /dev/null
+++ b/FRONT/DEV/src/components/settings/Settings.tsx
@@ -0,0 +1,264 @@
+import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../modalwindows/style";
+import { MainButton } from "../UI/buttons/mainButton";
+import {useCallback, useEffect, useMemo, useState} from "react";
+import styled, {useTheme} from "styled-components";
+import {Setting, useSettingsStore} from "../../store/settings/settingsStore";
+import {SettingItem} from "./SettingItem";
+import {IconComponent} from "../../const/buttons/icons";
+import {buttonNames} from "../../const/buttons/names";
+import {useTranslation} from "../../utils/i18n/hooks";
+
+
+interface Props {
+ onClose: () => void;
+}
+
+const SectionsSidebar = styled.ul`
+ display: flex;
+ justify-content: start;
+ align-items: start;
+ flex-direction: column;
+ gap: 4px;
+ width: 20%;
+ min-width: 180px;
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+`
+const SectionsBTN = styled.li`
+ font-size: 18px;
+ border: none;
+ cursor: pointer;
+ width: 100%;
+ padding:10px 16px;
+ box-sizing: border-box;
+ border-radius: 0 12px 12px 0;
+ &.active{
+ background-color: ${(props) => props.theme.accent};
+ color: ${props => props.theme.primary};
+ }
+`
+const SectionActive = styled.div`
+ width: 80%;
+`
+const SectionLine = styled.span`
+ height: 100%;
+ width: 1px;
+ background-color: #9D9D9D;
+`
+const CloseBtn = styled.span`
+ cursor: pointer;
+`
+const SettingsButtons = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ span{
+ display: flex;
+ gap: 12px;
+ }
+`
+export const Settings = ({ onClose }: Props) => {
+ const settingsConfig = useSettingsStore(state => state.settingsConfig);
+ const settingsValues = useSettingsStore(state => state.settingsValues);
+ const activeSection = useSettingsStore(state => state.activeSection);
+ const pendingChanges = useSettingsStore(state => state.pendingChanges);
+ const setActiveSection = useSettingsStore(state => state.setActiveSection);
+ const updateSetting = useSettingsStore(state => state.updateSetting);
+ const applyChanges = useSettingsStore(state => state.applyChanges);
+ const discardChanges = useSettingsStore(state => state.discardChanges);
+ const getSetting = useSettingsStore(state => state.getSetting);
+ const resetToDefaults = useSettingsStore(state => state.resetToDefaults);
+ const initializeSettings = useSettingsStore(state => state.initializeSettings);
+ const updateSettingsConfig = useSettingsStore(state => state.updateSettingsConfig);
+
+ const theme = useTheme()
+ const { t, language } = useTranslation('common');
+ const [fieldErrors, setFieldErrors] = useState>({});
+ const [currentLanguage, setCurrentLanguage] = useState(language);
+
+ // Проверка действий
+ const handleValidationError = useCallback((fieldId: string, error: string | null) => {
+ setFieldErrors(prev => {
+ const newErrors = { ...prev };
+ if (error) {
+ newErrors[fieldId] = error;
+ } else {
+ delete newErrors[fieldId];
+ }
+ return newErrors;
+ });
+ }, []);
+
+ const settingsBySection = useMemo(() => {
+ return settingsConfig.reduce((acc, setting) => {
+ // Применяем переводы к настройкам
+ const translatedSetting = {
+ ...setting,
+ sectionId: t(setting.sectionId),
+ label: t(setting.label),
+ defaultValue: typeof setting.defaultValue === 'string' && setting.defaultValue.startsWith('settings-') ? t(setting.defaultValue) : setting.defaultValue,
+ options: setting.options ? setting.options.map((option: any) => ({
+ ...option,
+ label: option.label.startsWith('settings-') ? t(option.label) : option.label
+ })) : setting.options,
+ value: settingsValues[setting.id] ?? setting.defaultValue
+ };
+
+ if (!acc[translatedSetting.sectionId]) {
+ acc[translatedSetting.sectionId] = [];
+ }
+ acc[translatedSetting.sectionId].push(translatedSetting);
+ return acc;
+ }, {} as Record);
+ }, [settingsConfig, settingsValues, t]);
+
+ const sections = useMemo(() => Object.keys(settingsBySection), [settingsBySection]);
+
+ useEffect(() => {
+ updateSettingsConfig();
+ initializeSettings();
+ }, [updateSettingsConfig, initializeSettings]);
+
+ useEffect(() => {
+ if (sections.length > 0 && !activeSection) {
+ setActiveSection(sections[0]);
+ }
+ }, [sections, activeSection, setActiveSection]);
+
+ // Обновляем текущий язык при изменении
+ useEffect(() => {
+ setCurrentLanguage(language);
+ }, [language]);
+
+ const handleSave = useCallback(() => {
+ const hasErrors = Object.values(fieldErrors).some(error => error?.trim());
+ if (hasErrors) {
+ alert(t('settings-fixErrors'));
+ return;
+ }
+
+ const requiredFields = settingsConfig.filter(s => s.required);
+ const emptyRequiredFields = requiredFields.filter(f => {
+ const value = pendingChanges[f.id] ?? settingsValues[f.id] ?? f.defaultValue;
+ return value == null || value === '';
+ });
+
+ if (emptyRequiredFields.length > 0) {
+ alert(t('settings-fillRequired'));
+ return;
+ }
+
+ // Проверяем, изменился ли язык
+ const newLanguage = pendingChanges['language'] ?? settingsValues['language'];
+ const languageChanged = newLanguage && newLanguage !== currentLanguage;
+
+ applyChanges();
+ onClose();
+
+ // Если язык изменился, перезагружаем приложение
+ if (languageChanged) {
+ setTimeout(() => {
+ window.location.reload();
+ }, 100);
+ }
+ }, [fieldErrors, settingsConfig, pendingChanges, settingsValues, applyChanges, onClose, t, currentLanguage]);
+
+ const handleCancel = useCallback(() => {
+ discardChanges();
+ setFieldErrors({});
+ onClose();
+ }, [discardChanges, onClose]);
+
+ const handleReset = () => {
+ if (window.confirm(t('settings-resetConfirm'))) {
+ resetToDefaults();
+ }
+ };
+
+ // Обработка нажатий клавиш
+ useEffect(() => {
+ const handleKeyPress = (e: KeyboardEvent) => {
+ if (e.key === 'Enter')
+ handleSave();
+ if (e.key === 'Escape')
+ handleCancel()
+ };
+ document.addEventListener('keydown', handleKeyPress);
+ return () => {
+ document.removeEventListener('keydown', handleKeyPress);
+ };
+ }, [handleSave, handleCancel]);
+
+
+ return (
+
+
+ {t('settings-title')}
+
+
+
+
+
+
+
+ {sections.map((sectionId) => (
+ setActiveSection(sectionId)}
+ >
+ {sectionId}
+
+ ))}
+
+
+
+ {activeSection && settingsBySection[activeSection] && (
+ <>
+ {settingsBySection[activeSection].map((setting) => (
+ handleValidationError(setting.id, error)}
+ onChange={(value) => updateSetting(setting.id, value)}
+ />
+ ))}
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx
new file mode 100644
index 0000000..8309157
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx
@@ -0,0 +1,252 @@
+
+import { MainButton } from "../../../UI/buttons/mainButton"
+import { workSpaceNames } from "../../../../const/workspace/names"
+import { useWorkSpaceStore } from "../../../../store"
+import styled, {useTheme} from "styled-components"
+import ThemeButton from "../../../UI/buttons/toggletheme"
+import {buttonNames} from "../../../../const/buttons/names";
+import {IconComponent} from "../../../../const/buttons/icons";
+import {useTranslation} from "../../../../utils/i18n/hooks";
+
+type Props = {
+ from: 'reg' | 'helloWindow' | 'workSpace'
+ onBack?: () => void
+}
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ width: 100%;
+ background: ${(props => props.theme.background)};
+ padding: 50px;
+ box-sizing: border-box;
+`
+
+const Content = styled.div`
+ max-width: 1200px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ box-sizing: border-box;
+ padding: 40px 50px;
+ background: ${(props => props.theme.primary)};
+ border-radius: 18px;
+ position: relative;
+`
+
+const Listing = styled.div`
+ display: flex;
+ flex-direction: column;
+ background-color: ${(props => props.theme.secondary)};
+ border-radius: 10px;
+ padding: 24px 20px;
+ p{
+ font-weight: bold;
+ font-size: 24px;
+ }
+`
+
+const List = styled.ul`
+ display: flex;
+ flex-direction: column;
+ margin: 10px 0;
+ padding: 0;
+ list-style-type: none;
+ font-size: 18px;
+ gap: 4px;
+ & li:before {
+ color: ${(props => props.theme.accent)};
+ content: "•";
+ font-size: 28px;
+ width: .8em;
+ display: inline-block;
+ position: relative;
+ top: 2px;
+ }
+`
+
+const Title = styled.div`
+ font-size: 20px;
+`
+
+const SubTitle = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ background-color:rgba(255, 248, 225, 0.6);
+ border-left: #ff9800 solid 4px;
+ border-radius: 0 8px 8px 0;
+ padding: 20px 12px;
+
+ a{
+ text-decoration: none;
+ color: ${(props => props.theme.errorColor)};
+ font-weight: 500;
+ font-size: 18px;
+ width: max-content;
+ }
+ p{
+ font-size: 18px;
+ color: ${(props=>props.theme.textColor)};
+ }
+ .icon{
+ width: 32px;
+ height: 32px;
+ fill: #ff9800;
+ }
+`
+
+const Links = styled.div`
+ display: flex;
+ justify-content: start;
+ gap: 20px;
+`
+
+const LinkBlock = styled.a`
+ text-decoration: none;
+ color: ${(props => props.theme.accent)};;
+`
+
+const Copyright = styled.div`
+ width: 100%;
+ text-align: center;
+ color: ${(props => props.theme.offlineColor)};
+`
+
+const ButtonContainer = styled.div`
+ position: absolute;
+ top: 12px;
+ right: 14px;
+`
+const AboutLine = styled.hr`
+ border: ${(props => props.theme.hoverBtn)} 1px solid;
+ width: 100%;
+`
+
+const MainTitle = styled.div`
+ width: 100%;
+ display: flex;
+ gap: 15px;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 10px;
+ box-sizing: border-box;
+
+ img{
+ width: 50px;
+ height: 50px;
+ }
+ .version{
+ text-wrap: nowrap;
+ background-color: ${(props => props.theme.hoverBtn)};
+ color: ${(props => props.theme.borderColor)};
+ border-radius: 20px;
+ font-size: 18px;
+ padding: 10px 18px;
+ line-height: 1;
+ }
+ .title{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 24px;
+ }
+`
+
+export default function About({ from, onBack }: Props) {
+ const { setWorkModule } = useWorkSpaceStore()
+ const {t} = useTranslation('common')
+ const getBackHandler = () => {
+ switch (from) {
+ case 'reg':
+ return onBack
+ case 'helloWindow':
+ return () => setWorkModule(workSpaceNames[1])
+ case 'workSpace':
+ return () => setWorkModule(workSpaceNames[3])
+ default:
+ return undefined
+ }
+ }
+
+ const backHandler = getBackHandler()
+ const showBackButton = from !== 'reg' || (from === 'reg' && onBack)
+
+ return (
+
+
+
+
+
+
+
{t('about-title')}
+
+
+ v 0.0.3
+
+
+
+
+
+ {t('about-description')}
+
+
+
+ {t('about-featuresTitle')}:
+
+ {t('about-feature1')}
+ {t('about-feature2')}
+ {t('about-feature3')}
+
+
+
+
+
+
+
+ {t('about-disclaimer')}
+
+ {t('about-goToPro')}
+
+
+
+
+
+ {t('about-homepage')}
+
+
+
+ {t('about-documentation')}
+
+
+
+ {t('about-license')}
+
+
+
+
+ {t('about-copyright')}
+
+
+
+ {showBackButton && backHandler && (
+
+ )}
+
+ {from == 'reg' &&
+
+ }
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/header.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/header.tsx
new file mode 100644
index 0000000..356764b
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/header.tsx
@@ -0,0 +1,46 @@
+import styled from "styled-components"
+import { IconComponent } from "../../../../const/buttons/icons"
+import { buttonNames } from "../../../../const/buttons/names"
+import { useWorkSpaceStore } from "../../../../store"
+import { useModalContext } from "../../../../store"
+import { useAdminStore } from "../../../../store/adminStore"
+import { ModalMap } from "../../../modalwindows/windowsType"
+import { QuitButton } from "../../../UI/buttons/quitButton"
+import { usePosStore } from "../../../../store/posStore"
+
+const Container = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ background-color: ${(props) => props.theme.primary};
+ padding: 0 5px;
+`
+
+const ButtonContainer = styled.div`
+ display: flex;
+ align-items: center;
+`
+
+const IconContainer = styled.div`
+ padding: 10px;
+
+ svg{
+ cursor: pointer;
+ }
+`
+
+export const Header = () => {
+ const {setNullPos} = usePosStore()
+
+ return (
+
+
+
+ setNullPos(true)}/>
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx
new file mode 100644
index 0000000..1761515
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx
@@ -0,0 +1,86 @@
+ import styled from "styled-components"
+ import { MainButton } from "../../../UI/buttons/mainButton"
+ import { useWorkSpaceStore } from "../../../../store"
+ import { workSpaceNames } from "../../../../const/workspace/names"
+ import { buttonNames } from "../../../../const/buttons/names"
+ import {useTranslation} from "../../../../utils/i18n/hooks";
+
+ const Wrapper = styled.div`
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: ${(props => props.theme.background)};
+ background-image: url('./assets/images/back1.svg');
+ background-size: 100%;
+ background-repeat: no-repeat;
+ background-position: bottom;
+ `
+
+ const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 52px;
+ margin-bottom: 100px;
+ `
+
+ const ButtonWrapper = styled.div`
+ display: flex;
+ gap: 75px;
+ flex-direction: column;
+ `
+
+ const ButtonContainer = styled.div`
+ display: flex;
+ gap: 15px;
+ flex-direction: column;
+ `
+
+ const Title = styled.div`
+ display: flex;
+ align-items: flex-end;
+ gap: 10px;
+
+ h1{
+ font-size: 52px;
+ }
+ `
+
+ export const HelloWindow = () => {
+ const {t} = useTranslation('common')
+ const { setWorkModule } = useWorkSpaceStore()
+
+ return (
+
+
+
+
+
+
+
+
+ setWorkModule(workSpaceNames[3])}
+ />
+ setWorkModule(workSpaceNames[4])}
+ />
+
+
+
+
+ )
+ }
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/IpScanning.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/IpScanning.tsx
new file mode 100644
index 0000000..1dc7b4f
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/IpScanning.tsx
@@ -0,0 +1,85 @@
+import React, {useState} from 'react';
+import styled, {keyframes} from 'styled-components';
+import {useTranslation} from "../../../../../../utils/i18n/hooks";
+
+interface Props {
+ value: string;
+ setIpScanning: (value: string) => void;
+ loading: boolean;
+ isValid: boolean;
+ setIsValid: (value: boolean) => void;
+}
+const Input = styled.input`
+ height: 40px;
+ width: 210px;
+ font-size: 20px;
+ text-align: center;
+ background-color: transparent;
+ border: none;
+ outline: none;
+ border-bottom: 2px solid ${props => props.theme.offlineColor};
+ color: ${props => props.theme.textColor};
+ transition: all 0.3s ease;
+ &:focus {
+ border-bottom: 2px solid ${props => props.theme.accent};
+ width: 270px;
+ }
+ &::placeholder {
+ color: ${props => props.theme.offlineColor};
+ transition: all 0.3s ease;
+ }
+
+ &:focus::placeholder {
+ opacity: 0;
+ }
+ &:disabled {
+ opacity: 0;
+ }
+`
+const errorVisible = keyframes`
+ 0%{opacity: 0}
+ 100%{opacity: 1}
+`;
+const ErrorMessage = styled.span`
+ margin-top: 6px;
+ color: ${props=>props.theme.errorColor};
+ animation: ${errorVisible} .4s linear;
+`
+
+
+const IpScanning = ({value, setIpScanning, loading, isValid, setIsValid}: Props) => {
+ const {t} = useTranslation('common')
+ const validateInput = (e: React.KeyboardEvent) => {
+ const allowedKeys = [
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '.', '/',
+ 'Backspace', 'Delete',
+ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'
+ ];
+
+ if (!allowedKeys.includes(e.key)) {
+ e.preventDefault();
+ }
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ setIpScanning(e.target.value);
+ }
+
+ return (
+ <>
+
+ {!isValid && !loading && (
+ {t('ipInput-error')}
+ )}
+ >
+ );
+};
+
+export default IpScanning;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx
new file mode 100644
index 0000000..513691c
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx
@@ -0,0 +1,115 @@
+import React, {useEffect, useState} from 'react';
+import styled from 'styled-components';
+import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
+import {DeviceUser} from "../../../../../../store/useDeviceStore";
+import {usePaginateSetting} from "../../../../../../hooks/useSettings";
+import Pagination from "../../../../../UI/Pagination";
+import {useTranslation} from "../../../../../../utils/i18n/hooks";
+
+const ScanBtn = styled.button`
+ position: relative;
+ padding: 14px 22px;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 18px;
+ transition: .4s ease;
+ &:hover{
+ transform: scale(1.03);
+ }
+`
+
+interface ListUsersProps {
+ loading: boolean;
+ handleScan: ()=>void;
+ devices: DeviceUser[];
+ scanInitiated: boolean;
+}
+
+const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
+ const {t} = useTranslation('common')
+ const activeDevices = devices.filter(device => device.status === 'online');
+ const inactiveDevices = devices.filter(device => device.status !== 'online');
+
+ // Пагинация
+ const paginateSetting = usePaginateSetting();
+ const [currentPage, setCurrentPage] = useState(1);
+ const [devicesPerPage, setDevicesPerPage] = useState(paginateSetting);
+
+ useEffect(() => {
+ setDevicesPerPage(paginateSetting);
+ }, [paginateSetting]);
+
+ const indexOfLastDevice = currentPage * devicesPerPage;
+ const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;
+ const currentActiveDevices = activeDevices.slice(indexOfFirstDevice, indexOfLastDevice);
+ const currentInactiveDevices = inactiveDevices.slice(indexOfFirstDevice, indexOfLastDevice);
+ const totalPages = Math.ceil(devices.length / devicesPerPage);
+
+ const handlePrevPage = () => {
+ if(currentPage > 1) setCurrentPage(currentPage - 1);
+ }
+ const handleNextPage = () => {
+ if(currentPage < totalPages) setCurrentPage(currentPage + 1);
+ }
+ const onPageChange = (page: number) => {
+ setCurrentPage(page);
+ }
+
+ return (
+ <>
+
+ {loading ? t('userScanner-loading') : devices.length > 0 ? t('userScanner-refresh') : t('userScanner-scan')}
+
+
+ {devices.length > 0 ? (
+
+ {/* Колонка активных устройств */}
+
+
+ {t('listUsers-activeNodes')} ({activeDevices.length})
+
+ {currentActiveDevices.map((device, index) => (
+
+ ))}
+
+ {/* Колонка неактивных устройств */}
+
+
+ {t('listUsers-inactiveNodes')} ({inactiveDevices.length})
+
+ {currentInactiveDevices.map((device, index) => (
+
+ ))}
+
+
+ ) : scanInitiated && !loading ? (
+ {t('userScanner-notFound')}
+ ) : null}
+ {totalPages > 1 && (
+
+ )}
+ >
+ );
+};
+
+export default ListUsers;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableScanner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableScanner.tsx
new file mode 100644
index 0000000..ccd405d
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableScanner.tsx
@@ -0,0 +1,268 @@
+import React, {useEffect, useMemo, useState} from 'react';
+import { DeviceUser } from '../../../../../../store/useDeviceStore';
+import styled from 'styled-components';
+import {IconComponent} from "../../../../../../const/buttons/icons";
+import {buttonNames} from "../../../../../../const/buttons/names";
+import {ModalMap} from "../../../../../modalwindows/windowsType";
+import {useModalContext} from "../../../../../../store";
+import {useTranslation} from "../../../../../../utils/i18n/hooks";
+import Pagination from "../../../../../UI/Pagination";
+import {
+ useDisabledColumns,
+ useNameSetting,
+ useOrdersColumns,
+ usePaginateSetting
+} from "../../../../../../hooks/useSettings";
+
+interface TableScannerProps {
+ devices: DeviceUser[];
+}
+
+const TableContainer = styled.div`
+ width: 100%;
+ margin: 24px 0;
+`;
+
+const StyledTable = styled.table`
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
+ background: ${({ theme }) => theme.primary};
+`;
+
+const TableHeaderCell = styled.th<{ sortable?: boolean }>`
+ padding: 16px 24px;
+ text-align: left;
+ font-weight: 600;
+ font-size: 14px;
+ color: ${({ theme }) => theme.textColor};
+ background: ${({ theme }) => theme.primary};
+ border-bottom: 1px solid ${({ theme }) => theme.borderColor};
+ transition: all 0.2s ease;
+`;
+
+const TableRow = styled.tr<{ clickable?: boolean }>`
+ transition: all 0.2s ease;
+ &:hover {
+ background-color: ${({ theme }) => theme.background};
+ }
+`;
+
+const TableCell = styled.td`
+ padding: 16px 24px;
+ font-size: 14px;
+ color: ${({ theme }) => theme.text};
+ border-bottom: 1px solid ${({ theme }) => theme.borderColor};
+ transition: all 0.2s ease;
+ button{
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ }
+`;
+
+const StatusBadge = styled.span<{ status:string }>`
+ width: 10px;
+ height: 30px;
+ background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.offlineColor)};
+ display: inline-block;
+ border-radius:4px;
+`
+
+export const TableScanner = ({ devices }: TableScannerProps) => {
+ const { openModal } = useModalContext();
+ const nameSetting = useNameSetting();
+ const ordersColumns = useOrdersColumns();
+ const disabledColumns = useDisabledColumns();
+ const paginateSetting = usePaginateSetting();
+ const {t} = useTranslation('common');
+
+
+ const [currentPage, setCurrentPage] = useState(1);
+ const [devicesPerPage, setDevicesPerPage] = useState(paginateSetting);
+
+ console.log(ordersColumns);
+
+ useEffect(() => {
+ setDevicesPerPage(paginateSetting);
+ }, [paginateSetting]);
+
+ const openModalNote = (e: React.MouseEvent, device: DeviceUser) => {
+ e.stopPropagation();
+ openModal(ModalMap['6'], {
+ address: device.mac,
+ currentNote: device.note || ''
+ });
+ };
+
+ const defaultOrder = useMemo(() => ([
+ t('settings-status'),
+ t('settings-name'),
+ t('settings-vendor'),
+ t('settings-ipAddress'),
+ t('settings-openPorts'),
+ t('settings-lastActivity'),
+ t('settings-note'),
+ t('settings-action'),
+ ]), [t]);
+
+ const normalizedOrders = useMemo(
+ () => (Array.isArray(ordersColumns) && ordersColumns.length ? ordersColumns : defaultOrder),
+ [ordersColumns, defaultOrder]
+ );
+ const normalizedDisabled = useMemo(
+ () => (Array.isArray(disabledColumns) ? disabledColumns : []),
+ [disabledColumns]
+ );
+
+ const visibleColumns = useMemo(() => {
+ const normalizeColumnName = (column: string) => column?.toLowerCase();
+
+ const columnToKey = (column: string) => {
+ const col = normalizeColumnName(column);
+ switch (col) {
+ case 'settings-status':
+ case normalizeColumnName(t('settings-status')):
+ case 'онлайн':
+ case 'status':
+ return 'status';
+ case 'settings-name':
+ case normalizeColumnName(t('settings-name')):
+ case 'имя':
+ case 'name':
+ return 'name';
+ case 'settings-vendor':
+ case normalizeColumnName(t('settings-vendor')):
+ case 'производитель':
+ case 'vendor':
+ return 'vendor';
+ case 'settings-ipaddress':
+ case normalizeColumnName(t('settings-ipAddress')):
+ case 'ip адрес':
+ case 'ip address':
+ case 'ip':
+ return 'ip';
+ case 'settings-openports':
+ case normalizeColumnName(t('settings-openPorts')):
+ case 'открытые порты':
+ case 'open ports':
+ case 'ports':
+ return 'ports';
+ case 'settings-lastactivity':
+ case normalizeColumnName(t('settings-lastActivity')):
+ case 'последняя активность':
+ case 'last activity':
+ case 'activity':
+ return 'activity';
+ case 'settings-note':
+ case normalizeColumnName(t('settings-note')):
+ case 'заметка':
+ case 'note':
+ return 'note';
+ case 'settings-action':
+ case normalizeColumnName(t('settings-action')):
+ case 'действие':
+ case 'action':
+ return 'action';
+ default:
+ return '';
+ }
+ };
+
+ return normalizedOrders
+ .map((column, index) => ({ column, index }))
+ .filter(({ index }) => !normalizedDisabled.includes(index))
+ .map(({ column }) => columnToKey(column))
+ .filter(Boolean);
+ }, [normalizedOrders, normalizedDisabled, t]);
+
+ // Пагинация
+ const indexOfLastDevice = currentPage * devicesPerPage;
+ const indexOfFirstDevice = indexOfLastDevice - devicesPerPage;
+ const currentDevices = devices.slice(indexOfFirstDevice, indexOfLastDevice);
+ const totalPages = Math.ceil(devices.length / devicesPerPage);
+
+ const handlePrevPage = () => {
+ if (currentPage > 1) setCurrentPage(currentPage - 1);
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < totalPages) setCurrentPage(currentPage + 1);
+ };
+
+ const onPageChange = (page: number) => {
+ setCurrentPage(page);
+ };
+
+ // Рендер ячейки таблицы
+ const renderTableCell = (column: string, item: DeviceUser) => {
+ switch (column) {
+ case 'status':
+ return ;
+ case 'name':
+ return {item.hostname || (nameSetting === 'ip' ? item.ip : 'Неизвестно')} ;
+ case 'vendor':
+ return {item.vendor} ;
+ case 'ip':
+ return {item.ip} ;
+ case 'ports':
+ return {item.open_ports.join(', ') || '-'} ;
+ case 'activity':
+ return {item.last_seen} ;
+ case 'note':
+ return {item.note || '-'} ;
+ case 'action':
+ return (
+
+ openModalNote(e, item)}>
+
+
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+ {visibleColumns.map(column => {
+ switch (column) {
+ case 'status': return ;
+ case 'name': return {t('settings-name')} ;
+ case 'vendor': return {t('settings-vendor')} ;
+ case 'ip': return {t('settings-ipAddress')} ;
+ case 'ports': return {t('settings-openPorts')} ;
+ case 'activity': return {t('settings-lastActivity')} ;
+ case 'note': return {t('settings-note')} ;
+ case 'action': return ;
+ default: return null;
+ }
+ })}
+
+
+
+ {currentDevices.map((item, index) => (
+
+ {visibleColumns.map(column => renderTableCell(column, item))}
+
+ ))}
+
+
+
+ {totalPages > 1 && (
+
+ )}
+
+ );
+};
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx
new file mode 100644
index 0000000..b548618
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import styled from 'styled-components';
+import {DeviceUser} from "../../../../../../store/useDeviceStore";
+import {TableScanner} from "./TableScanner";
+import {useTranslation} from "../../../../../../utils/i18n/hooks";
+
+const ScanBtn = styled.button`
+ position: relative;
+ padding: 14px 22px;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 18px;
+ transition: .4s ease;
+ &:hover{
+ transform: scale(1.03);
+ }
+`
+
+interface ListUsersProps {
+ loading: boolean;
+ handleScan: (replay?: boolean) => void;
+ devices: DeviceUser[];
+ scanInitiated: boolean;
+}
+
+const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
+ const {t} = useTranslation('common')
+
+ return (
+ <>
+ handleScan()} disabled={loading}>
+ {loading ? t('userScanner-loading') : devices.length > 0 ? t('userScanner-refresh') : t('userScanner-scan')}
+
+
+ {devices.length > 0 ? (
+
+ ) : scanInitiated && !loading ? (
+ {t('userScanner-notFound')}
+ ) : null}
+ >
+ );
+};
+
+export default ListUsers;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx
new file mode 100644
index 0000000..3ca659d
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx
@@ -0,0 +1,247 @@
+import { useIpStore } from "../../../../../store";
+import styled, { keyframes } from "styled-components";
+import {useEffect, useState, useTransition} from "react";
+import { NetworkService } from "../../../../../api/request";
+import ListUsers from "./modules/ListUsers";
+import TableUsers from "./modules/TableUsers";
+import DropdownType from "../../../../UI/Dropdown/DropdownType";
+import useNetworkStore from "../../../../../store/useDeviceStore";
+import {useIntervalBoolean, useIntervalTime} from "../../../../../hooks/useSettings";
+import IpScanning from "./modules/IpScanning";
+import {useTranslation} from "../../../../../utils/i18n/hooks";
+
+
+type Option ={
+ value: number,
+ label: string,
+}
+
+// Анимации
+const ring1 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
+`;
+
+const ring2 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
+`;
+
+const ring3 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
+`;
+const dotAnimation = keyframes`
+ 0% { content: '.'; }
+ 33% { content: '..'; }
+ 66% { content: '...'; }
+ 100% { content: '.'; }
+`;
+const pulse = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); }
+ 50% { transform: translate(-50%, -50%) scale(1.1); }
+ 100% { transform: translate(-50%, -50%) scale(1); }
+`;
+
+// Стили
+const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content:${({scanInitiated, loading})=>(scanInitiated && !loading ? 'start' : 'center')};
+ height: 100%;
+ width: 100%;
+ background: ${(props) => props.theme.background};
+ padding: 80px 50px 40px;
+ box-sizing: border-box;
+ gap: 24px;
+ transition: justify-content .4s ease;
+ overflow-y: auto;
+ overflow-x: hidden;
+ position: relative;
+`;
+const TopWrapper = styled.div`
+ position: absolute;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+`
+const TopTitle = styled.h2`
+ font-size: 32px;
+ width: max-content;
+ text-align: center;
+`
+const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ position: relative;
+ transition: .4s ease;
+ transform: ${({scanInitiated,loading})=>(scanInitiated && !loading ? 'scale(0)' : 'scale(1)')};
+ span {
+ position: absolute;
+ display: inline-block;
+ background: ${(props) => props.theme.scannerColor};
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ &.center {
+ width: 60px;
+ height: 60px;
+ animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
+ }
+ &.ring1 {
+ opacity: 0.12;
+ width: 250px;
+ height: 250px;
+ animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
+ }
+
+ &.ring2 {
+ opacity: 0.08;
+ width: 400px;
+ height: 400px;
+ animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
+ animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
+ }
+
+ &.ring3 {
+ opacity: 0.04;
+ width: 550px;
+ height: 550px;
+ animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
+ animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
+ }
+ }
+`;
+
+const LoadingText = styled.div`
+ position: relative;
+ padding: 14px 22px;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 18px;
+`;
+const ScanningText = styled.span`
+ &::after {
+ content: '.';
+ animation: ${dotAnimation} 1.5s infinite;
+ }
+`;
+
+
+export default function UserScanner() {
+ const {t} = useTranslation('common')
+ const { ipAddress } = useIpStore();
+ const [ipScanning, setIpScanning] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [isValid, setIsValid] = useState(true);
+ const { devices, setDevices } = useNetworkStore();
+ const [scanInitiated, setScanInitiated] = useState(devices.length > 0);
+
+ const validateIp = (ip: string) => {
+ const regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
+ return regex.test(ip);
+ };
+
+ // Сканирование
+ const handleScan = async (replay: boolean) => {
+ try {
+ if (ipScanning && !validateIp(ipScanning)) {
+ setIsValid(false);
+ return;
+ }
+
+ setIsValid(true);
+
+ if (!replay) {
+ setLoading(true);
+ }
+ setScanInitiated(true);
+ const res = await NetworkService.scanUser(ipAddress, ipScanning);
+ console.log(res)
+ setDevices(res.data.devices || []);
+ } catch (err) {
+ console.error("Ошибка сканирования сети:", err);
+ setDevices([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Интервально сканирование
+ const intervalToggle = useIntervalBoolean();
+ const intervalTime = useIntervalTime();
+ useEffect(() => {
+ let intervalId: NodeJS.Timeout | null = null;
+
+ if (intervalToggle) {
+ intervalId = setInterval(()=>handleScan(true), intervalTime * 60 * 1000);
+ }
+
+ return () => {
+ if (intervalId) {
+ clearInterval(intervalId);
+ }
+ };
+ }, [intervalToggle, intervalTime, handleScan]);
+
+ // Способ отображения
+ const viewOptions: Option[] = [
+ { value: 1, label: t('userScanner-visible_table') },
+ { value: 2, label: t('userScanner-visible_list') },
+ ];
+ const [selectedView, setSelectedView] = useState(viewOptions[0]);
+ const handleViewChange = (option: Option) => {
+ setSelectedView(option);
+ };
+
+ return (
+
+
+
+ {(!scanInitiated || loading) && ({t('userScanner-title')} )}
+
+
+
+
+
+
+
+
+
+
+ {scanInitiated && !loading && (
+
+ )}
+
+ {loading ? (
+
+ {t('userScanner-loading')}
+
+ ) : selectedView.value == 1 ? (
+ handleScan(false)} devices={devices} scanInitiated={scanInitiated} />
+ ):(
+ handleScan(false)} devices={devices} scanInitiated={scanInitiated} />
+ )}
+
+ );
+
+}
+
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx
new file mode 100644
index 0000000..d4b23be
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/WifiScanner.tsx
@@ -0,0 +1,209 @@
+import styled, { keyframes } from "styled-components";
+import React, { useState } from "react";
+import { NetworkService } from "../../../../../api/request";
+import WifiDot, {WifiNetworkScanner} from "./modules/WifiDot";
+import ModalWifiScaner from "./modules/ModalWifiScaner";
+import useNetworkStore from "../../../../../store/useDeviceStore";
+import {useIpStore} from "../../../../../store";
+import {useTranslation} from "../../../../../utils/i18n/hooks";
+
+// Анимации
+const ring1 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
+`;
+const ring2 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
+`;
+const ring3 = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
+ 50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
+`;
+const dotAnimation = keyframes`
+ 0% { content: '.'; }
+ 33% { content: '..'; }
+ 66% { content: '...'; }
+ 100% { content: '.'; }
+`;
+const pulse = keyframes`
+ 0% { transform: translate(-50%, -50%) scale(1); }
+ 50% { transform: translate(-50%, -50%) scale(1.1); }
+ 100% { transform: translate(-50%, -50%) scale(1); }
+`;
+
+// Стили
+const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content:center ;
+ height: 100%;
+ width: 100%;
+ position: relative;
+ background: ${(props) => props.theme.background};
+ padding: 50px;
+ box-sizing: border-box;
+ gap: 24px;
+ transition: justify-content .4s ease;
+ overflow-y: auto;
+ overflow-x: hidden;
+`;
+const TitleWrapper = styled.h2`
+ position: absolute;
+ top: 24px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 32px;
+ text-align: center;
+`
+
+const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ position: relative;
+ transition: .4s ease;
+ transform: scale(1);
+ .circel {
+ position: absolute;
+ display: inline-block;
+ background: ${(props) => props.theme.scannerColor};
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ &.center {
+ width: 60px;
+ height: 60px;
+ animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
+ }
+ &.ring1 {
+ opacity: 0.12;
+ width: 250px;
+ height: 250px;
+ animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
+ }
+
+ &.ring2 {
+ opacity: 0.08;
+ width: 400px;
+ height: 400px;
+ animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
+ animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
+ }
+
+ &.ring3 {
+ opacity: 0.04;
+ width: 550px;
+ height: 550px;
+ animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
+ animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
+ }
+ }
+`;
+
+
+const LoadingText = styled.div`
+ position: relative;
+ padding: 14px 22px;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 18px;
+`;
+const ScanningText = styled.span`
+ &::after {
+ content: '.';
+ animation: ${dotAnimation} 1.5s infinite;
+ }
+`;
+const ScanBtn = styled.button<{scanInitiated: boolean}>`
+ position: relative;
+ padding: 14px 22px;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 12px;
+ font-size: 18px;
+ transition: transform .4s ease;
+ transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")};
+ &:hover{
+ transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")} scale(1.03);
+ }
+`
+
+export function WifiScanner() {
+ const { ipAddress } = useIpStore();
+ //const ipAddress = '192.168.1.148'
+ const [loading, setLoading] = useState(false);
+ const [scanInitiated, setScanInitiated] = useState(false);
+ const { wifiNetworks, setWifiNetworks } = useNetworkStore();
+ const [currentNetwork, setCurrentNetwork] = useState(null);
+ const { t } = useTranslation('common');
+
+ const handleScan = async () => {
+ setWifiNetworks([]);
+ setCurrentNetwork(null);
+ setLoading(true);
+ setScanInitiated(true);
+ try {
+ const res = await NetworkService.scanWifi(ipAddress);
+ const networks = res.data.access_points || [];
+ setWifiNetworks(networks);
+ } catch (err) {
+ console.error(t('wifiScanner-scanError'), err);
+ setWifiNetworks([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ {t('wifiScanner-title')}
+
+
+
+
+
+
+ {wifiNetworks.map((network, index) => (
+
+ ))}
+
+ {currentNetwork && (
+ setCurrentNetwork(null)}
+ network={currentNetwork}
+ />
+ )}
+
+
+ {loading ? (
+
+ {t('wifiScanner-scanning')}
+
+ ) : (
+ 0}
+ >
+ {wifiNetworks.length > 0 ? t('wifiScanner-refresh') : t('wifiScanner-scan')}
+
+ )}
+
+ );
+}
+
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/ModalWifiScaner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/ModalWifiScaner.tsx
new file mode 100644
index 0000000..4fcc68d
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/ModalWifiScaner.tsx
@@ -0,0 +1,228 @@
+import React, {useEffect, useState} from 'react';
+import styled from 'styled-components';
+import {WifiNetworkScanner} from "./WifiDot";
+import {IconComponent} from "../../../../../../const/buttons/icons";
+import {buttonNames} from "../../../../../../const/buttons/names";
+import {ModalMap} from "../../../../../modalwindows/windowsType";
+import {useIpStore, useModalContext} from "../../../../../../store";
+import {Status} from "../../../../../customeNodes/nodes/networkInterface/networkNode/style";
+import SignalSVG from "../../../../../UI/signalSvg";
+import { useTheme } from 'styled-components';
+import useNetworkStore, {DeviceUser} from "../../../../../../store/useDeviceStore";
+import {NetworkService} from "../../../../../../api/request";
+import {useTranslation} from "../../../../../../utils/i18n/hooks";
+
+const Modal = styled.div`
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ background-color: ${(props) => props.theme.secondary};
+ border-radius: 24px;
+ padding: 32px 28px;
+ bottom: -40%;
+ left: 220px;
+ position: absolute;
+ z-index: 20;
+ min-width: 300px;
+ &:hover .close-btn{
+ opacity: 1;
+ visibility: visible;
+ }
+`
+const CloseBtn = styled.div`
+ position: absolute;
+ top: -20px;
+ right: -20px;
+ cursor: pointer;
+ background-color: ${(props) => props.theme.primary};
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 40px;
+ height: 40px;
+ transition: all .4s ease;
+ opacity: 0;
+ visibility: visible;
+
+ &:hover {
+ background-color: ${(props) => props.theme.hoverBtn};
+ }
+`
+const ModalTitle = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 20px;
+ line-height: 1;
+ margin-bottom: 32px;
+ h4{
+ font-weight: normal;
+ }
+ .functionalIcons{
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 18px;
+ }
+`
+const ModalContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+`
+const ModalRow = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: start;
+ gap: 12px;
+ width: 100%;
+ font-size: 14px;
+ h6{
+ margin: 0;
+ padding: 0;
+ font-size: 1em;
+ }
+ p{
+ max-width:180px;
+ text-align: right;
+ }
+`
+const ModalConnect = styled.button`
+ color: ${(props) => props.theme.primary};
+ padding: 10px 12px;
+ font-size: 18px;
+ background-color: ${(props) => props.theme.accent};
+ border-radius: 12px;
+ margin-top: 4px;
+ border: none;
+ outline: none;
+ width: 100%;
+ cursor: pointer;
+ transition: .4s ease;
+ &:hover{
+ background-color: ${(props) => props.theme.accentHover};
+ }
+`
+
+const ModalWifiScaner = ({ network,onClose }: { network: WifiNetworkScanner, onClose: ()=>void }) => {
+ const { openModal } = useModalContext();
+ const { ipAddress } = useIpStore();
+ const theme = useTheme()
+ const [signal, setSignal] = useState(1);
+ const { wifiNetworks } = useNetworkStore();
+ const [isConnected, setIsConnected] = useState(false);
+ const { t } = useTranslation('common');
+
+ useEffect(() => {
+ const getStatus = async () => {
+ try {
+ const response = await NetworkService.statusNetwork(ipAddress);
+ const { status, ssid } = response.data;
+ setIsConnected(
+ !!network?.ssid &&
+ ssid === network.ssid &&
+ status === 'connected'
+ );
+ } catch (error) {
+ setIsConnected(false);
+ }
+ };
+
+ getStatus();
+ }, [network?.ssid, ipAddress]);
+
+ const currentNetwork = wifiNetworks.find(n => n.bssid === network.bssid) || network;
+ const openModalNote = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ openModal(ModalMap['7'], {
+ address: currentNetwork.ssid,
+ currentNote: currentNetwork.note || ''
+ });
+ };
+
+ const openModalNetwork= (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if(isConnected){
+ openModal(ModalMap['9'], {
+ address: currentNetwork.ssid,
+ });
+ }else{
+ openModal(ModalMap['8'], {
+ address: currentNetwork.ssid,
+ });
+ }
+ };
+
+ useEffect(() => {
+ const perc = Number(network.signal);
+
+ switch (true) {
+ case perc >= -50: setSignal(5); break;
+ case perc >= -60: setSignal(4); break;
+ case perc >= -70: setSignal(3); break;
+ case perc >= -80: setSignal(2); break;
+ default: setSignal(1); break;
+ }
+ }, [network.signal])
+ return (
+
+
+
+
+
+ {network.ssid}
+
+
+
+
+
+
+
+
+
+
+
+ {t('wifiModal-bssid')}:
+ {network.bssid}
+
+
+ {t('wifiModal-vendor')}:
+ {network.vendor || t('wifiModal-unknown')}
+
+
+ {t('wifiModal-signal')}:
+ {network.signal} dBm
+
+
+ {t('wifiModal-channel')}:
+ {network.channel}
+
+
+ {t('wifiModal-encryption')}:
+ {network.encryption || t('wifiModal-none')}
+
+
+ {t('wifiModal-frequency')}:
+ {network.frequency} MHz
+
+
+ {t('wifiModal-hidden')}:
+ {network.hidden ? t('common-yes') : t('common-no')}
+
+
+ {t('wifiModal-note')}:
+ {currentNetwork.note || '-'}
+
+
+ {isConnected ? t('wifiModal-disconnect') : t('wifiModal-connect')}
+
+
+
+ );
+};
+
+export default ModalWifiScaner;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx
new file mode 100644
index 0000000..54fee2b
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/appModules/wifiScanner/modules/WifiDot.tsx
@@ -0,0 +1,81 @@
+import React from 'react';
+import styled from "styled-components";
+
+export interface WifiNetworkScanner {
+ ssid: string;
+ bssid: string;
+ channel: number;
+ signal: number;
+ encryption: string;
+ frequency: string;
+ vendor: string;
+ hidden: boolean;
+ note: string;
+}
+
+//Точка вайфай
+const Dot = styled.div<{ top: string; left: string }>`
+ position: absolute;
+ top: ${props => props.top};
+ left: ${props => props.left};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 18px;
+ transform: translate(-50%, -50%);
+ user-select: none;
+ transition: .4s;
+ text-wrap: nowrap;
+ &:hover {
+ transform: translate(-50%, -50%) scale(1.1);
+ }
+`;
+
+const getPositionInRing = (index: number, total: number, ringSize: number) => {
+ const angle = (index * (2 * Math.PI / total)) + (Math.PI / total * (index % 2));
+ const innerRadius = ringSize * 0.4;
+ const outerRadius = ringSize * 0.5;
+ const radius = innerRadius + (outerRadius - innerRadius) * (0.3 + 0.7 * (index % 10) / 10);
+
+ return {
+ x: Math.cos(angle) * radius,
+ y: Math.sin(angle) * radius
+ };
+};
+
+const WifiDot = ({ network, index, total, onSelect }: {
+ network: WifiNetworkScanner,
+ index: number,
+ total: number,
+ onSelect: (n: WifiNetworkScanner) => void
+}) => {
+ const getPosition = (signal: number, index: number, total: number) => {
+ let strength: 1 | 2 | 3;
+ if (signal >= -50) {
+ strength = 3;
+ } else if (signal >= -70) {
+ strength = 2;
+ } else {
+ strength = 1;
+ }
+
+ const ringSizes = { 1: 550, 2: 400, 3: 250 };
+ const ringSize = ringSizes[strength];
+ const offset = getPositionInRing(index, total, ringSize);
+
+ return {
+ top: `calc(50% + ${offset.y}px)`,
+ left: `calc(50% + ${offset.x}px)`
+ };
+ };
+
+ const position = getPosition(network.signal, index, total);
+
+ return (
+ onSelect(network)}>
+ {network.ssid}
+
+ );
+};
+
+export default WifiDot;
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/modules/workModules/networkInterfaces.tsx b/FRONT/DEV/src/components/workSpace/modules/workModules/networkInterfaces.tsx
new file mode 100644
index 0000000..d0325ef
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/modules/workModules/networkInterfaces.tsx
@@ -0,0 +1,282 @@
+import { useEffect, useState, useCallback, useRef } from 'react';
+import { useTheme } from 'styled-components';
+import { usePosStore } from '../../../../store/posStore';
+
+import { ReactFlow, useNodesState, useEdgesState, Background, addEdge, applyEdgeChanges, Controls } from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import '../../../../assets/css/flow.css'
+
+import { nodeTypes } from '../../../customeNodes/types/types';
+import { ModalMap } from '../../../modalwindows/windowsType';
+
+import {
+ useGroupStore,
+ useModalContext,
+ usePannableStore
+} from '../../../../store'
+
+import {
+ autosavePosition,
+ resetPosition,
+ processNodeChanges,
+ initNetworkIntData,
+ groupSort,
+ collapseGroup,
+ updateWifiData,
+ useInitCanvas
+} from '../../../../utils/nodeController';
+
+import {
+ ResponseData,
+ ResponseNetwork,
+ IEdge,
+ Interfaces,
+} from '../../../../const/types/types';
+
+type Props = {
+ startConnection: boolean
+ ws: string
+}
+
+const proOptions = { hideAttribution: true, };
+
+export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
+
+ const initializationAttempt = useRef(0);
+ const [requestError, setRequestError] = useState({ status: false, message: '' })
+
+ const { openModal, closeLoader } = useModalContext();
+ const [loader, setLoader] = useState(true)
+ const theme = useTheme();
+
+ const [socket, setSocket] = useState(null);
+ const [data, setData] = useState([]);
+ const [wifiData, setWifi] = useState()
+ const [hasInited, setHasInited] = useState(false);
+ const { initedNodes, initedEdges, handleInit } = useInitCanvas();
+
+ const [interfaces, setInterfaces] = useState([]);
+ const [edgeData, setEdgeData] = useState([]);
+
+ const [nodes, setNodes, onNodesChange] = useNodesState([] as any[]);
+ const [edges, setEdges] = useEdgesState([] as any[]);
+ const { isPanDisabled } = usePannableStore()
+
+ const [changeArray, setChangeArray] = useState([]);
+ const { groups } = useGroupStore();
+ const {isNullPos, setNullPos} = usePosStore()
+
+ useEffect(() => {
+ if (startConnection) {
+
+ openModal(ModalMap['2'], 'Идет загрузка данных');
+
+ setNodes([])
+ setEdges([])
+ setHasInited(false)
+ initializationAttempt.current = 0;
+
+ const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
+
+ let connectionTimeout;
+ setSocket(newSocket);
+
+ connectionTimeout = setTimeout(() => {
+ if (newSocket.readyState !== WebSocket.OPEN) {
+ newSocket.close();
+ }
+ }, 5000);
+
+ newSocket.onopen = () => {
+ console.log('WebSocket connection established');
+ };
+
+ newSocket.onmessage = (event) => {
+ const newData = JSON.parse(event.data);
+
+ setData(() => [
+ newData.activation,
+ newData.deactivation,
+ ]);
+ };
+
+ newSocket.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ };
+
+ newSocket.onclose = () => {
+ console.log('WebSocket connection closed');
+ setRequestError({
+ status: true,
+ message: 'Соединение с сервером разорвано'
+ })
+ };
+
+ return () => {
+ newSocket.close();
+ closeLoader()
+ };
+ }
+ }, [startConnection, ws]);
+
+
+ useEffect(() => {
+ if (data.length > 0) {
+ const { interfaces: newInterfaces, edgeData: newEdgeData } = initNetworkIntData(data);
+ const activationData = data[0].interfaces;
+
+ const wifi = activationData.find((el: any) => el.type === 'WIFI');
+
+ setInterfaces(newInterfaces);
+ setEdgeData(newEdgeData);
+ if (wifi) { setWifi(wifi); }
+ }
+ }, [data]);
+
+ useEffect(() => {
+ if (interfaces.length > 0 && !hasInited) {
+ handleInit(edgeData, interfaces)
+ setHasInited(true)
+ }
+ }, [interfaces, edgeData, hasInited])
+
+ useEffect(() => {
+ if (initializationAttempt.current === 0 && !requestError.status) {
+ const checkInterval = setInterval(() => {
+
+ if(initedNodes.length> 0 && initedNodes.length<=4){
+ window.location.reload()
+ }
+
+ if (initedNodes.length > 0 && initedEdges.length > 0) {
+ setNodes(initedNodes);
+ setEdges(initedEdges);
+ console.log("Ноды и эджи успешно инициализированы");
+ initializationAttempt.current = 1;
+ clearInterval(checkInterval);
+ clearTimeout(timeoutId);
+ } else {
+ console.log("Попытка инициализации нод и эджей...");
+ }
+ }, 1000);
+
+ const timeoutId = setTimeout(() => {
+ console.error("Ошибка: слишком долгая загрузка данных");
+ clearInterval(checkInterval);
+ setRequestError({
+ status: true,
+ message: 'Превышено время ожидания'
+ })
+ }, 60000);
+
+ return () => {
+ clearInterval(checkInterval);
+ clearTimeout(timeoutId);
+ };
+ }
+ }, [initedNodes, initedEdges, requestError]);
+
+ useEffect(() => {
+ if (nodes.length === 0) {
+ initializationAttempt.current = 0;
+ } else {
+ initializationAttempt.current = 1;
+ }
+ }, [nodes]);
+
+ useEffect(() => {
+ if ((nodes.length > 0 && loader == true)) {
+ setLoader(false)
+ closeLoader()
+ }
+ }, [nodes, loader])
+
+ const onEdgesChange = useCallback(
+ (changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
+ [setEdges],
+ );
+
+ const onConnect = useCallback(
+ (connection: any) => setEdges((eds) => addEdge(connection, eds)),
+ [setEdges],
+ );
+
+ const handleNodesChange = (changes: any) => {
+ onNodesChange(changes);
+
+ const newData = processNodeChanges(changes, data);
+
+ if (newData) {
+ setChangeArray((prevArray) => [...prevArray, newData]);
+ }
+ };
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ if (socket && socket.readyState === WebSocket.OPEN) {
+ autosavePosition(changeArray, socket);
+ }
+ setChangeArray([]);
+ }, 1000);
+
+ return () => {
+ clearInterval(interval);
+ };
+ }, [changeArray, socket]);
+
+ useEffect(() => {
+ const sortedGroups = groupSort(groups, nodes)
+ setNodes(collapseGroup(sortedGroups, nodes));
+ }, [groups]);
+
+ useEffect(() => {
+ if (wifiData) {
+ setNodes(updateWifiData(wifiData, nodes));
+ }
+ }, [wifiData])
+
+ useEffect(() => {
+ if (requestError.status) {
+ setTimeout(() => {
+ openModal(ModalMap['3'], {
+ message: requestError.message,
+ });
+ }, 1000);
+ }
+ }, [requestError]);
+
+ useEffect(() => {
+ if(isNullPos){
+ if(socket && socket.readyState === WebSocket.OPEN){
+ resetPosition(socket, setNullPos, nodes, setNodes)
+ }
+ }
+ }, [isNullPos]);
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
diff --git a/FRONT/DEV/src/components/workSpace/sideBar/sideBar.tsx b/FRONT/DEV/src/components/workSpace/sideBar/sideBar.tsx
new file mode 100644
index 0000000..256f62d
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/sideBar/sideBar.tsx
@@ -0,0 +1,17 @@
+import { ToolBar } from "./toolBar/toolBar"
+import { ToolSpace } from "./toolSpace/toolSpace"
+import styled from "styled-components"
+
+const Container = styled.div`
+ height: 100%;
+ display: flex;
+`
+
+export const SideBar = ()=>{
+ return(
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx b/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx
new file mode 100644
index 0000000..0b3b2cc
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx
@@ -0,0 +1,32 @@
+import styled from "styled-components"
+import { IconComponent as Icon } from "../../../../const/menus/sideBar/icons"
+import { useToolBar } from "../../../../hooks/useToolBar"
+import { useTheme } from "styled-components"
+
+type Props = {
+ menuId: string
+}
+
+const Button = styled.div`
+ width: 100%;
+ cursor: pointer;
+`
+
+const IconContainer = styled.div`
+ padding: 10px;
+`
+
+export const ToolBarButton = ({ menuId }: Props) => {
+ const { isActive, collapseMenu } = useToolBar(menuId);
+ const theme = useTheme();
+
+ const iconColor = isActive ? theme.textColor : theme.active;
+
+ return (
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar.tsx b/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar.tsx
new file mode 100644
index 0000000..f6d5825
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/sideBar/toolBar/toolBar.tsx
@@ -0,0 +1,92 @@
+import { useToolBarStore } from "../../../../store"
+import { ToolBarButton } from "./toolBar-button"
+import styled from "styled-components"
+import ThemeButton from "../../../UI/buttons/toggletheme"
+import { IconComponent } from "../../../../const/buttons/icons"
+import { buttonNames } from "../../../../const/buttons/names"
+import { workSpaceNames } from "../../../../const/workspace/names"
+
+import { useModalContext } from "../../../../store"
+import { useAdminStore } from "../../../../store/adminStore"
+import { ModalMap } from "../../../modalwindows/windowsType"
+import { useWorkSpaceStore } from "../../../../store"
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ background-color: ${(props) => props.theme.primary};
+ padding: 5px 0;
+`
+
+const ButtonContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`
+
+const Button = styled.div`
+ width: 100%;
+ cursor: pointer;
+`
+
+const IconContainer = styled.div`
+ padding: 10px;
+ color: ${props => props.theme.text};
+`
+
+export const ToolBar = () => {
+ const { toolBar } = useToolBarStore()
+ const toolBarMenu = toolBar?.toolSpace
+
+ const { openModal } = useModalContext()
+ const { isAdmin } = useAdminStore()
+ const { setWorkModule, workModule } = useWorkSpaceStore()
+
+ return (
+
+
+ {toolBarMenu && toolBarMenu.map((menu) => (
+
+ ))}
+
+
+ setWorkModule(workSpaceNames[5])} />
+
+
+
+
+ setWorkModule(workSpaceNames[6])} />
+
+
+
+
+
+
+
+ openModal(ModalMap['5'])} />
+
+
+
+ {/* */}
+
+
+
+ workModule === workSpaceNames[3]
+ ? setWorkModule(workSpaceNames[2])
+ : setWorkModule(workSpaceNames[4])
+ }
+ />
+
+
+
+
+ openModal(ModalMap['10'])} />
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx b/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx
new file mode 100644
index 0000000..5a43c44
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx
@@ -0,0 +1,247 @@
+import styled from "styled-components"
+import { useState, useEffect } from "react"
+import { useModalContext } from "../../../../store"
+import { ModalMap } from "../../../modalwindows/windowsType"
+import { NetworkService } from "../../../../api/request"
+import { AnimDot } from "../../../UI/animDot"
+import { useMenu } from "../../../../hooks/useMenu"
+import { useIpStore, useDevicesStore } from "../../../../store"
+import { menusNames } from "../../../../const/menus/othersMenus/names"
+import { useWorkSpaceStore } from "../../../../store"
+import { workSpaceNames } from "../../../../const/workspace/names"
+import { IconComponent } from "../../../../const/buttons/icons"
+import { buttonNames } from "../../../../const/buttons/names"
+import {useTranslation} from "../../../../utils/i18n/hooks";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+ padding: 20px;
+ width: 210px;
+ p{
+ margin: 0;
+ }
+`
+
+const Title = styled.div`
+ padding: 5px 0;
+ width: 100%;
+ text-align: center;
+ border-bottom: 1px solid ${props => props.theme.text};
+`
+
+const DeviceBlock = styled.div`
+ border-radius: 8px;
+ border: 2px solid ${props => props.theme.accent};
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+
+ .paddingP{
+ padding: 7px 5px;
+ }
+`
+
+const DeviceControl = styled.div`
+ padding: 5px 8px;
+ background: ${props => props.theme.accent};
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: white;
+ cursor: pointer;
+`
+
+const PlusBlock = styled.div`
+ font-size: 24px;
+ cursor: pointer;
+ border-radius: 8px;
+ border: 2px dotted ${props => props.theme.accent};
+ width: 100%;
+ text-align: center;
+ padding: 5px 0;
+`
+
+const Text = styled.div`
+ display: flex;
+ gap: 3px;
+ align-items: center;
+`
+
+const DevicesContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+`
+
+const DevicesHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+
+ .hControl{
+ font-size: 20px;
+ cursor: pointer;
+ }
+`
+
+const DevicesContent = styled.div<{ $isactive: string }>`
+ ${props => props.$isactive === 'true' && `display: flex;`}
+ ${props => props.$isactive === 'false' && `display: none;`}
+ flex-direction: column;
+ align-items: center;
+ gap: 15px;
+`
+
+interface CurrentDevice {
+ hostname: string
+ address: string
+}
+
+export const DevicesBar = () => {
+ const {openModal} = useModalContext();
+ const {setIpAddress, ipAddress} = useIpStore();
+ const {devices, addDevice, deleteDevice} = useDevicesStore()
+ const {isOpen, collapseMenu} = useMenu(menusNames[1]);
+ const {setWorkModule} = useWorkSpaceStore()
+ const {t} = useTranslation('common');
+
+ const [currentDevice, setCurrentDevice] = useState({
+ address: 'localhost',
+ hostname: ''
+ })
+
+ const checkWebSocketAvailability = async (ip: string): Promise => {
+ return new Promise((resolve) => {
+ const testSocket = new WebSocket(`ws://${ip}:8001/api/v1`);
+ const timeout = setTimeout(() => {
+ testSocket.close();
+ resolve(false);
+ }, 500
+ );
+
+ testSocket.onopen = () => {
+ clearTimeout(timeout);
+ testSocket.close();
+ resolve(true);
+ };
+
+ testSocket.onerror = () => {
+ clearTimeout(timeout);
+ testSocket.close();
+ resolve(false);
+ };
+ });
+ };
+
+ useEffect(() => {
+ const fetchHostame = async () => {
+ let response = await NetworkService.getHostname();
+ if (response.status === 200) {
+ setCurrentDevice(prevData => ({
+ ...prevData,
+ hostname: response.data.hostname
+ }))
+ }
+ };
+ fetchHostame();
+ }, [])
+
+ const handleAdd = () => {
+ openModal(ModalMap['4'], {
+ onSave: handleAddDeviceComplete,
+ });
+ };
+
+ const handleAddDeviceComplete = (data: string) => {
+ addDevice(data);
+ switchWs(data);
+ };
+
+ const handleDelete = (number: number, adress: string) => {
+ deleteDevice(number)
+
+
+ if (adress == ipAddress) {
+ setIpAddress('localhost')
+ }
+ };
+
+ const switchWs = async (ip: string) => {
+ const isAvailable = await checkWebSocketAvailability(ip);
+ if (!isAvailable) {
+ openModal(ModalMap['3'], {
+ message: t('nodes-connectionError') + ip,
+ });
+ return;
+ }
+
+ setIpAddress(ip);
+ setWorkModule(workSpaceNames[3]);
+ };
+
+ useEffect(() => {
+ if (devices.length === 0) {
+ setIpAddress('localhost');
+ }
+ }, [devices]);
+
+ return (
+
+
+ {t('nodes-availableNodes')}
+
+
+
+ switchWs(currentDevice.address)}>
+ {t('nodes-rootAddress')}
+
+ {currentDevice.hostname == '' ? (
+
+ {t('nodes-name')}: {t('nodes-loading')}
+
+ ) : (
+ {t('nodes-name')}: {currentDevice.hostname}
+ )}
+
+
+
+
+ {t('nodes-additionalNodes')}
+
+
+
+
+ {devices.length > 0 &&
+ devices.map((device, index) => (
+
+ switchWs(device.address)}>
+ {t('nodes-node')} {device.number}
+ {
+ e.stopPropagation();
+ handleDelete(device.number, device.address);
+ }}
+ />
+
+ {t('nodes-address')}: {device.address}
+
+ ))
+ }
+
+
+ +
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx b/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx
new file mode 100644
index 0000000..91a7091
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx
@@ -0,0 +1,31 @@
+import { useToolBarStore } from "../../../../store"
+import { useToolBar } from "../../../../hooks/useToolBar"
+import { Space } from "../../../../const/menus/sideBar/components"
+import styled from "styled-components"
+
+const Container = styled.div<{isOpen: boolean}>`
+ background-color: ${(props) => props.theme.secondary};
+ width: 250px;
+ font-size: 14px;
+ flex: 3;
+
+ overflow: hidden;
+ max-width: ${({isOpen})=>(isOpen ? '250px' : '0')};
+ transition: max-width .4s ease;
+`
+
+export const ToolSpace = () => {
+ const { toolBar } = useToolBarStore()
+ const name = toolBar?.current || ''
+ const { isActive, isOpen } = useToolBar(name)
+
+ if (!isActive) {
+ return null
+ }
+
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/components/workSpace/workSpace.tsx b/FRONT/DEV/src/components/workSpace/workSpace.tsx
new file mode 100644
index 0000000..5e97ebe
--- /dev/null
+++ b/FRONT/DEV/src/components/workSpace/workSpace.tsx
@@ -0,0 +1,101 @@
+import { useEffect, useState } from "react"
+import { HelloWindow } from "./modules/appModules/helloWindow";
+import { NetworkInterfaces } from "./modules/workModules/networkInterfaces";
+import styled from "styled-components"
+import { useIpStore, useToolBarStore } from "../../store";
+import { SideBar } from "./sideBar/sideBar";
+import { useWorkSpaceStore } from "../../store";
+import { workSpaceNames } from "../../const/workspace/names";
+import { Header } from "./modules/appModules/header";
+import { ReactFlowProvider } from "@xyflow/react";
+import About from "./modules/appModules/about";
+import UserScanner from "./modules/appModules/usersScanner/userScanner";
+import {WifiScanner} from "./modules/appModules/wifiScanner/WifiScanner";
+
+const Section = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+`
+
+const FlowContainer = styled.div`
+ display: flex;
+ height: 100%;
+`
+
+const WorkSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ position: relative;
+`
+
+export default function WorkSpace() {
+ const { ipAddress, setIpAddress } = useIpStore();
+ const { workModule } = useWorkSpaceStore()
+ const [currentWs, setCurrentWs] = useState(ipAddress);
+ const initMenus = useToolBarStore((state) => state.initialize);
+ const initModule = useWorkSpaceStore((state) => state.initialize)
+
+ useEffect(() => {
+ if (!localStorage.getItem('toolBar-storage')) {
+ initMenus();
+ }
+ }, [initMenus]);
+
+ useEffect(() => {
+ if (!localStorage.getItem('workSpace-storage')) {
+ initModule();
+ }
+ }, [initModule]);
+
+ useEffect(() => {
+ setCurrentWs(ipAddress);
+ }, [ipAddress]);
+
+ useEffect(() => {
+ setIpAddress(currentWs);
+ }, [currentWs]);
+
+ localStorage.getItem('workSpace-storage')
+
+ return (
+
+
+
+
+ {workModule === workSpaceNames[1] && }
+
+ {workModule === workSpaceNames[2] && (
+
+ )}
+
+ {workModule === workSpaceNames[4] && (
+
+ )}
+ {workModule === workSpaceNames[5] && (
+
+ )}
+ {workModule === workSpaceNames[6] && (
+
+ )}
+ {workModule === workSpaceNames[3] &&
+
+
+
+
+
+
+ }
+
+
+ )
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/buttons/icons.tsx b/FRONT/DEV/src/const/buttons/icons.tsx
new file mode 100644
index 0000000..12bf8d5
--- /dev/null
+++ b/FRONT/DEV/src/const/buttons/icons.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { buttonNames } from "./names";
+import { useTheme } from "styled-components";
+import { Icon } from '@iconify/react';
+
+import exitToApp from '@iconify/icons-ic/twotone-exit-to-app';
+import deleteIcon from '@iconify/icons-ic/baseline-delete';
+import settings from '@iconify/icons-ic/baseline-settings';
+import info from '@iconify/icons-ic/baseline-info';
+import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down';
+import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right';
+import arrowLeft from '@iconify/icons-ic/baseline-keyboard-arrow-left';
+import edit from '@iconify/icons-ic/baseline-edit';
+import checkCircle from '@iconify/icons-ic/round-check-circle';
+import cancel from '@iconify/icons-ic/baseline-cancel';
+import userSearch from '@iconify/icons-ic/baseline-person-search';
+import admin from '@iconify/icons-ic/baseline-admin-panel-settings';
+import noAdmin from '@iconify/icons-ic/baseline-remove-moderator'
+import refresh from '@iconify/icons-ic/baseline-refresh';
+import close from '@iconify/icons-ic/round-close';
+import wifiFind from '@iconify/icons-ic/baseline-wifi-find';
+import noteEdit from '@iconify/icons-ph/note-pencil';
+import swapHoriz from '@iconify/icons-ic/baseline-swap-horiz';
+import check from '@iconify/icons-ic/baseline-check';
+
+export const ICONS = {
+ [buttonNames[1]]: exitToApp,
+ [buttonNames[2]]: deleteIcon,
+ [buttonNames[3]]: settings,
+ [buttonNames[4]]: info,
+ [buttonNames[5]]: arrowDown,
+ [buttonNames[6]]: arrowRight,
+ [buttonNames[7]]: edit,
+ [buttonNames[8]]: checkCircle,
+ [buttonNames[9]]: cancel,
+ [buttonNames[10]]: admin,
+ [buttonNames[11]]: refresh,
+ [buttonNames[12]]: close,
+ [buttonNames[13]]: noAdmin,
+ [buttonNames[14]]: userSearch,
+ [buttonNames[15]]: wifiFind,
+ [buttonNames[16]]: noteEdit,
+ [buttonNames[17]]: arrowLeft,
+ [buttonNames[18]]: swapHoriz,
+ [buttonNames[19]]: check,
+} as const;
+
+type IconName = keyof typeof ICONS;
+
+interface IconsProps {
+ name: IconName,
+ width?: string
+ height?: string
+ setColor?: string
+ onClick?: (event?: React.MouseEvent) => void;
+}
+
+export const IconComponent = ({ name, width = "24", height = "24", setColor, onClick}: IconsProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/buttons/names.ts b/FRONT/DEV/src/const/buttons/names.ts
new file mode 100644
index 0000000..b6c7505
--- /dev/null
+++ b/FRONT/DEV/src/const/buttons/names.ts
@@ -0,0 +1,23 @@
+import { names } from "../types/types"
+
+export const buttonNames: names ={
+ 1: 'powerOff',
+ 2: 'delete',
+ 3: 'settings',
+ 4: 'information',
+ 5: 'arrow-down',
+ 6: 'arrow-right',
+ 7: 'edit',
+ 8: 'accept',
+ 9: 'cancel',
+ 10: 'admin',
+ 11: 'reset',
+ 12: 'close',
+ 13: 'noAdmin',
+ 14: 'userSearch',
+ 15: 'wifiFind',
+ 16: 'noteEdit',
+ 17: 'arrowLeft',
+ 18: 'swapHoriz',
+ 19: 'check'
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/othersMenus/data.ts b/FRONT/DEV/src/const/menus/othersMenus/data.ts
new file mode 100644
index 0000000..c5f2713
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/othersMenus/data.ts
@@ -0,0 +1,9 @@
+import { Menu } from "../../types/types";
+import { menusNames } from "./names";
+
+export const menusData: Menu[] = [
+ {
+ id: menusNames[1],
+ isOpen: false
+ }
+]
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/othersMenus/names.ts b/FRONT/DEV/src/const/menus/othersMenus/names.ts
new file mode 100644
index 0000000..1f06e93
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/othersMenus/names.ts
@@ -0,0 +1,5 @@
+import { names } from "../../types/types"
+
+export const menusNames: names ={
+ 1: 'collapse-devices',
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/sideBar/components.tsx b/FRONT/DEV/src/const/menus/sideBar/components.tsx
new file mode 100644
index 0000000..e2bd113
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/sideBar/components.tsx
@@ -0,0 +1,20 @@
+import React from "react"
+import { toolBarNames } from "./names"
+import { DevicesBar } from "../../../components/workSpace/sideBar/toolSpace/devicesBar"
+
+export const COMPONENTS = {
+ [toolBarNames[1]]:(
+
+ ),
+} as const
+
+type ComponentName = keyof typeof COMPONENTS
+
+interface SpaceProps {
+ name: ComponentName,
+}
+
+export const Space = ({name}: SpaceProps) => {
+ const Component = COMPONENTS[name]
+ return React.cloneElement(Component);
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/sideBar/data.ts b/FRONT/DEV/src/const/menus/sideBar/data.ts
new file mode 100644
index 0000000..c2bd739
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/sideBar/data.ts
@@ -0,0 +1,13 @@
+import { ToolBar } from "../../types/types";
+import { toolBarNames } from "./names";
+
+export const toolBarData: ToolBar = {
+ current: toolBarNames[1],
+ toolSpace: [
+ {
+ id: toolBarNames[1],
+ isOpen: true,
+ isActive: true
+ },
+ ]
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/sideBar/icons.tsx b/FRONT/DEV/src/const/menus/sideBar/icons.tsx
new file mode 100644
index 0000000..b5560ce
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/sideBar/icons.tsx
@@ -0,0 +1,32 @@
+import React from "react"
+import { toolBarNames } from "./names"
+import { useTheme } from "styled-components"
+import { Icon } from '@iconify/react';
+
+import desktop from '@iconify/icons-ic/baseline-menu';
+
+export const ICONS = {
+ [toolBarNames[1]]: desktop,
+} as const
+
+
+type IconName = keyof typeof ICONS
+
+interface IconsProps {
+ name: IconName,
+ width?: string
+ height?: string
+ setColor?: string
+}
+
+export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/menus/sideBar/names.ts b/FRONT/DEV/src/const/menus/sideBar/names.ts
new file mode 100644
index 0000000..32d3d9d
--- /dev/null
+++ b/FRONT/DEV/src/const/menus/sideBar/names.ts
@@ -0,0 +1,5 @@
+import { names } from "../../types/types"
+
+export const toolBarNames: names ={
+ 1: 'toolSpace-devices',
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/modals/names.ts b/FRONT/DEV/src/const/modals/names.ts
new file mode 100644
index 0000000..face023
--- /dev/null
+++ b/FRONT/DEV/src/const/modals/names.ts
@@ -0,0 +1,8 @@
+import { names } from "../types/types"
+
+export const modalNames: names ={
+ 1: 'loader',
+ 2: 'ipConfirm',
+ 3: 'connectionError',
+ 4: 'addDevice'
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/theme/globalStyles.ts b/FRONT/DEV/src/const/theme/globalStyles.ts
new file mode 100644
index 0000000..ca6059d
--- /dev/null
+++ b/FRONT/DEV/src/const/theme/globalStyles.ts
@@ -0,0 +1,55 @@
+import { createGlobalStyle } from 'styled-components';
+
+export const GlobalStyle = createGlobalStyle`
+ body {
+ background-color: ${(props) => props.theme.backgroundColor};
+ color: ${(props) => props.theme.textColor};
+ box-sizing: border-box;
+ font-family: sans-serif;
+ box-sizing: border-box;
+ min-height: 100%;
+ height: 100%;
+ margin: 0;
+
+ h1, h2, h3, h4, P{
+ margin: 0;
+ display: unset;
+ }
+ }
+
+ #root{
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ }
+
+ .react-flow__controls{
+ border-radius: 12px;
+ background-color: ${(props) => props.theme.secondary};
+ button{
+ background-color: ${(props) => props.theme.secondary};
+ border: none;
+ border-radius: 12px;
+
+ &:hover{
+ background-color: ${(props) => props.theme.primary};
+ }
+ }
+
+ .react-flow__controls-button{
+ border-radius: 12px;
+ }
+ }
+
+ .react-flow__handle{
+ background-color: ${(props) => props.theme.nodeColor};
+ }
+
+ .unselectable {
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ }
+`;
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/theme/icons.tsx b/FRONT/DEV/src/const/theme/icons.tsx
new file mode 100644
index 0000000..04b689e
--- /dev/null
+++ b/FRONT/DEV/src/const/theme/icons.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { Icon } from '@iconify/react';
+import { useTheme } from 'styled-components';
+import { themeNames } from './names';
+
+import light from '@iconify/icons-ic/baseline-brightness-7';
+import dark from '@iconify/icons-ic/baseline-brightness-5';
+import system from '@iconify/icons-ic/baseline-brightness-6';
+
+export const ICONS = {
+ [themeNames[1]]: light,
+ [themeNames[2]]: dark,
+ [themeNames[3]]: system,
+
+} as const;
+
+type IconName = keyof typeof ICONS;
+
+interface IconsProps {
+ name: IconName,
+ width?: string
+ height?: string
+ setColor?: string
+}
+
+export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => {
+ const theme = useTheme();
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/theme/names.ts b/FRONT/DEV/src/const/theme/names.ts
new file mode 100644
index 0000000..ec3fc5f
--- /dev/null
+++ b/FRONT/DEV/src/const/theme/names.ts
@@ -0,0 +1,10 @@
+import { names } from "../types/types"
+
+export const themeNames: names ={
+ 1: 'light',
+ 2: 'dark',
+ 3: 'system'
+}
+
+const themeType = themeNames[1] || themeNames[2] || themeNames[3]
+export type ThemName = typeof themeType
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/theme/theme.ts b/FRONT/DEV/src/const/theme/theme.ts
new file mode 100644
index 0000000..dbf1cb3
--- /dev/null
+++ b/FRONT/DEV/src/const/theme/theme.ts
@@ -0,0 +1,30 @@
+const baseTheme = {
+ accent: '#007ACC',
+ successColor: "#4CAf50",
+ errorColor: "#F44336",
+ accentHover: '#4A90E2',
+}
+
+export const light = {
+ ...baseTheme,
+ primary: '#F3F3F3',
+ secondary: '#EDEDED',
+ background: '#E8E8E8',
+ textColor: '#000000',
+ borderColor: '#404040',
+ scannerColor: '#007ACC',
+ offlineColor: '#ababab',
+ hoverBtn: '#d6d6d6',
+};
+
+export const dark = {
+ ...baseTheme,
+ primary: '#1E1E1E',
+ secondary: '#252525',
+ background: '#3A3A3A',
+ textColor: '#FFFFFF',
+ borderColor: '#b6b6b6',
+ scannerColor: '#00E5FF',
+ offlineColor: '#656565',
+ hoverBtn: '#4a4a4a',
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/types/types.ts b/FRONT/DEV/src/const/types/types.ts
new file mode 100644
index 0000000..473ffc2
--- /dev/null
+++ b/FRONT/DEV/src/const/types/types.ts
@@ -0,0 +1,213 @@
+export interface IEdge {
+ source: string
+ sourceHandle: string
+ target: string
+ targetHandle: string
+ id: string
+ type: string
+ style?: any
+}
+
+export interface ICanvas {
+ nodes: any[],
+ edges: IEdge[]
+}
+
+export interface IpRoutes {
+ destination: string
+ prefix: number,
+ next_hop: string
+ metric: number
+}
+
+export interface ResponseIpConf {
+ address: string,
+ method: string,
+ gateway: string,
+ dns: string[]
+ routes: IpRoutes[]
+}
+
+export interface ResponseNetIp {
+ configurations: ResponseIpConf[]
+}
+
+export interface ResponseNetwork {
+ name: string,
+ type: string,
+ mac_address: string,
+ device_state: string,
+ active_connection: string,
+ device_mtu: string,
+ strength: string,
+ ipv4: ResponseNetIp
+ ipv6: ResponseNetIp
+}
+
+export interface ResponseData {
+ interfaces: ResponseNetwork[],
+ container_x: number,
+ container_y: number,
+ id: number | string
+}
+
+export interface OutputIpData {
+ ipType: string,
+ ipAdress: string,
+ ipGate: string,
+ ipDns: string[]
+ ipRoutese: IpRoutes[]
+ ipVersion: string
+}
+
+export interface IPosition {
+ x: number;
+ y: number;
+}
+
+export interface IStyle {
+ width: number,
+ height: number,
+ borderType?: string;
+ borderColor?: string;
+ backgroundColor?: string
+}
+
+export interface NetworkData {
+ device: string
+ type: string
+ mac: string
+ mtu: string,
+ state: string,
+ connection: string
+ id?: string,
+ strength?: string
+}
+
+export interface IpData {
+ ipType: string,
+ ipAdress: string,
+ ipGate: string,
+ ipDns: string[]
+ ipRoutese: IpRoutes[]
+ ipVersion: string
+}
+
+
+export interface NetworkNode {
+ id: string;
+ position: IPosition;
+ data: {
+ networkData: NetworkData,
+ style: IStyle,
+ height: any,
+ visible: boolean
+ }
+ type: string;
+ parentId: string,
+ extent: string,
+}
+
+export interface PortNode {
+ id: string
+ position: IPosition
+ data: {
+ id: string
+ label: string
+ port?: string
+ style: IStyle
+ }
+ type: string,
+ parentId?: string,
+ extent?: string,
+}
+
+export interface IpNode {
+ id: string;
+ position: IPosition;
+ parentId: string
+ extent: string
+ draggable: boolean,
+ data: {
+ ipData: IpData;
+ interfaceData:{
+ device: string,
+ connection: string
+ }
+ route: boolean
+ style: IStyle
+ };
+ hidden?: boolean
+ type: string;
+}
+
+export interface NodeGroup {
+ id: string
+ position: IPosition
+ parentId?: string
+ extent?: string
+ draggable?: boolean,
+ data: {
+ id?: string
+ label?: string
+ height?: any
+ style: IStyle
+ },
+ hidden?: boolean
+ type: string
+}
+
+
+export interface OutputIpArray {
+ route: boolean,
+ ipData: OutputIpData[]
+}
+
+export interface MainData {
+ networkData: NetworkData
+ ipArrays: OutputIpArray[]
+}
+
+export interface Interfaces {
+ mainData: MainData[]
+ container_x: number,
+ container_y: number
+}
+
+export interface IData{
+ activation: ResponseData
+ deactivation: ResponseData
+}
+
+export interface changeIp {
+ currentIp: string
+ newIp: string
+ currentGate: string
+ newGate: string
+ currentDns: string[]
+ newDns: string[]
+ device: string
+ connection: string
+ version: string
+ prefix: number
+}
+
+export interface WorkSpace {
+ id: string,
+ isActive: boolean
+}
+
+export interface Menu {
+ id: string,
+ isOpen: boolean,
+ isActive?: boolean
+}
+
+export interface ToolBar{
+ toolSpace: Menu[]
+ current: string
+}
+
+export type names = Record;
+
+export type NetworkInterface = NetworkNode | PortNode | NodeGroup | IpNode
\ No newline at end of file
diff --git a/FRONT/DEV/src/const/workspace/names.ts b/FRONT/DEV/src/const/workspace/names.ts
new file mode 100644
index 0000000..4b67093
--- /dev/null
+++ b/FRONT/DEV/src/const/workspace/names.ts
@@ -0,0 +1,10 @@
+import { names } from "../types/types"
+
+export const workSpaceNames: names ={
+ 1: 'helloWindow',
+ 2: 'aboutFromWork',
+ 3: 'networkInterfaces',
+ 4: 'aboutFromHello',
+ 5: 'userScanner',
+ 6: 'wifiScanner',
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/electron.d.ts b/FRONT/DEV/src/electron.d.ts
new file mode 100644
index 0000000..5fb2869
--- /dev/null
+++ b/FRONT/DEV/src/electron.d.ts
@@ -0,0 +1,9 @@
+interface Window {
+ electron: {
+ quitApp: () => void;
+ };
+ electronAPI: {
+ showSaveDialog: (options: Electron.SaveDialogOptions) => Promise;
+ writeFile: (path: string, data: string) => Promise<{ success: boolean }>;
+ };
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/hooks/useMenu.ts b/FRONT/DEV/src/hooks/useMenu.ts
new file mode 100644
index 0000000..703b7b0
--- /dev/null
+++ b/FRONT/DEV/src/hooks/useMenu.ts
@@ -0,0 +1,15 @@
+import { useMenuStore } from "../store"
+
+export const useMenu = (menuId: string)=>{
+ const { menus, toggleMenu } = useMenuStore();
+ const menu = menus.find((m)=>m.id === menuId)
+ const isOpen = menu ? menu.isOpen : false;
+
+ const collapseMenu = ()=>{
+ if(menu){
+ toggleMenu(menuId)
+ }
+ }
+
+ return { isOpen, collapseMenu };
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/hooks/useSettings.ts b/FRONT/DEV/src/hooks/useSettings.ts
new file mode 100644
index 0000000..aa8aeb8
--- /dev/null
+++ b/FRONT/DEV/src/hooks/useSettings.ts
@@ -0,0 +1,37 @@
+import {useSettingsStore} from "../store/settings/settingsStore";
+import {defaultLanguage, Language} from "../utils/i18n/config";
+
+export const useNameSetting = () => {
+ return useSettingsStore(state => state.getSettingValue('netnames-nodes')) as string;
+};
+
+export const usePaginateSetting = () => {
+ return useSettingsStore(state => state.getNumberSetting('paginate-nodes', 20));
+};
+
+export const useThemeSettings = () => {
+ return useSettingsStore(state => state.getSettingValue('theme'));
+};
+
+//Интервальное сканирование под узлов
+export const useIntervalBoolean = () => {
+ return useSettingsStore(state => state.getSettingValue('rescan-nodes')) as boolean;
+};
+export const useIntervalTime = () => {
+ return useSettingsStore(state => state.getNumberSetting('interval-nodes', 60));
+};
+
+// Кастомизация таблиц
+export const useOrdersColumns = () => {
+ return useSettingsStore(state => state.getSettingValue('table-columns-order')) as string[];
+};
+export const useDisabledColumns = () => {
+ return useSettingsStore(state => state.getSettingValue('table-columns-disabled')) as number[];
+};
+
+
+export const useLanguage = () => {
+ return useSettingsStore(
+ state => state.getSettingValue('language')
+ ) ?? defaultLanguage;
+};
diff --git a/FRONT/DEV/src/hooks/useToolBar.ts b/FRONT/DEV/src/hooks/useToolBar.ts
new file mode 100644
index 0000000..debf65c
--- /dev/null
+++ b/FRONT/DEV/src/hooks/useToolBar.ts
@@ -0,0 +1,16 @@
+import { useToolBarStore } from "../store";
+
+export const useToolBar = (menuId: string) => {
+ const { toolBar, toggleMenu } = useToolBarStore();
+
+ const menu = toolBar.toolSpace.find((m) => m.id === menuId);
+
+ const isOpen = menu ? menu.isOpen : false;
+ const isActive = menu ? menu.isActive : false;
+
+ const collapseMenu = () => {
+ toggleMenu(menuId);
+ };
+
+ return { isOpen, isActive, collapseMenu };
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/router.tsx b/FRONT/DEV/src/router.tsx
new file mode 100644
index 0000000..7f73965
--- /dev/null
+++ b/FRONT/DEV/src/router.tsx
@@ -0,0 +1,43 @@
+import { Routes, Route, Outlet } from 'react-router-dom';
+import WorkSpace from './components/workSpace/workSpace';
+import { useIpStore } from './store';
+import { useEffect, useState } from 'react';
+import { NetworkService } from './api/request';
+import { CreateUser } from './components/register';
+import RouterLoader from './components/UI/routerLoader';
+
+export default function Router() {
+
+ const { ipAddress } = useIpStore();
+ const [configExists, setConfigExists] = useState(null);
+
+ useEffect(() => {
+ const fetchServerConf = async () => {
+ try {
+ let response = await NetworkService.checkServerConf(ipAddress);
+ if(response.status === 200) {
+ setConfigExists(response.data.has_users);
+ }
+ } catch (error) {
+ console.error('Error checking server config:', error);
+ setConfigExists(false);
+ }
+ };
+
+ fetchServerConf();
+ }, [ipAddress]);
+
+ if (configExists === null) {
+ return
+ }
+
+ if (configExists === false) {
+ return ;
+ }
+
+ return (
+
+ } />
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/adminStore.ts b/FRONT/DEV/src/store/adminStore.ts
new file mode 100644
index 0000000..a853372
--- /dev/null
+++ b/FRONT/DEV/src/store/adminStore.ts
@@ -0,0 +1,11 @@
+import { create } from 'zustand'
+
+interface AdminState {
+ isAdmin: boolean;
+ setAdmin: (isAdmin: boolean) => void;
+}
+
+export const useAdminStore = create()((set) => ({
+ isAdmin: false,
+ setAdmin: (isAdmin: boolean) => set({ isAdmin }),
+}))
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/devicesStore.ts b/FRONT/DEV/src/store/devicesStore.ts
new file mode 100644
index 0000000..3e7d143
--- /dev/null
+++ b/FRONT/DEV/src/store/devicesStore.ts
@@ -0,0 +1,39 @@
+import { create } from 'zustand'
+import { persist, createJSONStorage } from 'zustand/middleware'
+
+export interface Device {
+ number: number
+ address: string
+}
+
+interface DevicesState {
+ devices: Device[]
+ addDevice: (address: string) => void
+ deleteDevice: (number: number) => void
+}
+
+export const useDevicesStore = create()(
+ persist(
+ (set) => ({
+ devices: [],
+ addDevice: (address) =>
+ set((state) => {
+ const newDevice = {
+ number: state.devices.length + 1,
+ address
+ }
+ return { devices: [...state.devices, newDevice] }
+ }),
+ deleteDevice: (number) =>
+ set((state) => ({
+ devices: state.devices
+ .filter(device => device.number !== number)
+ .map((device, index) => ({ ...device, number: index + 1 }))
+ }))
+ }),
+ {
+ name: 'connectedDevices-storage',
+ storage: createJSONStorage(() => localStorage)
+ }
+ )
+)
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/flowStore.ts b/FRONT/DEV/src/store/flowStore.ts
new file mode 100644
index 0000000..5e66ece
--- /dev/null
+++ b/FRONT/DEV/src/store/flowStore.ts
@@ -0,0 +1,22 @@
+import { create } from 'zustand';
+
+interface NodesStore {
+ nodes: any[];
+ edges: any[];
+ setNodes: (nodes: any[]) => void;
+ setEdges: (edges: any[]) => void;
+ updateNode: (nodeId: string, newData: any) => void;
+}
+
+export const useNodesStore = create((set) => ({
+ nodes: [],
+ edges: [],
+ setNodes: (nodes) => set({ nodes }),
+ setEdges: (edges) => set({ edges }),
+ updateNode: (nodeId, newData) =>
+ set((state) => ({
+ nodes: state.nodes.map((node) =>
+ node.id === nodeId ? { ...node, data: { ...node.data, ...newData } } : node
+ ),
+ })),
+}));
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/groupStore.ts b/FRONT/DEV/src/store/groupStore.ts
new file mode 100644
index 0000000..e2859b7
--- /dev/null
+++ b/FRONT/DEV/src/store/groupStore.ts
@@ -0,0 +1,13 @@
+import {create} from 'zustand';
+
+interface GroupState {
+ groups: { id: string; click: boolean }[];
+ setGroups: (groups: { id: string; click: boolean }[]) => void;
+}
+
+const useGroupStore = create((set) => ({
+ groups: [],
+ setGroups: (groups) => set({ groups }),
+}));
+
+export { useGroupStore };
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/index.ts b/FRONT/DEV/src/store/index.ts
new file mode 100644
index 0000000..f341a28
--- /dev/null
+++ b/FRONT/DEV/src/store/index.ts
@@ -0,0 +1,9 @@
+export {useModalContext, ModalProvider} from './modalContext'
+export {useTheme, ThemeProvider} from './themeContext'
+export {usePannableStore} from './pannableStore'
+export {useGroupStore} from './groupStore'
+export {useIpStore} from './ipStore'
+export {useToolBarStore} from './toolBarStore'
+export {useDevicesStore} from './devicesStore'
+export {useMenuStore} from './menusStore'
+export {useWorkSpaceStore} from './workSpaceStore'
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/ipStore.ts b/FRONT/DEV/src/store/ipStore.ts
new file mode 100644
index 0000000..e7ed361
--- /dev/null
+++ b/FRONT/DEV/src/store/ipStore.ts
@@ -0,0 +1,20 @@
+import {create} from 'zustand'
+import { persist, createJSONStorage } from 'zustand/middleware'
+
+interface IpAddressState {
+ ipAddress: string;
+ setIpAddress: (newIp: string) => void;
+}
+
+export const useIpStore = create()(
+ persist(
+ (set)=>({
+ ipAddress: '192:168.1.0',
+ setIpAddress: (newIp: string) => set({ ipAddress: newIp }),
+ }),
+ {
+ name: 'currentIp-storage',
+ storage: createJSONStorage(()=>localStorage)
+ }
+ )
+)
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/menusStore.ts b/FRONT/DEV/src/store/menusStore.ts
new file mode 100644
index 0000000..a7cc1ee
--- /dev/null
+++ b/FRONT/DEV/src/store/menusStore.ts
@@ -0,0 +1,28 @@
+
+import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
+import { Menu } from '../const/types/types';
+import { menusData } from '../const/menus/othersMenus/data';
+
+interface MenuState {
+ menus: Menu[];
+ toggleMenu: (menuId: string) => void;
+}
+
+export const useMenuStore = create()(
+ persist(
+ (set) => ({
+ menus: menusData,
+ toggleMenu: (menuId) =>
+ set((state) => ({
+ menus: state.menus.map((menu) =>
+ menu.id === menuId ? { ...menu, isOpen: !menu.isOpen } : menu
+ ),
+ })),
+ }),
+ {
+ name: 'menus-storage',
+ storage: createJSONStorage(() => localStorage)
+ }
+ )
+);
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/modalContext.tsx b/FRONT/DEV/src/store/modalContext.tsx
new file mode 100644
index 0000000..af27986
--- /dev/null
+++ b/FRONT/DEV/src/store/modalContext.tsx
@@ -0,0 +1,63 @@
+import { createContext, useContext, useState, ReactNode, useCallback } from 'react';
+import { useEffect } from 'react';
+
+interface ModalContextType {
+ isOpen: boolean;
+ modalType: string;
+ modalData: any;
+ openModal: (modalType: string, modalData?: any) => void;
+ closeModal: () => void;
+ closeLoader: () => void;
+}
+
+const ModalContext = createContext({
+ isOpen: false,
+ modalType: '',
+ modalData: null,
+ openModal: () => {},
+ closeModal: () => {},
+ closeLoader: () => {},
+});
+
+export const useModalContext = () => useContext(ModalContext);
+
+export function ModalProvider({ children }: { children: ReactNode }) {
+ const [isOpen, setOpen] = useState(false);
+ const [modalType, setType] = useState('');
+ const [modalData, setData] = useState(null);
+
+ const openModal = (type: string, data?: any) => {
+ setOpen(true);
+ setType(type);
+ setData(data);
+ };
+
+ const closeModal = useCallback(() => {
+ setOpen(false);
+ setType('');
+ setData(null);
+
+ window.location.reload()
+ }, []);
+
+ const closeLoader = useCallback(() => {
+ setOpen(false);
+ setType('');
+ setData(null);
+ }, []);
+
+ useEffect(() => {
+ if (isOpen) {
+ document.body.style.overflow = 'hidden';
+ }
+ else {
+ document.body.style.overflow = 'auto';
+ }
+ }, [isOpen])
+
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/pannableStore.ts b/FRONT/DEV/src/store/pannableStore.ts
new file mode 100644
index 0000000..75b9c65
--- /dev/null
+++ b/FRONT/DEV/src/store/pannableStore.ts
@@ -0,0 +1,11 @@
+import {create} from 'zustand';
+
+interface FlowState {
+ isPanDisabled: boolean;
+ toggleEditingMode: () => void;
+}
+
+export const usePannableStore = create((set) => ({
+ isPanDisabled: false,
+ toggleEditingMode: () => set((state) => ({ isPanDisabled: !state.isPanDisabled })),
+}));
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/posStore.ts b/FRONT/DEV/src/store/posStore.ts
new file mode 100644
index 0000000..979545c
--- /dev/null
+++ b/FRONT/DEV/src/store/posStore.ts
@@ -0,0 +1,11 @@
+import { create } from 'zustand'
+
+interface PosState {
+ isNullPos: boolean;
+ setNullPos: (isNullPos: boolean) => void;
+}
+
+export const usePosStore = create()((set) => ({
+ isNullPos: false,
+ setNullPos: (isNullPos: boolean) => set({ isNullPos }),
+}))
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/settings/config/appearanceSettings.ts b/FRONT/DEV/src/store/settings/config/appearanceSettings.ts
new file mode 100644
index 0000000..e573149
--- /dev/null
+++ b/FRONT/DEV/src/store/settings/config/appearanceSettings.ts
@@ -0,0 +1,30 @@
+
+const createAppearanceSettings = () => {
+ return [
+ {
+ id: 'theme',
+ sectionId: 'settings-sections-appearance',
+ label: 'settings-colorTheme',
+ type: 'select',
+ defaultValue: 'light',
+ options: [
+ { value: 'light', label: 'settings-light' },
+ { value: 'dark', label: 'settings-dark' },
+ { value: 'system', label: 'settings-system' }
+ ],
+ },
+ {
+ id: 'language',
+ sectionId: 'settings-sections-appearance',
+ label: 'settings-interfaceLanguage',
+ type: 'select',
+ defaultValue: 'ru',
+ options: [
+ { value: 'ru', label: 'settings-russian' },
+ { value: 'en', label: 'settings-english' },
+ ],
+ },
+ ] as const;
+};
+
+export { createAppearanceSettings };
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/settings/config/networkSettings.ts b/FRONT/DEV/src/store/settings/config/networkSettings.ts
new file mode 100644
index 0000000..9e9ce9f
--- /dev/null
+++ b/FRONT/DEV/src/store/settings/config/networkSettings.ts
@@ -0,0 +1,55 @@
+import {useIpStore} from "../../ipStore";
+import {NetworkService} from "../../../api/request";
+
+const createNetworkSettings = () => {
+ const { ipAddress } = useIpStore.getState();
+
+ return [
+ {
+ id: 'clean-network',
+ type: 'button',
+ label: 'settings-clearSessionData',
+ sectionId: 'settings-sections-accessPoints',
+ defaultValue: 'settings-clear',
+ action: async ()=>{
+ try {
+ const res = await NetworkService.clearNetwork(ipAddress);
+ console.log(res);
+ }catch (e){
+ console.error(e);
+ alert('settings-dataNotDeleted' + e)
+ }
+ },
+ },
+ {
+ id: 'export-network',
+ type: 'button',
+ label: 'settings-exportNetworkData',
+ sectionId: 'settings-sections-accessPoints',
+ defaultValue: 'settings-export',
+ action: async () => {
+ try {
+ const response = await NetworkService.exportNetwork(ipAddress);
+ const { filePath } = await window.electronAPI.showSaveDialog({
+ title: 'settings-saveNetworksTitle',
+ defaultPath: "network_export.json",
+ filters: [{ name: "JSON", extensions: ["json"] }],
+ });
+
+ if (filePath) {
+ await window.electronAPI.writeFile(
+ filePath,
+ JSON.stringify(response.data, null, 2)
+ );
+ alert('settings-exportSuccess');
+ }
+ } catch (e: any) {
+ alert('settings-exportError' + e.message);
+ }
+ },
+
+ },
+ ] as const;
+};
+
+export { createNetworkSettings };
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/settings/config/nodesSettings.ts b/FRONT/DEV/src/store/settings/config/nodesSettings.ts
new file mode 100644
index 0000000..4673999
--- /dev/null
+++ b/FRONT/DEV/src/store/settings/config/nodesSettings.ts
@@ -0,0 +1,119 @@
+import {useIpStore} from "../../ipStore";
+import {NetworkService} from "../../../api/request";
+
+const createNodesSettings = () => {
+ const { ipAddress } = useIpStore.getState();
+
+ return [
+ {
+ id: 'rescan-nodes',
+ sectionId: 'settings-sections-subnetNodes',
+ label: 'settings-intervalScanning',
+ type: 'boolean',
+ defaultValue: false,
+ },
+ {
+ id: 'interval-nodes',
+ sectionId: 'settings-sections-subnetNodes',
+ label: 'settings-scanInterval',
+ type: 'number',
+ defaultValue: 60,
+ min: 1,
+ max: 2880
+ },
+ {
+ id: 'netnames-nodes',
+ sectionId: 'settings-sections-subnetNodes',
+ label: 'settings-displayNames',
+ type: 'select',
+ defaultValue: 'ip',
+ options: [
+ { value: 'ip', label: 'settings-ipAddress' },
+ { value: 'unknown', label: 'settings-unknown' },
+ ],
+ },
+ {
+ id: 'paginate-nodes',
+ type: 'number',
+ label: 'settings-rowsPerPage',
+ sectionId: 'settings-sections-subnetNodes',
+ defaultValue: 20,
+ min: 1,
+ },
+ {
+ id: 'customize-nodes',
+ type: 'button',
+ label: 'settings-displaySettings',
+ sectionId: 'settings-sections-subnetNodes',
+ defaultValue: 'settings-configure',
+ },
+ {
+ id: 'clean-nodes',
+ type: 'button',
+ label: 'settings-clearSessionData',
+ sectionId: 'settings-sections-subnetNodes',
+ defaultValue: 'settings-clear',
+ action: async ()=>{
+ try {
+ const res = await NetworkService.clearSubnet(ipAddress);
+ console.log(res)
+ }catch (e){
+ console.error(e);
+ alert('settings-dataNotDeleted' + e)
+ }
+ },
+ },
+ {
+ id: "export-nodes",
+ type: "button",
+ label: "settings-exportData",
+ sectionId: "settings-sections-subnetNodes",
+ defaultValue: "settings-export",
+ action: async () => {
+ try {
+ const response = await NetworkService.exportDevices(ipAddress);
+ const { filePath } = await window.electronAPI.showSaveDialog({
+ title: "settings-saveNodesTitle",
+ defaultPath: "nodes_export.json",
+ filters: [{ name: "JSON", extensions: ["json"] }],
+ });
+
+ if (filePath) {
+ await window.electronAPI.writeFile(
+ filePath,
+ JSON.stringify(response.data, null, 2)
+ );
+ alert("settings-exportSuccess");
+ }
+ } catch (e: any) {
+ alert("settings-exportError" + e.message);
+ }
+ },
+ },
+ {
+ id: 'table-columns-order',
+ type: 'hidden',
+ label: '',
+ sectionId: 'settings-sections-subnetNodes',
+ defaultValue: [
+ 'settings-status',
+ 'settings-name',
+ 'settings-vendor',
+ 'settings-ipAddress',
+ 'settings-openPorts',
+ 'settings-lastActivity',
+ 'settings-note',
+ 'settings-action'
+ ]
+ },
+ {
+ id: 'table-columns-disabled',
+ label: '',
+ sectionId: 'settings-sections-subnetNodes',
+ type: 'hidden',
+ defaultValue: []
+ }
+ ] as const;
+};
+
+export { createNodesSettings };
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/settings/settingsStore.ts b/FRONT/DEV/src/store/settings/settingsStore.ts
new file mode 100644
index 0000000..c54ffe0
--- /dev/null
+++ b/FRONT/DEV/src/store/settings/settingsStore.ts
@@ -0,0 +1,139 @@
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+import {createNodesSettings} from "./config/nodesSettings";
+import { createNetworkSettings } from './config/networkSettings';
+import {createAppearanceSettings} from "./config/appearanceSettings";
+
+type SettingValue = any;
+type SettingsDictionary = Record;
+
+export type Setting = {
+ readonly id: string;
+ readonly type: 'boolean' | 'select' | 'text' | 'number' | 'button' | 'hidden';
+ readonly label: string;
+ readonly defaultValue: any;
+ value?: string;
+ readonly min?: number;
+ readonly max?: number;
+ readonly options?: any;
+ readonly sectionId: string;
+ readonly required?: boolean;
+ readonly action?: () => void;
+};
+
+type SettingsState = {
+ settingsConfig: Setting[];
+ settingsValues: SettingsDictionary;
+ activeSection: string | null;
+ pendingChanges: SettingsDictionary;
+ setActiveSection: (sectionId: string | null) => void;
+ updateSetting: (id: string, value: any) => void;
+ initializeSettings: () => void;
+ getSetting: (id: string) => Setting & { currentValue: any };
+ resetSettings: () => void;
+ applyChanges: () => void;
+ discardChanges: () => void;
+ resetToDefaults: () => void;
+ getSettingValue: (id: string) => T;
+ getNumberSetting: (id: string, fallback?: number) => number;
+ updateSettingsConfig: () => void;
+};
+
+export const useSettingsStore = create()(
+ persist(
+ (set, get) => ({
+ settingsConfig: [],
+ settingsValues: {},
+ activeSection: null,
+ pendingChanges: {},
+
+ setActiveSection: (sectionId) => set({ activeSection: sectionId }),
+
+ updateSetting: (id, value) =>
+ set((state) => ({
+ pendingChanges: { ...state.pendingChanges, [id]: value }
+ })),
+
+ resetToDefaults: () => set({
+ settingsValues: Object.fromEntries(
+ get().settingsConfig.map(setting => [
+ setting.id,
+ setting.defaultValue
+ ])
+ ),
+ pendingChanges: {}
+ }),
+
+ applyChanges: () =>
+ set((state) => ({
+ settingsValues: { ...state.settingsValues, ...state.pendingChanges },
+ pendingChanges: {}
+ })),
+
+ discardChanges: () => set({ pendingChanges: {} }),
+
+ initializeSettings: () => {
+ const { settingsValues, settingsConfig } = get();
+ const newValues = { ...settingsValues };
+
+ settingsConfig.forEach(setting => {
+ if (newValues[setting.id] === undefined) {
+ newValues[setting.id] = setting.defaultValue;
+ }
+ });
+
+ set({ settingsValues: newValues });
+ },
+
+ updateSettingsConfig: () => {
+ const defaultSettings: Setting[] = [
+ ...createNodesSettings(),
+ ...createNetworkSettings(),
+ ...createAppearanceSettings(),
+ ];
+ set({ settingsConfig: defaultSettings });
+ },
+
+ getSetting: (id) => {
+ const state = get();
+ const setting = state.settingsConfig.find(s => s.id === id);
+ if (!setting) throw new Error(`Setting ${id} not found`);
+
+ const hasPendingChange = Object.prototype.hasOwnProperty.call(state.pendingChanges, id);
+ const hasSavedValue = Object.prototype.hasOwnProperty.call(state.settingsValues, id);
+
+ const currentValue = hasPendingChange
+ ? state.pendingChanges[id]
+ : hasSavedValue
+ ? state.settingsValues[id]
+ : setting.defaultValue;
+
+ return {
+ ...setting,
+ currentValue
+ };
+ },
+ getSettingValue: (id: string) => {
+ const state = get();
+ return state.settingsValues[id] ??
+ state.settingsConfig.find(s => s.id === id)?.defaultValue;
+ },
+
+ getNumberSetting: (id: string, fallback = 0) => {
+ const value = get().getSettingValue(id);
+ return Number(value) || fallback;
+ },
+
+ resetSettings: () => set({ settingsValues: {}, pendingChanges: {} }),
+ }),
+ {
+ name: 'app-settings',
+ partialize: (state) => ({
+ settingsValues: state.settingsValues
+ }),
+ onRehydrateStorage: () => (state) => {
+ state?.initializeSettings();
+ }
+ }
+ )
+);
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/themeContext.tsx b/FRONT/DEV/src/store/themeContext.tsx
new file mode 100644
index 0000000..5a425f7
--- /dev/null
+++ b/FRONT/DEV/src/store/themeContext.tsx
@@ -0,0 +1,59 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import { ThemeProvider as StyledTheme } from 'styled-components';
+import { light, dark} from '../const/theme/theme';
+import { GlobalStyle } from '../const/theme/globalStyles';
+import { themeNames, ThemName } from '../const/theme/names';
+import {useThemeSettings} from "../hooks/useSettings";
+
+type ThemeContextType = {
+ theme: ThemName;
+};
+
+const ThemeContext = createContext(undefined);
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
+
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const getSystemTheme = () =>
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNames[2] : themeNames[1];
+
+ const getInitialTheme = useThemeSettings();
+
+ const [theme, setTheme] = useState(getInitialTheme);
+
+ useEffect(() => {
+ setTheme(getInitialTheme)
+ }, [getInitialTheme]);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ let currentTheme = localStorage.getItem('theme')
+ const handleChange = () => {
+ if (currentTheme === themeNames[3]) {
+ const newTheme = getSystemTheme();
+
+ setTheme(newTheme);
+ localStorage.setItem('theme', themeNames[3]);
+ }
+ };
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, [theme]);
+
+ const currentTheme = theme === themeNames[3] ? getSystemTheme() : theme;
+
+ return (
+
+
+
+ {children}
+
+
+ );
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/toolBarStore.ts b/FRONT/DEV/src/store/toolBarStore.ts
new file mode 100644
index 0000000..761defc
--- /dev/null
+++ b/FRONT/DEV/src/store/toolBarStore.ts
@@ -0,0 +1,50 @@
+import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
+import { toolBarData } from '../const/menus/sideBar/data';
+import { ToolBar } from '../const/types/types';
+
+interface MenuState {
+ toolBar: ToolBar;
+ toggleMenu: (menuId: string) => void;
+ initialize: () => void;
+}
+
+export const useToolBarStore = create()(
+ persist(
+ (set, get) => ({
+ toolBar: toolBarData,
+ toggleMenu: (menuId) => set((state) => {
+ const updatedToolBar = state.toolBar.toolSpace.map((menu) => {
+ if (menu.id === menuId) {
+ return {
+ ...menu,
+ isOpen: !menu.isOpen,
+ isActive: true,
+ };
+ } else {
+ return {
+ ...menu,
+ isActive: false,
+ isOpen: false
+ };
+ }
+ });
+
+ return {
+ toolBar: {
+ ...state.toolBar,
+ current: menuId,
+ toolSpace: updatedToolBar,
+ },
+ };
+ }),
+ initialize: () => {
+ set({ toolBar: toolBarData });
+ },
+ }),
+ {
+ name: 'toolBar-storage',
+ storage: createJSONStorage(() => localStorage),
+ }
+ )
+);
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/useDeviceStore.ts b/FRONT/DEV/src/store/useDeviceStore.ts
new file mode 100644
index 0000000..6b2c815
--- /dev/null
+++ b/FRONT/DEV/src/store/useDeviceStore.ts
@@ -0,0 +1,51 @@
+import { create } from 'zustand';
+
+export interface DeviceUser {
+ ip: string;
+ mac: string;
+ hostname: string;
+ vendor: string;
+ open_ports: string[];
+ status: string;
+ last_seen: string;
+ note: string;
+}
+
+export interface WifiNetworkScanner {
+ ssid: string;
+ bssid: string;
+ channel: number;
+ signal: number;
+ encryption: string;
+ frequency: string;
+ vendor: string;
+ hidden: boolean;
+ note: string;
+}
+
+interface NetworkStore {
+ devices: DeviceUser[];
+ setDevices: (devices: DeviceUser[]) => void;
+ addDevice: (device: DeviceUser) => void;
+ wifiNetworks: WifiNetworkScanner[];
+ setWifiNetworks: (networks: WifiNetworkScanner[]) => void;
+ addWifiNetwork: (network: WifiNetworkScanner) => void;
+
+ clearAll: () => void;
+}
+
+const useNetworkStore = create((set) => ({
+ devices: [],
+ setDevices: (devices) => set({ devices }),
+ addDevice: (device) =>
+ set((state) => ({ devices: [...state.devices, device] })),
+
+ wifiNetworks: [],
+ setWifiNetworks: (networks) => set({ wifiNetworks: networks }),
+ addWifiNetwork: (network) =>
+ set((state) => ({ wifiNetworks: [...state.wifiNetworks, network] })),
+
+ clearAll: () => set({ devices: [], wifiNetworks: [] })
+}));
+
+export default useNetworkStore;
\ No newline at end of file
diff --git a/FRONT/DEV/src/store/workSpaceStore.ts b/FRONT/DEV/src/store/workSpaceStore.ts
new file mode 100644
index 0000000..2f14215
--- /dev/null
+++ b/FRONT/DEV/src/store/workSpaceStore.ts
@@ -0,0 +1,26 @@
+import { create } from 'zustand'
+import { persist, createJSONStorage } from 'zustand/middleware'
+import { workSpaceNames } from '../const/workspace/names';
+
+interface WorkSpaceState {
+ workModule: string;
+ setWorkModule: (newModule: string) => void;
+ initialize: () => void;
+}
+
+export const useWorkSpaceStore = create()(
+ persist(
+ (set) => ({
+ workModule: workSpaceNames[1],
+ setWorkModule: (newModule: string) => set({ workModule: newModule }),
+ initialize: () => {
+ set({ workModule: workSpaceNames[1]});
+ },
+ }),
+
+ {
+ name: 'workSpace-storage',
+ storage: createJSONStorage(() => localStorage)
+ }
+ )
+)
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/checkAdmin.ts b/FRONT/DEV/src/utils/checkAdmin.ts
new file mode 100644
index 0000000..9bcadaf
--- /dev/null
+++ b/FRONT/DEV/src/utils/checkAdmin.ts
@@ -0,0 +1,19 @@
+import { useIpStore } from "../store";
+import { useAdminStore } from "../store/adminStore";
+import { NetworkService } from "../api/request";
+
+export const useCheckAdmin = () => {
+ const { ipAddress } = useIpStore()
+ const { setAdmin } = useAdminStore()
+
+ return async () => {
+ const response = await NetworkService.whoami(ipAddress);
+ console.log(response.data);
+ if(response.status == 401){
+ setAdmin(false)
+ }
+ else{
+ setAdmin(response.data.user.is_admin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/i18n/config.ts b/FRONT/DEV/src/utils/i18n/config.ts
new file mode 100644
index 0000000..97b38b3
--- /dev/null
+++ b/FRONT/DEV/src/utils/i18n/config.ts
@@ -0,0 +1,11 @@
+export const supportedLanguages = {
+ en: 'English',
+ ru: 'Русский',
+} as const;
+
+export type Language = keyof typeof supportedLanguages;
+
+export const defaultLanguage = 'ru';
+
+export const namespaces = ['common', 'components'] as const;
+export type Namespace = (typeof namespaces)[number];
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/i18n/hooks.ts b/FRONT/DEV/src/utils/i18n/hooks.ts
new file mode 100644
index 0000000..6c60749
--- /dev/null
+++ b/FRONT/DEV/src/utils/i18n/hooks.ts
@@ -0,0 +1,11 @@
+import { Namespace } from './config';
+import { useI18n } from './provider';
+
+ export const useTranslation = (ns?: Namespace) => {
+ const { t, language } = useI18n();
+
+ return {
+ t: (key: string) => t(key, ns),
+ language,
+ };
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/i18n/provider.tsx b/FRONT/DEV/src/utils/i18n/provider.tsx
new file mode 100644
index 0000000..d453c1b
--- /dev/null
+++ b/FRONT/DEV/src/utils/i18n/provider.tsx
@@ -0,0 +1,92 @@
+'use client'
+import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
+import { defaultLanguage, Language, Namespace, namespaces, supportedLanguages } from './config';
+
+type Translation = Record;
+type NamespaceTranslations = Record;
+type LanguageTranslations = Record;
+
+const I18nContext = createContext<{
+ language: Language;
+ t: (key: string, namespace?: Namespace) => string;
+}>({
+ language: defaultLanguage,
+ t: (key: string) => key,
+});
+
+interface I18nProviderProps {
+ children: React.ReactNode;
+ initialLanguage: Language;
+}
+
+export const I18nProvider = ({ children, initialLanguage }: I18nProviderProps) => {
+ const [language, setLanguage] = useState(initialLanguage);
+ const [translations, setTranslations] = useState({} as LanguageTranslations);
+
+
+ // Загрузка переводов
+ useEffect(() => {
+ const loadTranslations = async () => {
+ const newTranslations: LanguageTranslations = { ...translations };
+
+ await Promise.all(
+ (Object.keys(supportedLanguages) as Language[]).map(async (lang) => {
+ if (!newTranslations[lang]) {
+ newTranslations[lang] = {} as NamespaceTranslations;
+
+ await Promise.all(
+ namespaces.map(async (ns) => {
+ try {
+ const res = await fetch(`/locales/${lang}/${ns}.json`);
+ if (!res.ok) {
+ throw new Error(`HTTP ${res.status}`);
+ }
+ const data = await res.json();
+ newTranslations[lang][ns] = data;
+ } catch (e) {
+ console.error(`Failed to load ${lang}/${ns} translations`, e);
+ newTranslations[lang][ns] = {};
+ }
+ })
+ );
+ }
+ })
+ );
+
+ setTranslations(newTranslations);
+ };
+
+ loadTranslations();
+ }, []);
+
+
+ useEffect(() => {
+ setLanguage(initialLanguage);
+ }, [initialLanguage]);
+
+ const t = useMemo(() => {
+ return (key: string, namespace: Namespace = 'common') => {
+ return translations[language]?.[namespace]?.[key] || key;
+ };
+ }, [language, translations]);
+
+
+ const contextValue = useMemo(() => ({
+ language,
+ t,
+ }), [language, t]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useI18n = () => {
+ const context = useContext(I18nContext);
+ if (!context) {
+ throw new Error('useI18n must be used within I18nProvider');
+ }
+ return context;
+};
diff --git a/FRONT/DEV/src/utils/nodeController/autosavePosition.ts b/FRONT/DEV/src/utils/nodeController/autosavePosition.ts
new file mode 100644
index 0000000..7b9e21e
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/autosavePosition.ts
@@ -0,0 +1,104 @@
+export const processNodeChanges = (changes: any, mainGroups: any) => {
+ let newData;
+
+ changes.forEach((change: any) => {
+ if (change.type === 'position' && change.position) {
+ const nodeId = change.id;
+
+ if (nodeId === 'g0') {
+ newData = {
+ container_id: mainGroups[0].id,
+ container_x: change.position.x,
+ container_y: change.position.y,
+ isOpen: true
+ };
+ } else if (nodeId === 'g1') {
+ newData = {
+ container_id: mainGroups[1].id,
+ container_x: change.position.x,
+ container_y: change.position.y,
+ isOpen: true
+ };
+ }
+ }
+ });
+
+ return newData;
+};
+
+const getLastChanges = (changes: any[]) => {
+ const latestChanges: { [key: number]: any } = {};
+
+ changes.forEach((change) => {
+ latestChanges[change.container_id] = change;
+ });
+
+ return Object.values(latestChanges);
+};
+
+export const autosavePosition = (changeArray: any[], socket: any,) => {
+ if (changeArray.length > 0) {
+ const lastChanges = getLastChanges(changeArray);
+
+ lastChanges.forEach((change) => {
+ if (socket && socket.readyState === WebSocket.OPEN && change) {
+ socket.send(JSON.stringify(change))
+ console.log('JSON отправлен:', JSON.stringify(change));
+ console.log('Отправленные данные:', change);
+ } else {
+ console.log('WebSocket не открыт или нет данных для отправки');
+ }
+ });
+ }
+}
+
+export const resetPosition = (socket: any, setNullPos: (value: boolean) => void, nodes: any[], setNodes: (nodes: any[]) => void) => {
+ const nullPositions = [
+ {
+ container_id: 1,
+ container_x: 0,
+ container_y: 0,
+ isOpen: true
+ },
+ {
+ container_id: 2,
+ container_x: 500,
+ container_y: 0,
+ isOpen: true
+ },
+ ]
+
+ nullPositions.forEach(pos => {
+ if (socket && socket.readyState === WebSocket.OPEN) {
+ socket.send(JSON.stringify(pos))
+ console.log('JSON отправлен:', JSON.stringify(pos))
+ console.log('Отправленные данные:', pos)
+ } else {
+ console.log('WebSocket не открыт или нет данных для отправки')
+ }
+ })
+
+ const updatedNodes = nodes.map(node => {
+
+ if(node.id=="g0"){
+ return {
+ ...node,
+ position: { x: 0, y: 0 },
+ positionAbsolute: { x: 0, y: 0 }
+ };
+ }
+
+ if(node.id=="g1"){
+ return {
+ ...node,
+ position: { x: 500, y: 0 },
+ positionAbsolute: { x: 500, y: 0 }
+ };
+ }
+
+ return node;
+ });
+
+ setNodes(updatedNodes);
+ setNullPos(false);
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/collapseGroup.ts b/FRONT/DEV/src/utils/nodeController/collapseGroup.ts
new file mode 100644
index 0000000..a21cc9f
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/collapseGroup.ts
@@ -0,0 +1,65 @@
+export const collapseGroup = (sortedGroup: any, nodes: any[]) => {
+ const nodesMap = new Map(nodes.map(node => [node.id, structuredClone(node)]));
+
+ Object.entries(sortedGroup).forEach(([groupId, groupData]: any) => {
+ const groupNode = nodesMap.get(groupId);
+ if (!groupNode) return;
+
+ const clicked = groupData.clicked;
+ const padding = 20;
+
+ const higherOrderNodes = groupData.higherOrder.map((childId: string) => {
+ const child = nodesMap.get(childId);
+ if (!child) return null;
+
+ const newHeight = clicked ? 60 : child.data.height;
+ const updatedChild = {
+ ...child,
+ data: {
+ ...child.data,
+ visible: !clicked,
+ style: { ...child.data.style, height: newHeight }
+ },
+ measured: {
+ ...child.measured,
+ height: newHeight + 4,
+ width: child.data.style.width + 4
+ }
+ };
+ nodesMap.set(childId, updatedChild);
+ return updatedChild;
+ }).filter(Boolean);
+
+ groupData.lowerOrder.forEach((childId: string) => {
+ const child = nodesMap.get(childId);
+ if (child) child.hidden = clicked;
+ });
+
+ let currentY = padding;
+ higherOrderNodes.forEach((child: any) => {
+ nodesMap.set(child.id, {
+ ...child,
+ position: { ...child.position, y: currentY }
+ });
+ currentY += child.measured.height + padding;
+ });
+
+ const newGroupHeight = clicked
+ ? currentY
+ : groupNode.data.height;
+
+ nodesMap.set(groupId, {
+ ...groupNode,
+ data: {
+ ...groupNode.data,
+ style: { ...groupNode.data.style, height: newGroupHeight }
+ },
+ measured: {
+ ...groupNode.measured,
+ height: newGroupHeight + 4
+ }
+ });
+ });
+
+ return Array.from(nodesMap.values());
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/groupSort.ts b/FRONT/DEV/src/utils/nodeController/groupSort.ts
new file mode 100644
index 0000000..3d9064f
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/groupSort.ts
@@ -0,0 +1,37 @@
+export const groupSort = (groups: any, nodes: any)=>{
+
+ const result: {
+ [key: string]: {
+ higherOrder: string[];
+ lowerOrder: string[];
+ clicked: boolean
+ };
+ } = {};
+
+ groups.forEach((group: any) => {
+ const groupId = group.id;
+ result[groupId] = {
+ higherOrder: [],
+ lowerOrder: [],
+ clicked: group.click
+ };
+
+ const children = nodes.filter((node: any) => node.id.startsWith(`${groupId}_`));
+
+ children.forEach((child: any) => {
+ const childId = child.id;
+
+ const parts = childId.split('_');
+ if (parts.length === 2) {
+ const higherOrderValue = childId
+ result[groupId].higherOrder.push(higherOrderValue)
+ } else {
+ const lowerOrderValue = childId
+ result[groupId].lowerOrder.push(lowerOrderValue)
+ }
+ });
+ });
+
+
+ return result
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/index.ts b/FRONT/DEV/src/utils/nodeController/index.ts
new file mode 100644
index 0000000..26cebec
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/index.ts
@@ -0,0 +1,6 @@
+export { autosavePosition, processNodeChanges , resetPosition} from './autosavePosition';
+export { initNetworkIntData } from './initNetIntData';
+export { collapseGroup } from './collapseGroup';
+export {groupSort} from './groupSort'
+export { updateWifiData } from './updateWifiData';
+export {useInitCanvas} from './nodeCreator/inits/initor'
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/initNetIntData.ts b/FRONT/DEV/src/utils/nodeController/initNetIntData.ts
new file mode 100644
index 0000000..c54f060
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/initNetIntData.ts
@@ -0,0 +1,60 @@
+import { ResponseData, Interfaces, MainData, IEdge, NetworkData } from '../../const/types/types'
+import { ipsInitor } from './nodeCreator/inits/initIpArray';
+
+export const initNetworkIntData = (mainGroups: ResponseData[]): { interfaces: Interfaces[], edgeData: IEdge[] } => {
+ const edgeData: IEdge[] = [];
+ const interfaces: Interfaces[] = [];
+
+ if (mainGroups) {
+ mainGroups.map((el, indx) => {
+ let nodeData: MainData[] = [];
+ el.interfaces.map((net, index) => {
+ let nodeID = `g${indx}_nw${index}`;
+ let portID = `p-nw${index}`;
+ let targetId = `${portID}_target-right`;
+ let sourceId = `${nodeID}-source-left`;
+
+ if (indx === 0) {
+ const edge: IEdge = {
+ id: `${nodeID}-${portID}`,
+ source: nodeID,
+ target: portID,
+ sourceHandle: sourceId,
+ targetHandle: targetId,
+ type: "default",
+ };
+ edgeData.push(edge);
+ }
+
+ const network: NetworkData = {
+ device: net.name,
+ type: net.type,
+ mac: net.mac_address,
+ connection: net.active_connection,
+ mtu: net.device_mtu,
+ state: net.device_state,
+ strength: net.strength
+ };
+
+ const ipArray = ipsInitor(net);
+
+ let interfaceData: MainData = {
+ networkData: network,
+ ipArrays: ipArray
+ };
+
+ nodeData.push(interfaceData);
+ });
+
+ let j: Interfaces = {
+ container_x: el.container_x,
+ container_y: el.container_y,
+ mainData: nodeData
+ };
+
+ interfaces.push(j);
+ });
+ }
+
+ return { interfaces, edgeData };
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/nodeCreator/deviceState.ts b/FRONT/DEV/src/utils/nodeController/nodeCreator/deviceState.ts
new file mode 100644
index 0000000..aa2dd6f
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/nodeCreator/deviceState.ts
@@ -0,0 +1,33 @@
+type StateType = Record;
+
+export const stateMap: StateType = {
+ '0': "Неизвестно",
+ '10': "Не обслуживается",
+ '20': "Недоступно",
+ '30': "Отключено",
+ '40': "Подготовка",
+ '50': "Конфигурация",
+ '60': "Требуется аутентификация",
+ '70': "Настройка IP",
+ '80': "Проверка IP",
+ '90': "Дополнительные настройки",
+ '100': "Подключено",
+ '110': "Отключается",
+ '120': "Ошибка",
+}
+
+export const stateColor: StateType = {
+ '0': "#7e7e7e",
+ '10': "#d95454",
+ '20': "#d95454",
+ '30': "#d95454",
+ '40': "",
+ '50': "",
+ '60': "",
+ '70': "",
+ '80': "",
+ '90': "",
+ '100': "#66BB6A",
+ '110': "",
+ '120': "",
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initEdges.ts b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initEdges.ts
new file mode 100644
index 0000000..c422a83
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initEdges.ts
@@ -0,0 +1,19 @@
+import { useState } from "react";
+import { IEdge } from "../../../../const/types/types";
+
+export const useInitEdges = () => {
+ const [initedEdges, setEdges] = useState([])
+
+ const setInitEdges = (something: IEdge[]) => {
+ setEdges((prevEdges) => {
+ const edges: IEdge[] = []
+
+ something.map(edge=>{
+ edges.push(edge)
+ })
+
+ return [...prevEdges, ...edges]
+ })
+ }
+ return { initedEdges, setInitEdges }
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initIpArray.ts b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initIpArray.ts
new file mode 100644
index 0000000..5806438
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initIpArray.ts
@@ -0,0 +1,44 @@
+import { ResponseIpConf, ResponseNetwork, IpData } from "../../../../const/types/types"
+
+interface IpArray {
+ route: boolean,
+ ipData: IpData[]
+}
+
+export const ipsInitor = (network: ResponseNetwork) => {
+ const array: IpArray[] = [
+ { route: true, ipData: [] },
+ { route: false, ipData: [] },
+ ];
+
+ const ipArrayInit = (ip: ResponseIpConf, version: string) => {
+ const isRouted = ip.routes.length > 0
+
+ const newData: IpData = {
+ ipType: ip.method,
+ ipAdress: ip.address,
+ ipGate: ip.gateway,
+ ipDns: ip.dns,
+ ipRoutese: ip.routes,
+ ipVersion: version
+ };
+
+ if (isRouted) {
+ array[0].ipData.push(newData);
+ } else {
+ array[1].ipData.push(newData);
+ }
+ }
+
+ network.ipv4.configurations.forEach((ip: ResponseIpConf) => {
+ ipArrayInit(ip, '4')
+ });
+
+ network.ipv6.configurations.forEach((ip: ResponseIpConf) => {
+ ipArrayInit(ip, '6')
+ });
+
+ const resultArray = array.filter((item) => item.ipData.length > 0);
+
+ return resultArray;
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initNodes.ts b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initNodes.ts
new file mode 100644
index 0000000..a539069
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initNodes.ts
@@ -0,0 +1,297 @@
+import { useState } from "react";
+import { NodeColors } from "../../../../components/customeNodes/types/colors";
+import { stateColor, stateMap } from "../deviceState";
+import { Interfaces, NetworkNode, IpNode, PortNode, NodeGroup, NetworkInterface } from "../../../../const/types/types"
+
+export const useInitNodes = () => {
+ const [initedNodes, setNodes] = useState<(NetworkInterface)[]>([]);
+ const yGap = 20
+
+ interface MainHeight {
+ x?: number,
+ y?: number,
+ height?: number;
+ }
+
+ const findMaxHeight = (data: any[])=>{
+ const filtred = data.filter((el)=> el.x ===0 || el.x ===1)
+ if(filtred.length ===0){
+ return 140
+ }
+ const max =filtred.reduce((max, el)=> Math.max(max, el.height), 0)
+ return max
+
+ }
+
+ const setInitNodes = (interfaces: Interfaces[]) => {
+ setNodes(() => {
+ const networkNodes: NetworkNode[] = []
+ const portNodes: PortNode[] = []
+ const ipGroupes: NodeGroup[][] = []
+ const ipNodes: IpNode[][] = []
+ const networkGroups: NodeGroup[] = []
+
+ let width: number, columnCount: MainHeight[] = []
+ let widthArr: number[] = []
+ let heightArr: number[] = []
+
+ interfaces.map((b, indexB) => {
+ const centerX = 20
+ const centerY = 20
+ let currentY = centerY;
+ b.mainData.map((data, networkIndex) => {
+ data.ipArrays.map((el) => {
+ if (el.route === true) {
+ const column = { x: 1 }
+ columnCount.push(column)
+ }
+ else if (el.route === false) {
+ const column = { x: 0 }
+ columnCount.push(column)
+ }
+ })
+
+ if (columnCount.length == 2) {
+ width = (260 * 2) + 45
+ }
+ else {
+ width = 260 + 35
+ }
+
+ let mainHeight: MainHeight[] = []
+
+ data.ipArrays.forEach((ipInfo, groupIndx) => {
+ if(!ipInfo || !ipInfo.ipData || ipInfo.ipData.length ===0){
+ return
+ }
+ let groupes: NodeGroup[] = []
+ let nodeCount = ipInfo.ipData.length
+ let x = 20, y = 150, newX, newY, gap = 0
+
+ let height = (140 + 15) * (nodeCount)
+
+
+ if (ipInfo.route) {
+ newX = width - 260 - 15
+
+ const groupHeight = {
+ x: 1,
+ height: height
+ }
+ mainHeight.push(groupHeight)
+ }
+ else {
+ newX = x
+ const groupHeight = {
+ x: 0,
+ height: height
+ }
+ mainHeight.push(groupHeight)
+ }
+
+
+ const newIpGroupe: NodeGroup = {
+ id: `g${indexB}_nw${networkIndex}_rx${groupIndx}`,
+ position: {
+ x: newX,
+ y: y
+ },
+ data: {
+ style: {
+ width: 260,
+ height: height
+ }
+ },
+ hidden: false,
+ type: "ipgroup",
+ parentId: `g${indexB}_nw${networkIndex}`,
+ extent: 'parent',
+ draggable: false,
+ }
+
+ let startY = 0
+ const ipGap = 15
+
+
+ ipInfo.ipData.map((ip, ipIndex) => {
+ let nodes: IpNode[] = []
+ const y = startY;
+
+ startY += 140 + ipGap;
+
+
+ const newIpNode: IpNode = {
+ id: `g${indexB}_nw${networkIndex}_rx${groupIndx}_ip${ipIndex}`,
+ position: {
+ x: 0,
+ y: y
+ },
+ type: 'ip',
+ data: {
+ ipData: {
+ ipType: ip.ipType,
+ ipAdress: ip.ipAdress,
+ ipGate: ip.ipGate,
+ ipDns: ip.ipDns,
+ ipRoutese: [],
+ ipVersion: ip.ipVersion
+ },
+ interfaceData:{
+ device: data.networkData.device,
+ connection: data.networkData.connection,
+ },
+ route: ipInfo.route,
+ style: {
+ width: 260,
+ height: 140,
+ borderType: "solid",
+ borderColor: NodeColors["1"],
+ },
+ },
+ hidden: false,
+ parentId: `g${indexB}_nw${networkIndex}_rx${groupIndx}`,
+ extent: 'parent',
+ draggable: false,
+ }
+ nodes.push(newIpNode)
+ ipNodes.push(nodes)
+ })
+ groupes.push(newIpGroupe)
+ ipGroupes.push(groupes)
+ })
+
+ const maxHeight = findMaxHeight(mainHeight);
+ // const networkNodeHeight = (maxHeight || 0) + 150;
+ const networkNodeHeight = maxHeight +150
+
+ let groupId: string
+ if (data.networkData.connection === 'unknown') {
+ groupId = 'cn1'
+ }
+ else {
+ groupId = 'cn0'
+ }
+
+ const y = currentY;
+
+ currentY += networkNodeHeight + yGap;
+
+
+ const newNode: NetworkNode = {
+ id: `g${indexB}_nw${networkIndex}`,
+ position: {
+ x: centerX,
+ y: y
+ },
+ type: "network",
+ data: {
+ networkData: {
+ device: data.networkData.device,
+ type: data.networkData.type,
+ mac: data.networkData.mac,
+ mtu: data.networkData.mtu,
+ state: stateMap[data.networkData.state],
+ connection: data.networkData.connection,
+ id: `nw${networkIndex}`,
+ strength: data.networkData.strength
+ },
+ style: {
+ width: width,
+ height: networkNodeHeight as number,
+ borderType: "solid",
+ borderColor: NodeColors["0"],
+ backgroundColor: stateColor[data.networkData.state]
+ },
+ height: networkNodeHeight,
+ visible: true
+ },
+ parentId: `g${indexB}`,
+ extent: 'parent',
+ };
+
+ widthArr.push(width)
+ heightArr.push(networkNodeHeight)
+ networkNodes.push(newNode)
+ columnCount = []
+
+ let portNodeHeight = 60;
+ let absoluteY = (currentY - portNodeHeight + 10) - (networkNodeHeight / 2);
+ if (data.networkData.connection !== 'unknown') {
+ const portNode: PortNode = {
+ id: `p-nw${networkIndex}`,
+ position: {
+ x: -350,
+ y: absoluteY,
+ },
+ type: 'portNode',
+ data: {
+ label: `${data.networkData.connection}`,
+ id: `p-nw${networkIndex}`,
+ style: {
+ width: 260,
+ height: portNodeHeight,
+ borderType: "solid",
+ borderColor: NodeColors["0"],
+ },
+ }
+ }
+ portNodes.push(portNode)
+ }
+ })
+
+ const sumHeight = heightArr.reduce((a, c) => a + c, 0);
+ const maxWidth = Math.max(...widthArr)
+
+ let count = b.mainData.length
+ let groupHeight = sumHeight + (count * 30)
+ let groupWidth = maxWidth + 40
+
+ let label: string;
+
+ if (indexB == 0) {
+ label = 'Активные интерфейсы'
+ }
+ else {
+ label = 'Неактивные интерфейсы'
+ }
+
+ let x = b.container_x
+ if (indexB > 0 && x === 0) {
+ x = x + 500
+ }
+
+ if (b.mainData.length > 0) {
+ const networkGroup: NodeGroup = {
+ id: `g${indexB}`,
+ position: {
+ x: x,
+ y: b.container_y
+ },
+ type: "nodegroup",
+ data: {
+ label: label,
+ id: `g${indexB}`,
+ height: groupHeight,
+ style: {
+ width: groupWidth,
+ height: groupHeight,
+ borderType: "solid",
+ borderColor: NodeColors["0"],
+ },
+ },
+ }
+
+ networkGroups.push(networkGroup)
+ }
+
+
+ heightArr = []
+ })
+
+ return [...networkGroups, ...networkNodes, ...ipGroupes.flat(), ...portNodes, ...ipNodes.flat()];
+ });
+
+ };
+
+ return { initedNodes, setInitNodes };
+};
diff --git a/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initor.ts b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initor.ts
new file mode 100644
index 0000000..bf2f187
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/nodeCreator/inits/initor.ts
@@ -0,0 +1,15 @@
+import { useInitNodes } from "./initNodes";
+import { useInitEdges } from "./initEdges";
+import { IEdge, Interfaces } from "../../../../const/types/types";
+
+export const useInitCanvas = () => {
+ const { initedNodes, setInitNodes } = useInitNodes();
+ const { initedEdges, setInitEdges } = useInitEdges();
+
+ const handleInit = (edgeData: IEdge[], interfaces: Interfaces[])=>{
+ setInitNodes(interfaces)
+ setInitEdges(edgeData)
+ }
+
+ return { initedNodes, initedEdges, handleInit };
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/updateWifiData.ts b/FRONT/DEV/src/utils/nodeController/updateWifiData.ts
new file mode 100644
index 0000000..7be7d01
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/updateWifiData.ts
@@ -0,0 +1,21 @@
+export const updateWifiData = (wifiData: any, nodes: any)=>{
+ const wifiName = wifiData.name;
+
+ const updatedNodes = nodes.map((node: any) => {
+ if (node.data.networkData && node.data.networkData.device === wifiName) {
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ networkData: {
+ ...node.data.networkData,
+ strength: wifiData.strength,
+ },
+ },
+ };
+ }
+ return node;
+ });
+
+ return updatedNodes
+}
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/nodeController/validator.ts b/FRONT/DEV/src/utils/nodeController/validator.ts
new file mode 100644
index 0000000..0f5294c
--- /dev/null
+++ b/FRONT/DEV/src/utils/nodeController/validator.ts
@@ -0,0 +1,35 @@
+export const validateIPAddress = (ip: string) => {
+ const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
+ return ipv4Pattern.test(ip) || ipv6Pattern.test(ip);
+};
+
+export const validateSameSubnet = (ip1: string, ip2: string, ip3: string | string[]): boolean => {
+ const extractSubnet = (ip: string): string => {
+ if (typeof ip !== 'string') {
+ throw new TypeError(`Некорректный формат IP-адреса: ${ip}`);
+ }
+ return ip.split('.').slice(0, 3).join('.');
+ };
+
+ const areInSameSubnet = (ip: string, subnet: string): boolean => {
+ return extractSubnet(ip) === subnet;
+ };
+
+ try {
+ const subnet1 = extractSubnet(ip1);
+ const subnet2 = extractSubnet(ip2);
+
+ // Проверяем, что IP и шлюз в одной подсети
+ if (subnet1 !== subnet2) {
+ return false;
+ }
+
+ // Если DNS — массив, проверяем, что хотя бы один DNS в той же подсети
+ const dnsArray = Array.isArray(ip3) ? ip3 : [ip3];
+ return dnsArray.some(dns => areInSameSubnet(dns, subnet1));
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+};
\ No newline at end of file
diff --git a/FRONT/DEV/src/utils/pageReloads.ts b/FRONT/DEV/src/utils/pageReloads.ts
new file mode 100644
index 0000000..0830c75
--- /dev/null
+++ b/FRONT/DEV/src/utils/pageReloads.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "react";
+
+export const usePageReloadCounter = (): number => {
+ const [reloadCount, setReloadCount] = useState(() => {
+ // Инициализируем состояние из sessionStorage или начинаем с 0
+ const savedCount = sessionStorage.getItem('pageReloadCount');
+ return savedCount ? parseInt(savedCount, 10) : 0;
+ });
+
+ useEffect(() => {
+ // Увеличиваем счётчик при каждом монтировании компонента
+ const newCount = reloadCount + 1;
+ setReloadCount(newCount);
+ sessionStorage.setItem('pageReloadCount', newCount.toString());
+ }, []); // Пустой массив зависимостей означает, что эффект сработает только при монтировании
+
+ return reloadCount;
+};
\ No newline at end of file
diff --git a/FRONT/DEV/tsconfig.app.json b/FRONT/DEV/tsconfig.app.json
new file mode 100644
index 0000000..358ca9b
--- /dev/null
+++ b/FRONT/DEV/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/FRONT/DEV/tsconfig.json b/FRONT/DEV/tsconfig.json
new file mode 100644
index 0000000..347251f
--- /dev/null
+++ b/FRONT/DEV/tsconfig.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ESNext"
+ ],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ },
+ "include": [
+ "src",
+ "electron.d.ts",
+ "main.js"
+ ],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/FRONT/DEV/tsconfig.node.json b/FRONT/DEV/tsconfig.node.json
new file mode 100644
index 0000000..dcfd137
--- /dev/null
+++ b/FRONT/DEV/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
\ No newline at end of file
diff --git a/FRONT/DEV/vite-env.d.ts b/FRONT/DEV/vite-env.d.ts
new file mode 100644
index 0000000..151aa68
--- /dev/null
+++ b/FRONT/DEV/vite-env.d.ts
@@ -0,0 +1 @@
+///
\ No newline at end of file
diff --git a/FRONT/DEV/vite.config.ts b/FRONT/DEV/vite.config.ts
new file mode 100644
index 0000000..8de6065
--- /dev/null
+++ b/FRONT/DEV/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ rollupOptions: {
+ external: ['@electron/remote'],
+ },
+ },
+});
\ No newline at end of file
diff --git a/FRONT/DEV/yarn.lock b/FRONT/DEV/yarn.lock
new file mode 100644
index 0000000..46de806
--- /dev/null
+++ b/FRONT/DEV/yarn.lock
@@ -0,0 +1,2305 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz"
+ integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@babel/code-frame@^7.26.2":
+ version "7.26.2"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz"
+ integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/compat-data@^7.26.8":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz"
+ integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
+
+"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.26.10":
+ version "7.26.10"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz"
+ integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.26.10"
+ "@babel/helper-compilation-targets" "^7.26.5"
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helpers" "^7.26.10"
+ "@babel/parser" "^7.26.10"
+ "@babel/template" "^7.26.9"
+ "@babel/traverse" "^7.26.10"
+ "@babel/types" "^7.26.10"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.26.10", "@babel/generator@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz"
+ integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==
+ dependencies:
+ "@babel/parser" "^7.27.0"
+ "@babel/types" "^7.27.0"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
+"@babel/helper-compilation-targets@^7.26.5":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz"
+ integrity sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==
+ dependencies:
+ "@babel/compat-data" "^7.26.8"
+ "@babel/helper-validator-option" "^7.25.9"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-module-imports@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz"
+ integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
+ dependencies:
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-module-transforms@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz"
+ integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/helper-plugin-utils@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz"
+ integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==
+
+"@babel/helper-string-parser@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
+ integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
+
+"@babel/helper-validator-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
+"@babel/helper-validator-option@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz"
+ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+
+"@babel/helpers@^7.26.10":
+ version "7.26.10"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz"
+ integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
+ dependencies:
+ "@babel/template" "^7.26.9"
+ "@babel/types" "^7.26.10"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz"
+ integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==
+ dependencies:
+ "@babel/types" "^7.27.0"
+
+"@babel/plugin-transform-react-jsx-self@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz"
+ integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-react-jsx-source@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz"
+ integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/runtime@^7.18.9":
+ version "7.26.10"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz"
+ integrity sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@babel/template@^7.26.9", "@babel/template@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz"
+ integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/parser" "^7.27.0"
+ "@babel/types" "^7.27.0"
+
+"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz"
+ integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.27.0"
+ "@babel/parser" "^7.27.0"
+ "@babel/template" "^7.27.0"
+ "@babel/types" "^7.27.0"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz"
+ integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+
+"@electron/asar@^3.2.1":
+ version "3.2.18"
+ resolved "https://registry.npmjs.org/@electron/asar/-/asar-3.2.18.tgz"
+ integrity sha512-2XyvMe3N3Nrs8cV39IKELRHTYUWFKrmqqSY1U+GMlc0jvqjIVnoxhNd2H4JolWQncbJi1DCvb5TNxZuI2fEjWg==
+ dependencies:
+ commander "^5.0.0"
+ glob "^7.1.6"
+ minimatch "^3.0.4"
+
+"@electron/get@^2.0.0":
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz"
+ integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==
+ dependencies:
+ debug "^4.1.1"
+ env-paths "^2.2.0"
+ fs-extra "^8.1.0"
+ got "^11.8.5"
+ progress "^2.0.3"
+ semver "^6.2.0"
+ sumchecker "^3.0.1"
+ optionalDependencies:
+ global-agent "^3.0.0"
+
+"@electron/notarize@^1.2.3":
+ version "1.2.4"
+ resolved "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.4.tgz"
+ integrity sha512-W5GQhJEosFNafewnS28d3bpQ37/s91CDWqxVchHfmv2dQSTWpOzNlUVQwYzC1ay5bChRV/A9BTL68yj0Pa+TSg==
+ dependencies:
+ debug "^4.1.1"
+ fs-extra "^9.0.1"
+
+"@electron/osx-sign@^1.0.5":
+ version "1.3.2"
+ resolved "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.2.tgz"
+ integrity sha512-KqVlm9WMWq19lBpCXQoThC/Koaiji2zotUDYwZDaZlZZym+FXY9mQW8wN6sUQ93nkVc42f3TQ1S/XN9S1kjM5Q==
+ dependencies:
+ compare-version "^0.1.2"
+ debug "^4.3.4"
+ fs-extra "^10.0.0"
+ isbinaryfile "^4.0.8"
+ minimist "^1.2.6"
+ plist "^3.0.5"
+
+"@electron/universal@^1.3.2":
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz"
+ integrity sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==
+ dependencies:
+ "@electron/asar" "^3.2.1"
+ "@malept/cross-spawn-promise" "^1.1.0"
+ debug "^4.3.1"
+ dir-compare "^3.0.0"
+ fs-extra "^9.0.1"
+ minimatch "^3.0.4"
+ plist "^3.0.4"
+
+"@emotion/is-prop-valid@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz"
+ integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
+ dependencies:
+ "@emotion/memoize" "^0.8.1"
+
+"@emotion/memoize@^0.8.1":
+ version "0.8.1"
+ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz"
+ integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
+
+"@emotion/unitless@0.8.1":
+ version "0.8.1"
+ resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz"
+ integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
+
+"@esbuild/linux-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz"
+ integrity sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==
+
+"@iconify-json/ic@^1.2.2":
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/@iconify-json/ic/-/ic-1.2.2.tgz"
+ integrity sha512-QmjwS3lYiOmVWgTCEOTFyGODaR/+689+ajep/VsrCcsUN0Gdle5PmIcibDsdmRyrOsW/E77G41UUijdbjQUofw==
+ dependencies:
+ "@iconify/types" "*"
+
+"@iconify/icons-ic@^1.2.13":
+ version "1.2.13"
+ resolved "https://registry.npmjs.org/@iconify/icons-ic/-/icons-ic-1.2.13.tgz"
+ integrity sha512-TphrhwOvgd7CTmUhz6jgXF+SPnMtvTm03bZsAYbny2kwq4zlkhr9e16YEyPGMvKhjtTqNooA3iZ9Wa+pZ8moXQ==
+ dependencies:
+ "@iconify/types" "*"
+
+"@iconify/icons-ph@^1.2.5":
+ version "1.2.5"
+ resolved "https://registry.npmjs.org/@iconify/icons-ph/-/icons-ph-1.2.5.tgz"
+ integrity sha512-ewrnpL0u3MAP0wLDnXb/39W9W71ObyY8DNig6rYj7NEwcndWWlrGHt1RxEGYE3eqILl/IUcNIQQF1CXZAhwr/w==
+ dependencies:
+ "@iconify/types" "*"
+
+"@iconify/react@^5.2.0":
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/@iconify/react/-/react-5.2.0.tgz"
+ integrity sha512-7Sdjrqq3fkkQNks9SY3adGC37NQTHsBJL2PRKlQd455PoDi9s+Es9AUTY+vGLFOYs5yO9w9yCE42pmxCwG26WA==
+ dependencies:
+ "@iconify/types" "^2.0.0"
+
+"@iconify/types@*", "@iconify/types@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz"
+ integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
+
+"@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.5"
+ resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz"
+ integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
+ dependencies:
+ "@jridgewell/set-array" "^1.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/set-array@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz"
+ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+ version "0.3.25"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
+ integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@malept/cross-spawn-promise@^1.1.0":
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz"
+ integrity sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==
+ dependencies:
+ cross-spawn "^7.0.1"
+
+"@reduxjs/toolkit@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz"
+ integrity sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==
+ dependencies:
+ immer "^10.0.3"
+ redux "^5.0.1"
+ redux-thunk "^3.1.0"
+ reselect "^5.1.0"
+
+"@rollup/rollup-linux-x64-gnu@4.40.1":
+ version "4.40.1"
+ resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz"
+ integrity sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==
+
+"@rollup/rollup-linux-x64-musl@4.40.1":
+ version "4.40.1"
+ resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz"
+ integrity sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==
+
+"@sindresorhus/is@^4.0.0":
+ version "4.6.0"
+ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz"
+ integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+
+"@szmarczak/http-timer@^4.0.5":
+ version "4.0.6"
+ resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz"
+ integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
+ dependencies:
+ defer-to-connect "^2.0.0"
+
+"@types/babel__core@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
+ integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz"
+ integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.4.4"
+ resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz"
+ integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*":
+ version "7.20.7"
+ resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz"
+ integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==
+ dependencies:
+ "@babel/types" "^7.20.7"
+
+"@types/cacheable-request@^6.0.1":
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz"
+ integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==
+ dependencies:
+ "@types/http-cache-semantics" "*"
+ "@types/keyv" "^3.1.4"
+ "@types/node" "*"
+ "@types/responselike" "^1.0.0"
+
+"@types/d3-array@*":
+ version "3.2.1"
+ resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz"
+ integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==
+
+"@types/d3-axis@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz"
+ integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-brush@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz"
+ integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-chord@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz"
+ integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==
+
+"@types/d3-color@*":
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz"
+ integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
+
+"@types/d3-contour@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz"
+ integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/geojson" "*"
+
+"@types/d3-delaunay@*":
+ version "6.0.4"
+ resolved "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz"
+ integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==
+
+"@types/d3-dispatch@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz"
+ integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==
+
+"@types/d3-drag@*", "@types/d3-drag@^3.0.7":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz"
+ integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-dsv@*":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz"
+ integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==
+
+"@types/d3-ease@*":
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz"
+ integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
+
+"@types/d3-fetch@*":
+ version "3.0.7"
+ resolved "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz"
+ integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==
+ dependencies:
+ "@types/d3-dsv" "*"
+
+"@types/d3-force@*":
+ version "3.0.10"
+ resolved "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz"
+ integrity sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==
+
+"@types/d3-format@*":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz"
+ integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==
+
+"@types/d3-geo@*":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz"
+ integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==
+ dependencies:
+ "@types/geojson" "*"
+
+"@types/d3-hierarchy@*":
+ version "3.1.7"
+ resolved "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz"
+ integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==
+
+"@types/d3-interpolate@*":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz"
+ integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
+ dependencies:
+ "@types/d3-color" "*"
+
+"@types/d3-path@*":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz"
+ integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==
+
+"@types/d3-polygon@*":
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz"
+ integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==
+
+"@types/d3-quadtree@*":
+ version "3.0.6"
+ resolved "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz"
+ integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==
+
+"@types/d3-random@*":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz"
+ integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==
+
+"@types/d3-scale-chromatic@*":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz"
+ integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==
+
+"@types/d3-scale@*":
+ version "4.0.8"
+ resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz"
+ integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==
+ dependencies:
+ "@types/d3-time" "*"
+
+"@types/d3-selection@*", "@types/d3-selection@^3.0.10":
+ version "3.0.11"
+ resolved "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz"
+ integrity sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==
+
+"@types/d3-shape@*":
+ version "3.1.6"
+ resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz"
+ integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==
+ dependencies:
+ "@types/d3-path" "*"
+
+"@types/d3-time-format@*":
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz"
+ integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==
+
+"@types/d3-time@*":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz"
+ integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==
+
+"@types/d3-timer@*":
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz"
+ integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
+
+"@types/d3-transition@*", "@types/d3-transition@^3.0.8":
+ version "3.0.9"
+ resolved "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz"
+ integrity sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==
+ dependencies:
+ "@types/d3-selection" "*"
+
+"@types/d3-zoom@*", "@types/d3-zoom@^3.0.8":
+ version "3.0.8"
+ resolved "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz"
+ integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==
+ dependencies:
+ "@types/d3-interpolate" "*"
+ "@types/d3-selection" "*"
+
+"@types/d3@^7.4.0":
+ version "7.4.3"
+ resolved "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz"
+ integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==
+ dependencies:
+ "@types/d3-array" "*"
+ "@types/d3-axis" "*"
+ "@types/d3-brush" "*"
+ "@types/d3-chord" "*"
+ "@types/d3-color" "*"
+ "@types/d3-contour" "*"
+ "@types/d3-delaunay" "*"
+ "@types/d3-dispatch" "*"
+ "@types/d3-drag" "*"
+ "@types/d3-dsv" "*"
+ "@types/d3-ease" "*"
+ "@types/d3-fetch" "*"
+ "@types/d3-force" "*"
+ "@types/d3-format" "*"
+ "@types/d3-geo" "*"
+ "@types/d3-hierarchy" "*"
+ "@types/d3-interpolate" "*"
+ "@types/d3-path" "*"
+ "@types/d3-polygon" "*"
+ "@types/d3-quadtree" "*"
+ "@types/d3-random" "*"
+ "@types/d3-scale" "*"
+ "@types/d3-scale-chromatic" "*"
+ "@types/d3-selection" "*"
+ "@types/d3-shape" "*"
+ "@types/d3-time" "*"
+ "@types/d3-time-format" "*"
+ "@types/d3-timer" "*"
+ "@types/d3-transition" "*"
+ "@types/d3-zoom" "*"
+
+"@types/estree@1.0.7":
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz"
+ integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
+
+"@types/geojson@*":
+ version "7946.0.14"
+ resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz"
+ integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==
+
+"@types/http-cache-semantics@*":
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz"
+ integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
+
+"@types/keyv@^3.1.4":
+ version "3.1.4"
+ resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz"
+ integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/node@*", "@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^22.7.7":
+ version "22.19.1"
+ resolved "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz"
+ integrity sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==
+ dependencies:
+ undici-types "~6.21.0"
+
+"@types/prop-types@*":
+ version "15.7.13"
+ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz"
+ integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
+
+"@types/react-dom@^18.0.11":
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz"
+ integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^18.0.28", "@types/react@^18.2.25 || ^19", "@types/react@>=16.8", "@types/react@>=18.0.0":
+ version "18.3.12"
+ resolved "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz"
+ integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^3.0.2"
+
+"@types/resize-observer-browser@^0.1.7":
+ version "0.1.11"
+ resolved "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz"
+ integrity sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==
+
+"@types/responselike@^1.0.0":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz"
+ integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/stylis@4.2.5":
+ version "4.2.5"
+ resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz"
+ integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==
+
+"@types/use-sync-external-store@^0.0.6":
+ version "0.0.6"
+ resolved "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz"
+ integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==
+
+"@types/yauzl@^2.9.1":
+ version "2.10.3"
+ resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz"
+ integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==
+ dependencies:
+ "@types/node" "*"
+
+"@vitejs/plugin-basic-ssl@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz"
+ integrity sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==
+
+"@vitejs/plugin-react@^4.4.1":
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz"
+ integrity sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==
+ dependencies:
+ "@babel/core" "^7.26.10"
+ "@babel/plugin-transform-react-jsx-self" "^7.25.9"
+ "@babel/plugin-transform-react-jsx-source" "^7.25.9"
+ "@types/babel__core" "^7.20.5"
+ react-refresh "^0.17.0"
+
+"@xmldom/xmldom@^0.8.8":
+ version "0.8.10"
+ resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz"
+ integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
+
+"@xyflow/react@^12.3.5":
+ version "12.3.5"
+ resolved "https://registry.npmjs.org/@xyflow/react/-/react-12.3.5.tgz"
+ integrity sha512-wAYqpicdrVo1rxCu0X3M9s3YIF45Agqfabw0IBryTGqjWvr2NyfciI8gIP4MB+NKpWWN5kxZ9tiZ9u8lwC7iAg==
+ dependencies:
+ "@xyflow/system" "0.0.46"
+ classcat "^5.0.3"
+ zustand "^4.4.0"
+
+"@xyflow/system@0.0.46":
+ version "0.0.46"
+ resolved "https://registry.npmjs.org/@xyflow/system/-/system-0.0.46.tgz"
+ integrity sha512-bmFXvboVdiydIFZmDCjrbBCYgB0d5pYdkcZPWbAxGmhMRUZ+kW3CksYgYxWabrw51rwpWitLEadvLrivG0mVfA==
+ dependencies:
+ "@types/d3-drag" "^3.0.7"
+ "@types/d3-selection" "^3.0.10"
+ "@types/d3-transition" "^3.0.8"
+ "@types/d3-zoom" "^3.0.8"
+ d3-drag "^3.0.0"
+ d3-selection "^3.0.0"
+ d3-zoom "^3.0.0"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
+author-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz"
+ integrity sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==
+
+axios@^1.7.9:
+ version "1.13.2"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz"
+ integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.4"
+ proxy-from-env "^1.1.0"
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base64-js@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+bluebird@^3.1.1:
+ version "3.7.2"
+ resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz"
+ integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
+boolean@^3.0.1:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz"
+ integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
+
+brace-expansion@^1.1.7:
+ version "1.1.12"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz"
+ integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+browserslist@^4.24.0, "browserslist@>= 4.21.0":
+ version "4.24.4"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz"
+ integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+ dependencies:
+ caniuse-lite "^1.0.30001688"
+ electron-to-chromium "^1.5.73"
+ node-releases "^2.0.19"
+ update-browserslist-db "^1.1.1"
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"
+ integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
+
+buffer-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz"
+ integrity sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==
+
+cacheable-lookup@^5.0.3:
+ version "5.0.4"
+ resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz"
+ integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
+
+cacheable-request@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz"
+ integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^4.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^6.0.1"
+ responselike "^2.0.0"
+
+call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
+camelize@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz"
+ integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
+
+caniuse-lite@^1.0.30001688:
+ version "1.0.30001716"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz"
+ integrity sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==
+
+classcat@^5.0.3:
+ version "5.0.5"
+ resolved "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz"
+ integrity sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==
+
+clone-response@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz"
+ integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
+ dependencies:
+ mimic-response "^1.0.0"
+
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
+compare-version@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz"
+ integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+cookie@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz"
+ integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==
+
+cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz"
+ integrity sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw==
+ dependencies:
+ "@malept/cross-spawn-promise" "^1.1.0"
+ is-wsl "^2.2.0"
+ which "^2.0.2"
+
+cross-spawn@^7.0.1:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+css-color-keywords@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz"
+ integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
+
+css-to-react-native@3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz"
+ integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
+ dependencies:
+ camelize "^1.0.0"
+ css-color-keywords "^1.0.0"
+ postcss-value-parser "^4.0.2"
+
+csstype@^3.0.2, csstype@3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+"d3-color@1 - 3":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+"d3-dispatch@1 - 3":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz"
+ integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
+
+d3-drag@^3.0.0, "d3-drag@2 - 3":
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz"
+ integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-selection "3"
+
+"d3-ease@1 - 3":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz"
+ integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
+"d3-interpolate@1 - 3":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
+ integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+ dependencies:
+ d3-color "1 - 3"
+
+d3-selection@^3.0.0, "d3-selection@2 - 3", d3-selection@3:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz"
+ integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
+
+"d3-timer@1 - 3":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz"
+ integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+
+"d3-transition@2 - 3":
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz"
+ integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+ dependencies:
+ d3-color "1 - 3"
+ d3-dispatch "1 - 3"
+ d3-ease "1 - 3"
+ d3-interpolate "1 - 3"
+ d3-timer "1 - 3"
+
+d3-zoom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz"
+ integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-drag "2 - 3"
+ d3-interpolate "1 - 3"
+ d3-selection "2 - 3"
+ d3-transition "2 - 3"
+
+debug@^2.2.0:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4:
+ version "4.3.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
+defer-to-connect@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz"
+ integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
+define-data-property@^1.0.1:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+dir-compare@^3.0.0:
+ version "3.3.0"
+ resolved "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz"
+ integrity sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==
+ dependencies:
+ buffer-equal "^1.0.0"
+ minimatch "^3.0.4"
+
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
+electron-packager@^17.1.2:
+ version "17.1.2"
+ resolved "https://registry.npmjs.org/electron-packager/-/electron-packager-17.1.2.tgz"
+ integrity sha512-XofXdikjYI7MVBcnXeoOvRR+yFFFHOLs3J7PF5KYQweigtgLshcH4W660PsvHr4lYZ03JBpLyEcUB8DzHZ+BNw==
+ dependencies:
+ "@electron/asar" "^3.2.1"
+ "@electron/get" "^2.0.0"
+ "@electron/notarize" "^1.2.3"
+ "@electron/osx-sign" "^1.0.5"
+ "@electron/universal" "^1.3.2"
+ cross-spawn-windows-exe "^1.2.0"
+ debug "^4.0.1"
+ extract-zip "^2.0.0"
+ filenamify "^4.1.0"
+ fs-extra "^11.1.0"
+ galactus "^1.0.0"
+ get-package-info "^1.0.0"
+ junk "^3.1.0"
+ parse-author "^2.0.0"
+ plist "^3.0.0"
+ rcedit "^3.0.1"
+ resolve "^1.1.6"
+ semver "^7.1.3"
+ yargs-parser "^21.1.1"
+
+electron-to-chromium@^1.5.73:
+ version "1.5.145"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz"
+ integrity sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw==
+
+electron@^39.2.3:
+ version "39.2.3"
+ resolved "https://registry.npmjs.org/electron/-/electron-39.2.3.tgz"
+ integrity sha512-j7k7/bj3cNA29ty54FzEMRUoqirE+RBQPhPFP+XDuM93a1l2WcDPiYumxKWz+iKcXxBJLFdMIAlvtLTB/RfCkg==
+ dependencies:
+ "@electron/get" "^2.0.0"
+ "@types/node" "^22.7.7"
+ extract-zip "^2.0.1"
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+env-paths@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz"
+ integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
+
+error-ex@^1.2.0:
+ version "1.3.2"
+ resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz"
+ integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-define-property@^1.0.0, es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
+ integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ dependencies:
+ es-errors "^1.3.0"
+
+es-set-tostringtag@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
+ integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
+ dependencies:
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+es6-error@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz"
+ integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
+
+esbuild-wasm@^0.17.12:
+ version "0.17.19"
+ resolved "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.19.tgz"
+ integrity sha512-X9UQEMJMZXwlGCfqcBmJ1jEa+KrLfd+gCBypO/TSzo5hZvbVwFqpxj1YCuX54ptTF75wxmrgorR4RL40AKtLVg==
+
+esbuild@^0.25.0:
+ version "0.25.3"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz"
+ integrity sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.25.3"
+ "@esbuild/android-arm" "0.25.3"
+ "@esbuild/android-arm64" "0.25.3"
+ "@esbuild/android-x64" "0.25.3"
+ "@esbuild/darwin-arm64" "0.25.3"
+ "@esbuild/darwin-x64" "0.25.3"
+ "@esbuild/freebsd-arm64" "0.25.3"
+ "@esbuild/freebsd-x64" "0.25.3"
+ "@esbuild/linux-arm" "0.25.3"
+ "@esbuild/linux-arm64" "0.25.3"
+ "@esbuild/linux-ia32" "0.25.3"
+ "@esbuild/linux-loong64" "0.25.3"
+ "@esbuild/linux-mips64el" "0.25.3"
+ "@esbuild/linux-ppc64" "0.25.3"
+ "@esbuild/linux-riscv64" "0.25.3"
+ "@esbuild/linux-s390x" "0.25.3"
+ "@esbuild/linux-x64" "0.25.3"
+ "@esbuild/netbsd-arm64" "0.25.3"
+ "@esbuild/netbsd-x64" "0.25.3"
+ "@esbuild/openbsd-arm64" "0.25.3"
+ "@esbuild/openbsd-x64" "0.25.3"
+ "@esbuild/sunos-x64" "0.25.3"
+ "@esbuild/win32-arm64" "0.25.3"
+ "@esbuild/win32-ia32" "0.25.3"
+ "@esbuild/win32-x64" "0.25.3"
+
+escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-string-regexp@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+extract-zip@^2.0.0, extract-zip@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz"
+ integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
+ dependencies:
+ debug "^4.1.1"
+ get-stream "^5.1.0"
+ yauzl "^2.10.0"
+ optionalDependencies:
+ "@types/yauzl" "^2.9.1"
+
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz"
+ integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
+ dependencies:
+ pend "~1.2.0"
+
+fdir@^6.4.4:
+ version "6.4.4"
+ resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz"
+ integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==
+
+filename-reserved-regex@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz"
+ integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==
+
+filenamify@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz"
+ integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==
+ dependencies:
+ filename-reserved-regex "^2.0.0"
+ strip-outer "^1.0.1"
+ trim-repeated "^1.0.0"
+
+find-up@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz"
+ integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==
+ dependencies:
+ locate-path "^2.0.0"
+
+flora-colossus@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz"
+ integrity sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==
+ dependencies:
+ debug "^4.3.4"
+ fs-extra "^10.1.0"
+
+follow-redirects@^1.15.6:
+ version "1.15.9"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
+form-data@^4.0.4:
+ version "4.0.5"
+ resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz"
+ integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ es-set-tostringtag "^2.1.0"
+ hasown "^2.0.2"
+ mime-types "^2.1.12"
+
+fs-extra@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^11.1.0:
+ version "11.3.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz"
+ integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz"
+ integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs-extra@^9.0.1:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+galactus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz"
+ integrity sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==
+ dependencies:
+ debug "^4.3.4"
+ flora-colossus "^2.0.0"
+ fs-extra "^10.1.0"
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-intrinsic@^1.2.6:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ function-bind "^1.1.2"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-package-info@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz"
+ integrity sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==
+ dependencies:
+ bluebird "^3.1.1"
+ debug "^2.2.0"
+ lodash.get "^4.0.0"
+ read-pkg-up "^2.0.0"
+
+get-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+glob@^7.1.6:
+ version "7.2.3"
+ resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+global-agent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz"
+ integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
+ dependencies:
+ boolean "^3.0.1"
+ es6-error "^4.1.1"
+ matcher "^3.0.0"
+ roarr "^2.15.3"
+ semver "^7.3.2"
+ serialize-error "^7.0.1"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globalthis@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz"
+ integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==
+ dependencies:
+ define-properties "^1.2.1"
+ gopd "^1.0.1"
+
+gopd@^1.0.1, gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
+got@^11.8.5:
+ version "11.8.6"
+ resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz"
+ integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==
+ dependencies:
+ "@sindresorhus/is" "^4.0.0"
+ "@szmarczak/http-timer" "^4.0.5"
+ "@types/cacheable-request" "^6.0.1"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^5.0.3"
+ cacheable-request "^7.0.2"
+ decompress-response "^6.0.0"
+ http2-wrapper "^1.0.0-beta.5.2"
+ lowercase-keys "^2.0.0"
+ p-cancelable "^2.0.0"
+ responselike "^2.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+ version "4.2.11"
+ resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+has-property-descriptors@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-symbols@^1.0.3, has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+hosted-git-info@^2.1.4:
+ version "2.8.9"
+ resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+
+http-cache-semantics@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
+
+http2-wrapper@^1.0.0-beta.5.2:
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz"
+ integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.0.0"
+
+immer@^10.0.3, immer@>=9.0.6:
+ version "10.1.1"
+ resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz"
+ integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-core-module@^2.16.0:
+ version "2.16.1"
+ resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
+ integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+ dependencies:
+ hasown "^2.0.2"
+
+is-docker@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+isbinaryfile@^4.0.8:
+ version "4.0.10"
+ resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz"
+ integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz"
+ integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-stringify-safe@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
+ integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
+
+json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz"
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+junk@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz"
+ integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
+
+keyv@^4.0.0:
+ version "4.5.4"
+ resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz"
+ integrity sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz"
+ integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lodash.get@^4.0.0:
+ version "4.4.2"
+ resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz"
+ integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+
+loose-envify@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+matcher@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz"
+ integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
+ dependencies:
+ escape-string-regexp "^4.0.0"
+
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mimic-response@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
+minimatch@^3.0.4, minimatch@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+nanoid@^3.3.7, nanoid@^3.3.8:
+ version "3.3.8"
+ resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz"
+ integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
+
+node-releases@^2.0.19:
+ version "2.0.19"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz"
+ integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
+
+normalize-package-data@^2.3.2:
+ version "2.5.0"
+ resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz"
+ integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
+ dependencies:
+ hosted-git-info "^2.1.4"
+ resolve "^1.10.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+p-cancelable@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz"
+ integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
+
+p-limit@^1.1.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz"
+ integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
+ dependencies:
+ p-try "^1.0.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz"
+ integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==
+ dependencies:
+ p-limit "^1.1.0"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz"
+ integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==
+
+parse-author@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz"
+ integrity sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==
+ dependencies:
+ author-regex "^1.0.0"
+
+parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
+ integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==
+ dependencies:
+ error-ex "^1.2.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
+ integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz"
+ integrity sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==
+ dependencies:
+ pify "^2.0.0"
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
+ integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
+
+picocolors@^1.0.0, picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+"picomatch@^3 || ^4", picomatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz"
+ integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
+
+pify@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
+ integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
+
+plist@^3.0.0, plist@^3.0.4, plist@^3.0.5:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz"
+ integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==
+ dependencies:
+ "@xmldom/xmldom" "^0.8.8"
+ base64-js "^1.5.1"
+ xmlbuilder "^15.1.1"
+
+postcss-value-parser@^4.0.2:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss@^8.5.3:
+ version "8.5.3"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz"
+ integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
+ dependencies:
+ nanoid "^3.3.8"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+postcss@8.4.38:
+ version "8.4.38"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz"
+ integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.0"
+ source-map-js "^1.2.0"
+
+progress@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
+ integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+pump@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz"
+ integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
+rcedit@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/rcedit/-/rcedit-3.1.0.tgz"
+ integrity sha512-WRlRdY1qZbu1L11DklT07KuHfRk42l0NFFJdaExELEu4fEQ982bP5Z6OWGPj/wLLIuKRQDCxZJGAwoFsxhZhNA==
+ dependencies:
+ cross-spawn-windows-exe "^1.1.0"
+
+react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=17, react-dom@>=18, "react-dom@16 || 17 || 18":
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
+ integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.2"
+
+react-flow-renderer@^10.3.17:
+ version "10.3.17"
+ resolved "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.17.tgz"
+ integrity sha512-bywiqVErlh5kCDqw3x0an5Ur3mT9j9CwJsDwmhmz4i1IgYM1a0SPqqEhClvjX+s5pU4nHjmVaGXWK96pwsiGcQ==
+ dependencies:
+ "@babel/runtime" "^7.18.9"
+ "@types/d3" "^7.4.0"
+ "@types/resize-observer-browser" "^0.1.7"
+ classcat "^5.0.3"
+ d3-drag "^3.0.0"
+ d3-selection "^3.0.0"
+ d3-zoom "^3.0.0"
+ zustand "^3.7.2"
+
+"react-redux@^7.2.1 || ^8.1.3 || ^9.0.0", react-redux@^9.2.0:
+ version "9.2.0"
+ resolved "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz"
+ integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==
+ dependencies:
+ "@types/use-sync-external-store" "^0.0.6"
+ use-sync-external-store "^1.4.0"
+
+react-refresh@^0.17.0:
+ version "0.17.0"
+ resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz"
+ integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
+
+react-router-dom@^7.0.2:
+ version "7.5.2"
+ resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.2.tgz"
+ integrity sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==
+ dependencies:
+ react-router "7.5.2"
+
+react-router@7.5.2:
+ version "7.5.2"
+ resolved "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz"
+ integrity sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==
+ dependencies:
+ cookie "^1.0.1"
+ set-cookie-parser "^2.6.0"
+ turbo-stream "2.4.0"
+
+"react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.9.0 || ^17.0.0 || ^18 || ^19", "react@^18.0 || ^19", react@^18.3.1, "react@>= 16.8.0", react@>=16, react@>=16.8, react@>=17, react@>=18, react@>=18.0.0, "react@16 || 17 || 18":
+ version "18.3.1"
+ resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
+ integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz"
+ integrity sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz"
+ integrity sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
+redux-thunk@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz"
+ integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
+
+redux@^5.0.0, redux@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
+ integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
+
+regenerator-runtime@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
+ integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
+
+reselect@^5.1.0:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz"
+ integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
+
+resolve-alpn@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz"
+ integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
+resolve@^1.1.6, resolve@^1.10.0:
+ version "1.22.10"
+ resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz"
+ integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
+ dependencies:
+ is-core-module "^2.16.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+responselike@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz"
+ integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==
+ dependencies:
+ lowercase-keys "^2.0.0"
+
+roarr@^2.15.3:
+ version "2.15.4"
+ resolved "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz"
+ integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
+ dependencies:
+ boolean "^3.0.1"
+ detect-node "^2.0.4"
+ globalthis "^1.0.1"
+ json-stringify-safe "^5.0.1"
+ semver-compare "^1.0.0"
+ sprintf-js "^1.1.2"
+
+rollup@^4.34.9:
+ version "4.40.1"
+ resolved "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz"
+ integrity sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==
+ dependencies:
+ "@types/estree" "1.0.7"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.40.1"
+ "@rollup/rollup-android-arm64" "4.40.1"
+ "@rollup/rollup-darwin-arm64" "4.40.1"
+ "@rollup/rollup-darwin-x64" "4.40.1"
+ "@rollup/rollup-freebsd-arm64" "4.40.1"
+ "@rollup/rollup-freebsd-x64" "4.40.1"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.40.1"
+ "@rollup/rollup-linux-arm-musleabihf" "4.40.1"
+ "@rollup/rollup-linux-arm64-gnu" "4.40.1"
+ "@rollup/rollup-linux-arm64-musl" "4.40.1"
+ "@rollup/rollup-linux-loongarch64-gnu" "4.40.1"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.40.1"
+ "@rollup/rollup-linux-riscv64-gnu" "4.40.1"
+ "@rollup/rollup-linux-riscv64-musl" "4.40.1"
+ "@rollup/rollup-linux-s390x-gnu" "4.40.1"
+ "@rollup/rollup-linux-x64-gnu" "4.40.1"
+ "@rollup/rollup-linux-x64-musl" "4.40.1"
+ "@rollup/rollup-win32-arm64-msvc" "4.40.1"
+ "@rollup/rollup-win32-ia32-msvc" "4.40.1"
+ "@rollup/rollup-win32-x64-msvc" "4.40.1"
+ fsevents "~2.3.2"
+
+scheduler@^0.23.2:
+ version "0.23.2"
+ resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
+ integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+semver-compare@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
+ integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
+
+semver@^6.2.0, semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.1.3:
+ version "7.6.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+semver@^7.3.2:
+ version "7.6.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+"semver@2 || 3 || 4 || 5":
+ version "5.7.2"
+ resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
+ integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
+
+serialize-error@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz"
+ integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
+ dependencies:
+ type-fest "^0.13.1"
+
+set-cookie-parser@^2.6.0:
+ version "2.7.1"
+ resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz"
+ integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==
+
+shallowequal@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz"
+ integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+source-map-js@^1.2.0, source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+spdx-correct@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz"
+ integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==
+ dependencies:
+ spdx-expression-parse "^3.0.0"
+ spdx-license-ids "^3.0.0"
+
+spdx-exceptions@^2.1.0:
+ version "2.5.0"
+ resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz"
+ integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==
+
+spdx-expression-parse@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz"
+ integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
+ dependencies:
+ spdx-exceptions "^2.1.0"
+ spdx-license-ids "^3.0.0"
+
+spdx-license-ids@^3.0.0:
+ version "3.0.21"
+ resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz"
+ integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==
+
+sprintf-js@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz"
+ integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz"
+ integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
+
+strip-outer@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz"
+ integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==
+ dependencies:
+ escape-string-regexp "^1.0.2"
+
+styled-components@^6.1.13:
+ version "6.1.13"
+ resolved "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz"
+ integrity sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==
+ dependencies:
+ "@emotion/is-prop-valid" "1.2.2"
+ "@emotion/unitless" "0.8.1"
+ "@types/stylis" "4.2.5"
+ css-to-react-native "3.2.0"
+ csstype "3.1.3"
+ postcss "8.4.38"
+ shallowequal "1.1.0"
+ stylis "4.3.2"
+ tslib "2.6.2"
+
+stylis@4.3.2:
+ version "4.3.2"
+ resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz"
+ integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==
+
+sumchecker@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz"
+ integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
+ dependencies:
+ debug "^4.1.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tinyglobby@^0.2.13:
+ version "0.2.13"
+ resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz"
+ integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
+ dependencies:
+ fdir "^6.4.4"
+ picomatch "^4.0.2"
+
+trim-repeated@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz"
+ integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==
+ dependencies:
+ escape-string-regexp "^1.0.2"
+
+tslib@2.6.2:
+ version "2.6.2"
+ resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
+ integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
+turbo-stream@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz"
+ integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==
+
+type-fest@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz"
+ integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
+
+typescript@^4.9.5:
+ version "4.9.5"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
+ integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+
+undici-types@~6.21.0:
+ version "6.21.0"
+ resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz"
+ integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+universalify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
+update-browserslist-db@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz"
+ integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
+use-sync-external-store@^1.2.2, use-sync-external-store@>=1.2.0:
+ version "1.2.2"
+ resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz"
+ integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
+
+use-sync-external-store@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz"
+ integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz"
+ integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
+ dependencies:
+ spdx-correct "^3.0.0"
+ spdx-expression-parse "^3.0.0"
+
+"vite@^4.2.0 || ^5.0.0 || ^6.0.0", vite@^6.0.0, vite@^6.3.4:
+ version "6.4.1"
+ resolved "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz"
+ integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==
+ dependencies:
+ esbuild "^0.25.0"
+ fdir "^6.4.4"
+ picomatch "^4.0.2"
+ postcss "^8.5.3"
+ rollup "^4.34.9"
+ tinyglobby "^0.2.13"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+which@^2.0.1, which@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+xmlbuilder@^15.1.1:
+ version "15.1.1"
+ resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz"
+ integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yauzl@^2.10.0:
+ version "2.10.0"
+ resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
+ integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"
+
+zustand@^3.7.2:
+ version "3.7.2"
+ resolved "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz"
+ integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==
+
+zustand@^4.4.0:
+ version "4.5.6"
+ resolved "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz"
+ integrity sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==
+ dependencies:
+ use-sync-external-store "^1.2.2"
+
+zustand@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz"
+ integrity sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==
diff --git a/SRV/.gitignore b/SRV/.gitignore
index 9cb3d81..20dffb4 100644
--- a/SRV/.gitignore
+++ b/SRV/.gitignore
@@ -6,4 +6,5 @@ __pycache__
.wheels
.vendor
.python3-deb
-storage/devices/
\ No newline at end of file
+storage/subnets/
+storage/wifi/
\ No newline at end of file
diff --git a/SRV/api/router.py b/SRV/api/router.py
index 9c1061f..a168d73 100644
--- a/SRV/api/router.py
+++ b/SRV/api/router.py
@@ -1,3 +1,27 @@
+"""
+Маршрутизация API-версии v1 для GONETS.
+
+Модуль агрегирует и монтирует роутеры отдельных доменных областей под общим
+корневым роутером. Каждый подключенный модуль предоставляет набор эндпоинтов
+со своим префиксом и тегами для группировки в документации FastAPI.
+
+Структура маршрутов:
+ - `/ufw` (UFW): управление правилами и состоянием Uncomplicated Firewall.
+ - `/ip` (IP): проверки и применение IP-конфигураций для сетевых профилей.
+ - `/network` (Network): получение и визуализация состояния интерфейсов,
+ управление позициями UI-контейнеров.
+ - `/auth` (Auth): регистрация, аутентификация (JWT), сессии.
+ - `/subnet` (Subnet): сканирование подсетей, заметки к устройствам,
+ взаимодействие с Wi-Fi (скан/подключение/отключение/статус).
+ - `WebSocket`: обработчики взаимодействия по WS (без префикса, путь внутри
+ модуля), периодическая трансляция сетевых данных.
+ - `/settings` (Settings): сервисные операции над локальными JSON-хранилищами
+ (очистка данных подсетей/wi-fi и пр.).
+
+Назначение:
+ - Централизованная точка подключения доменных роутеров к приложению.
+ - Единообразие тегов и префиксов для автогенерации схемы и документации.
+"""
from fastapi import APIRouter
from .v1 import ufw, websocket, ip, network, auth, subnet, settings
diff --git a/SRV/api/v1/auth.py b/SRV/api/v1/auth.py
index a3fbe2a..7238ff3 100644
--- a/SRV/api/v1/auth.py
+++ b/SRV/api/v1/auth.py
@@ -3,6 +3,8 @@ from fastapi.security import OAuth2PasswordBearer
from services.auth_service import AuthService
from models.auth import UserCreate, UserLogin, UserResponse, Token
from storage.json_storage import JsonStorage
+from utils.i18n import get_translations, _
+from babel.support import Translations
import logging
import os
@@ -13,9 +15,8 @@ router = APIRouter()
auth_service = AuthService()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
-
@router.post("/register", response_model=UserResponse)
-async def register(user: UserCreate):
+async def register(user: UserCreate, translations: Translations = Depends(get_translations)):
"""Регистрирует нового пользователя.
Создаёт нового пользователя на основе предоставленных данных и возвращает
@@ -23,6 +24,7 @@ async def register(user: UserCreate):
Args:
user (UserCreate): Объект с данными нового пользователя (username, password, is_admin).
+ translations: Объект переводов для текущего языка.
Returns:
UserResponse: Объект с данными зарегистрированного пользователя (id, username, is_admin).
@@ -38,12 +40,11 @@ async def register(user: UserCreate):
except HTTPException as e:
raise e
except Exception as e:
- logger.error(f"Ошибка в register: {type(e).__name__} - {str(e)}")
- raise HTTPException(status_code=500, detail="Не удалось зарегистрировать пользователя")
-
+ logger.error(f"Error in register: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось зарегистрировать пользователя"))
@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)):
"""Авторизует пользователя и создаёт сессию.
Проверяет учетные данные пользователя и возвращает токен и session_id для
@@ -52,6 +53,7 @@ async def login(user: UserLogin, request: Request):
Args:
user (UserLogin): Объект с данными для входа (username, password).
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+ translations: Объект переводов для текущего языка.
Returns:
Token: Объект с access_token и session_id для авторизации.
@@ -62,21 +64,18 @@ async def login(user: UserLogin, request: Request):
"""
try:
client_id = request.client.host
- logger.debug(f"Попытка авторизации для client_id {client_id}")
+ logger.debug(f"Попытка входа в систему клиента {client_id}")
result = auth_service.login(user, client_id)
- logger.info(
- f"Успешная авторизация для client_id {client_id}, session_id: {result['session_id']}"
- )
+ logger.info(f"Успешная авторизация для клиента {client_id}, Идентификатор сессии: {result['session_id']}")
return result
except HTTPException as e:
raise e
except Exception as e:
- logger.error(f"Ошибка в login: {type(e).__name__} - {str(e)}")
- raise HTTPException(status_code=500, detail="Не удалось авторизовать пользователя")
-
+ logger.error(f"Ошибка при авторизации: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось авторизовать пользователя"))
@router.get("/me")
-async def get_current_user(request: Request):
+async def get_current_user(request: Request, translations: Translations = Depends(get_translations)):
"""Возвращает данные текущего пользователя и токен.
Проверяет активную сессию по client_id и возвращает информацию о пользователе
@@ -84,6 +83,7 @@ async def get_current_user(request: Request):
Args:
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с ключами "user" (UserResponse), "access_token" (str) и
@@ -95,29 +95,28 @@ async def get_current_user(request: Request):
"""
try:
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)
return {
"user": result["user"],
"access_token": result["access_token"],
- "token_type": "bearer",
+ "token_type": translations.gettext("bearer"),
}
except HTTPException as e:
raise e
except Exception as e:
- logger.error(
- f"Ошибка в get_current_user: {type(e).__name__} - {str(e)}")
- raise HTTPException(status_code=500, detail="Не удалось получить данные пользователя")
-
+ logger.error(f"Ошибка при get_current_user: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить данные пользователя"))
@router.get("/logout")
-async def logout(request: Request):
+async def logout(request: Request, translations: Translations = Depends(get_translations)):
"""Выход из системы, удаление сессии.
Удаляет активную сессию пользователя по client_id.
Args:
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+ translations: Объект переводов для текущего языка.
Returns:
dict: Пустой словарь или сообщение об успешном выходе.
@@ -128,17 +127,16 @@ async def logout(request: Request):
"""
try:
client_id = request.client.host
- logger.debug(f"Запрос logout для client_id {client_id}")
+ logger.debug(f"Запрос на выход из системы для клиента {client_id}")
return auth_service.logout(client_id)
except HTTPException as e:
raise e
except Exception as e:
- logger.error(f"Ошибка в logout: {type(e).__name__} - {str(e)}")
- raise HTTPException(status_code=500, detail="Не удалось выйти из системы")
-
+ logger.error(f"Не удалось выйти из системы: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось выйти из системы"))
@router.get("/check_json")
-async def check_users_file():
+async def check_users_file(translations: Translations = Depends(get_translations)):
"""Проверяет, существует ли файл users.json и содержит ли он пользователей.
Возвращает информацию о состоянии файла и наличии пользователей.
@@ -161,31 +159,25 @@ async def check_users_file():
return {
"exists": False,
"has_users": False,
- "message": "Файл users.json не существует",
+ "message": translations.gettext("Файл users.json не существует"),
}
data = storage._read_data()
has_users = len(data.get("users", [])) > 0
if has_users:
- logger.info(
- f"Файл {file_path} существует и содержит пользователей"
- )
+ logger.info(f"Файл {file_path} существует и содержит пользователей")
return {
"exists": True,
"has_users": True,
- "message": "Файл users.json существует и содержит пользователей",
+ "message": translations.gettext("Файл users.json существует и содержит пользователей"),
}
else:
- logger.info(
- f"Файл {file_path} существует, но не содержит пользователей"
- )
+ logger.info(f"Файл {file_path} существует, но не содержит пользователей")
return {
"exists": True,
"has_users": False,
- "message": "Файл users.json существует, но не содержит пользователей",
+ "message": translations.gettext("Файл users.json существует, но не содержит пользователей"),
}
except Exception as e:
- logger.error(
- f"Ошибка при проверке файла users.json: {type(e).__name__} - {str(e)}"
- )
- raise HTTPException(status_code=500, detail="Ошибка при проверке файла")
\ No newline at end of file
+ logger.error(f"Ошибка при проверке файла: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Ошибка при проверке файла"))
diff --git a/SRV/api/v1/ip.py b/SRV/api/v1/ip.py
index 387ed54..4fdefe0 100644
--- a/SRV/api/v1/ip.py
+++ b/SRV/api/v1/ip.py
@@ -1,13 +1,14 @@
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 utils.i18n import get_translations
+from babel.support import Translations
router = APIRouter()
ip_service = IPService()
-
@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)):
"""Проверяет доступность IP-адреса перед его применением.
Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
@@ -16,6 +17,7 @@ async def check_ip_config(data: Dict[str, Any] = Body(...)):
Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая
"ipv4_updates" с полем "ipv4.address-data" для нового IP-адреса.
+ translations: Объект переводов для текущего языка.
Returns:
Dict[str, Any]: Словарь с ключами:
@@ -26,16 +28,16 @@ async def check_ip_config(data: Dict[str, Any] = Body(...)):
HTTPException: Если произошла внутренняя ошибка (status_code=500).
"""
try:
- result = await ip_service.check_ip_config(data)
+ result = await ip_service.check_ip_config(data, translations)
return result
except Exception as e:
raise HTTPException(
status_code=500,
- detail=f"Ошибка проверки IP конфигурации: {str(e)}")
-
+ detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
+ )
@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)):
"""Применяет проверенные IP-настройки к указанному сетевому профилю.
Обновляет настройки соединения через NetworkManager, отключает и
@@ -44,6 +46,7 @@ async def apply_ip_config(data: Dict[str, Any] = Body(...)):
Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая
"connection_id" и "ipv4_updates" с обновлениями IP-настроек.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с ключами:
@@ -55,19 +58,23 @@ async def apply_ip_config(data: Dict[str, Any] = Body(...)):
произошла внутренняя ошибка (status_code=500).
"""
try:
- await ip_service.apply_ip_config(data)
+ await ip_service.apply_ip_config(data, translations)
return {
"success": True,
- "message": f"Настройки сетевого профиля для {data['connection_id']} применены",
+ "message": translations.gettext("Настройки сетевого профиля для {connection_id} применены").format(
+ connection_id=data["connection_id"]
+ ),
}
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:
- 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")
-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)):
"""Применяет проверенные IP-настройки для отката сетевого профиля.
Используется для сохранения и отката настроек сетевого профиля к предыдущему
@@ -76,6 +83,7 @@ async def save_ip_config(data: Dict[str, Any] = Body(...)):
Args:
data (Dict[str, Any]): Словарь с данными конфигурации, включая
"connection_id" и "ipv4_updates" с обновлениями IP-настроек.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с ключами:
@@ -87,12 +95,17 @@ async def save_ip_config(data: Dict[str, Any] = Body(...)):
произошла внутренняя ошибка (status_code=500).
"""
try:
- await ip_service.apply_ip_config(data)
+ await ip_service.apply_ip_config(data, translations)
return {
"success": True,
- "message": f"Настройки сетевого профиля для {data['connection_id']} применены",
+ "message": translations.gettext("Настройки сетевого профиля для {connection_id} применены").format(
+ connection_id=data["connection_id"]
+ ),
}
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:
- raise HTTPException(status_code=500, detail=f"Ошибка проверки IP конфигурации: {str(e)}")
\ No newline at end of file
+ raise HTTPException(
+ status_code=500,
+ detail=translations.gettext("Ошибка проверки IP конфигурации: {error}").format(error=str(e))
+ )
diff --git a/SRV/api/v1/network.py b/SRV/api/v1/network.py
index 904a14d..395bc78 100644
--- a/SRV/api/v1/network.py
+++ b/SRV/api/v1/network.py
@@ -1,7 +1,6 @@
-from fastapi import APIRouter, Body, HTTPException
+from fastapi import APIRouter, Body, HTTPException, Depends
import logging
from typing import Dict, Tuple, List, Union
-
from services.network_service import NetworkService
from models.network import (
NetworkInterfacesContainer,
@@ -10,6 +9,8 @@ from models.network import (
NetworkInterface,
WiFiDevice,
)
+from utils.i18n import get_translations, _, _load_translations
+from babel.support import Translations
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@@ -17,7 +18,6 @@ logger = logging.getLogger(__name__)
router = APIRouter()
network_service = NetworkService()
-
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) -> Tuple[int, int, bool]:
"""Получает текущие координаты и состояние указанного контейнера.
Args:
container_name (str): Имя контейнера ("activation" или "deactivation").
+ translations: Объект переводов для текущего языка.
Returns:
Tuple[int, int, bool]: Кортеж с координатами (x, y) и состоянием (isOpen).
@@ -58,7 +59,7 @@ class ContainerPositionManager:
"""
container = self.positions.get(container_name)
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"]
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"
self.positions[container_name] = {"x": x, "y": y, "isOpen": isOpen}
-
container_manager = ContainerPositionManager()
-
-async def get_network_data() -> Dict[str, Dict]:
+async def get_network_data(translations: Translations = Depends(get_translations)) -> Dict[str, Dict]:
"""Возвращает текущие сетевые данные в виде словаря.
Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
и возвращает их в формате Activation и Deactivation с учётом позиций и состояний
контейнеров.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Returns:
Dict[str, Dict]: Словарь с ключами "activation" и "deactivation", содержащими
данные о соответствующих контейнерах.
@@ -94,14 +96,14 @@ async def get_network_data() -> Dict[str, Dict]:
Raises:
HTTPException: Если произошла ошибка при получении данных (status_code=500).
"""
- logger.debug("Вызов get_network_data()")
+ logger.debug("Calling get_network_data()")
try:
network_data = await network_service.get_network_interfaces_container()
activation_interfaces, deactivation_interfaces = _split_interfaces(network_data.interfaces)
- activation_x, activation_y, activation_is_open = (container_manager.get_position_and_state("activation"))
- deactivation_x, deactivation_y, deactivation_is_open = (container_manager.get_position_and_state("deactivation"))
+ 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", translations)
activation = Activation(
id=1,
@@ -123,9 +125,8 @@ async def get_network_data() -> Dict[str, Dict]:
"deactivation": deactivation.model_dump(),
}
except Exception as e:
- logger.error(f"Ошибка в get_network_data: {e}")
- raise HTTPException(status_code=500, detail="Не удалось получить сетевые данные")
-
+ logger.error(f"Error in get_network_data: {e}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить сетевые данные"))
def _split_interfaces(
interfaces: List[Union[NetworkInterface, WiFiDevice]],
@@ -148,9 +149,8 @@ def _split_interfaces(
deactivation_interfaces.append(interface)
return activation_interfaces, deactivation_interfaces
-
@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)):
"""Обновляет позицию и состояние контейнера.
Принимает данные о новом положении и состоянии контейнера и обновляет их
@@ -162,6 +162,7 @@ async def update_container_position(data: Dict[str, Union[int, bool]] = Body(...
- "container_x" (int): Новая координата X.
- "container_y" (int): Новая координата Y.
- "isOpen" (bool): Новое состояние контейнера (открыт/закрыт).
+ translations: Объект переводов для текущего языка.
Returns:
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):
raise ValueError(
- "Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen"
+ translations.gettext("Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen")
)
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_y, int)
or not isinstance(is_open, bool)):
- raise ValueError("Неверный тип данных в запросе")
+ raise ValueError(translations.gettext("Неверный тип данных в запросе"))
container_manager.update_position(container_id, container_x, container_y, is_open)
return {
"success": True,
- "message": "Позиция и состояние контейнера обновлены"
+ "message": translations.gettext("Позиция и состояние контейнера обновлены")
}
except Exception as e:
- logger.error(f"Ошибка обновления позиции контейнера: {e}")
- raise HTTPException(status_code=400, detail=f"Не удалось обновить положение контейнера: {str(e)}")
-
+ logger.error(f"Error updating container position: {e}")
+ raise HTTPException(status_code=400, detail=translations.gettext("Не удалось обновить положение контейнера: {error}").format(error=str(e)))
@router.get("/get_hostname")
-async def get_hostname():
+async def get_hostname(translations: Translations = Depends(get_translations)):
"""Возвращает имя хоста системы.
Выполняет запрос к сервису для получения имени хоста.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Returns:
dict: Словарь с ключом "hostname" и значением имени хоста.
@@ -218,5 +221,5 @@ async def get_hostname():
hostname_info = await network_service.get_hostname()
return hostname_info
except Exception as e:
- logger.error(f"Ошибка в get_hostname: {e}")
- raise HTTPException(status_code=500, detail="Не удалось получить имя хоста")
\ No newline at end of file
+ logger.error(f"Error in get_hostname: {e}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось получить имя хоста"))
diff --git a/SRV/api/v1/settings.py b/SRV/api/v1/settings.py
index ea370bf..27773bc 100644
--- a/SRV/api/v1/settings.py
+++ b/SRV/api/v1/settings.py
@@ -1,15 +1,31 @@
-from fastapi import APIRouter, HTTPException
+import os
+import logging
+import subprocess
+from typing import Optional
+from fastapi import APIRouter, HTTPException, Depends, Response, Request, Body
+from starlette.responses import FileResponse
from services.settings_service import SettingsService
+from utils.i18n import get_translations
+from babel.support import Translations
+from models.subnet import SubnetScanRequest # Предполагается, что модель определена
+
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
router = APIRouter()
settings_service = SettingsService()
@router.post("/clear_subnet")
-def clear_subnet():
+def clear_subnet(subnet: Optional[str] = None, translations: Translations = Depends(get_translations)):
"""Очищает данные узлов подсети, сохранённые в devices.json.
Удаляет все записи об узлах подсети, оставляя файл пустым.
+ Args:
+ subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+ Если None, очищает все подсети.
+ translations: Объект переводов для текущего языка.
+
Returns:
dict: Словарь с сообщением об успешной очистке.
@@ -17,18 +33,27 @@ def clear_subnet():
HTTPException: Если произошла ошибка очистки (status_code=500).
"""
try:
- success = settings_service.clear_subnet()
+ success = settings_service.clear_subnet(subnet)
if success:
- return {"message": "Данные узлов подсети успешно очищены"}
+ if subnet:
+ return {"message": translations.gettext("Данные узлов подсети {subnet} успешно очищены").format(subnet=subnet)}
+ else:
+ return {"message": translations.gettext("Данные всех подсетей успешно очищены")}
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить данные узлов подсети"))
except Exception as e:
- 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")
-def clear_wifi():
+def clear_wifi(translations: Translations = Depends(get_translations)):
"""Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
Удаляет все записи о точках доступа, оставляя файл пустым.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Returns:
dict: Словарь с сообщением об успешной очистке.
@@ -38,16 +63,22 @@ def clear_wifi():
try:
success = settings_service.clear_wifi()
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:
- 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")
-def clear_all_data():
+def clear_all_data(translations: Translations = Depends(get_translations)):
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
Удаляет все записи из devices.json и wifi.json.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Returns:
dict: Словарь с сообщением об успешной очистке.
@@ -57,6 +88,122 @@ def clear_all_data():
try:
success = settings_service.clear_all_data()
if success:
- return {"message": "Все сетевые данные успешно очищены"}
+ return {"message": translations.gettext("Все сетевые данные успешно очищены")}
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось очистить все сетевые данные"))
except Exception as e:
- raise HTTPException(status_code=500, detail=f"Ошибка очистки: {str(e)}")
\ No newline at end of file
+ logger.error(f"Error clearing all data: {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Ошибка очистки: {error}").format(error=str(e)))
+
+WIFI_PATH = "storage/wifi/wifi.json"
+
+@router.post("/export_devices", response_class=FileResponse)
+async def export_devices(request: SubnetScanRequest = Body(...), translations: Translations = Depends(get_translations)):
+ """
+ Экспортирует файл с данными устройств в формате JSON для указанной подсети.
+
+ Args:
+ request (SubnetScanRequest): Объект с полем subnet в формате CIDR.
+ translations: Объект переводов для текущего языка.
+
+ Returns:
+ FileResponse: Файл .json для скачивания.
+
+ Raises:
+ HTTPException: Если файл не найден (status_code=404).
+ """
+ subnet = request.subnet
+ file_path = f"storage/subnets/{subnet.replace('/', '_')}.json"
+ if not os.path.exists(file_path):
+ logger.warning(f"Export file not found: {file_path}")
+ raise HTTPException(status_code=404, detail=translations.gettext("Файл для подсети {subnet} не найден").format(subnet=subnet))
+ logger.info(f"Exporting file: {file_path}")
+ return FileResponse(
+ path=file_path,
+ filename=f"{subnet.replace('/', '_')}.json",
+ media_type="application/json"
+ )
+
+@router.get("/export_wifi", response_class=FileResponse)
+async def export_wifi(translations: Translations = Depends(get_translations)):
+ """
+ Экспортирует файл с данными устройств в формате JSON.
+
+ Args:
+ translations: Объект переводов для текущего языка.
+
+ Returns:
+ FileResponse: Файл wifi.json для скачивания.
+
+ Raises:
+ HTTPException: Если файл не найден (status_code=404).
+ """
+ if not os.path.exists(WIFI_PATH):
+ logger.warning(f"Export file not found: {WIFI_PATH}")
+ raise HTTPException(status_code=404, detail=translations.gettext("Файл не найден"))
+ logger.info(f"Exporting file: {WIFI_PATH}")
+ return FileResponse(
+ path=WIFI_PATH,
+ filename="wifi.json",
+ media_type="application/json"
+ )
+
+@router.post("/set_language")
+def set_language(locale: str, response: Response, translations: Translations = Depends(get_translations)):
+ """
+ Устанавливает куку locale для смены языка пользователя.
+
+ Args:
+ locale (str): Используемый язык.
+ response (Response): Запрос.
+ translations: Объект переводов текущего языка.
+
+ Returns:
+ dict: Сообщение об успешной установке языка.
+ """
+ lc_messages_path = os.path.join(os.path.dirname(__file__), '..', '..', 'locales', locale, 'LC_MESSAGES')
+ if not os.path.isdir(lc_messages_path):
+ raise HTTPException(
+ status_code=400,
+ detail=translations.gettext("Язык '{locale}' не поддерживается.").format(locale=locale)
+ )
+ has_mo_or_po = any(f.endswith('.mo') or f.endswith('.po') for f in os.listdir(lc_messages_path))
+ if not has_mo_or_po:
+ raise HTTPException(
+ status_code=400,
+ detail=translations.gettext("Язык '{locale}' не поддерживается (нет файлов переводов).").format(locale=locale)
+ )
+ response.set_cookie(key="locale", value=locale, path="/", httponly=True)
+ return {"message": translations.gettext("Язык успешно установлен: {locale}").format(locale=locale)}
+
+@router.get("/get_system_language")
+async def get_system_language(request: Request, translations: Translations = Depends(get_translations)):
+ """Выводит текущий язык, установленный в системе на основе locale.
+
+ Выполняет команду 'locale' в системе и извлекает параметры LANG
+
+ Args:
+ request (Request): Объект запроса для доступа к параметрам.
+ translations: Объект переводов текущего языка.
+
+ Returns:
+ dict: Словарь с текущим языком системы.
+
+ Raises:
+ HTTPException: Если не удалось извлечь системный язык (status_code=500).
+ """
+ try:
+ result = subprocess.run(['locale'], capture_output=True, text=True, check=True)
+ output = result.stdout
+ lang = next((line.split('=')[1].strip() for line in output.splitlines() if line.startswith('LANG=')), None)
+ if lang:
+ return {"language": lang}
+ else:
+ logger.warning(f"No locale found: {output}")
+ return {"language": 'unknown'}
+ except subprocess.CalledProcessError as e:
+ logger.error(f"Error executing locale command: {e}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Не удалось определить язык в системе"))
+ except Exception as e:
+ logger.error(f"Unexpected error getting system language: {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Ошибка при определении языка в системе {error}").format(error=str(e)))
diff --git a/SRV/api/v1/subnet.py b/SRV/api/v1/subnet.py
index 43e4d1c..0116d2a 100644
--- a/SRV/api/v1/subnet.py
+++ b/SRV/api/v1/subnet.py
@@ -1,25 +1,29 @@
-from fastapi import APIRouter, HTTPException
-from typing import List, Dict
-from models.subnet import SubnetScanResult
+from fastapi import APIRouter, HTTPException, Body, Depends
+from models.subnet import SubnetScanResult, SubnetScanRequest
from models.wifi import WifiScanResult
from models.request import NoteRequest, WifiConnectRequest, WifiDisconnectRequest
-from services.subnet_service import SubnetService
+from services.subnet_service import SubnetService, get_subnet_service
from services.wifi_service import WifiService
from services.connect_service import WifiConnectionService
+from utils.i18n import get_translations, _
+from babel.support import Translations
+import logging
router = APIRouter()
-@router.get("/devices", response_model=SubnetScanResult)
-def scan_subnet(subnet: str = "192.168.1.0/24", ports: List[int] = None):
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+@router.post("/devices", response_model=SubnetScanResult)
+def scan_subnet(request: SubnetScanRequest = Body(...), subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
"""Сканирует локальную подсеть и возвращает данные об устройствах.
- Выполняет сканирование указанной подсети для обнаружения активных устройств
- с возможностью проверки указанных портов.
+ Выполняет сканирование указанной подсети для обнаружения активных устройств.
Args:
- subnet (str, optional): Адрес подсети в формате CIDR (например, "192.168.1.0/24").
- По умолчанию "192.168.1.0/24".
- ports (List[int], optional): Список портов для сканирования. По умолчанию None.
+ request (SubnetScanRequest): Объект с полем subnet.
+ subnet_service: Сервис сканирования подсети.
+ translations: Объект переводов для текущего языка.
Returns:
SubnetScanResult: Объект с результатами сканирования (устройства и их данные).
@@ -28,20 +32,26 @@ def scan_subnet(subnet: str = "192.168.1.0/24", ports: List[int] = None):
HTTPException: Если произошла ошибка сканирования (status_code=500).
"""
try:
- subnet_service = SubnetService()
- result = subnet_service.scan_subnet(subnet, ports)
+ # Если subnet пустая строка, определяем автоматически
+ subnet = request.subnet.strip() if request.subnet else None
+ logger.info(f"Requested subnet scan for: {subnet or 'auto-detected'}")
+ result = subnet_service.scan_subnet(subnet)
+ if not result.devices and result.error:
+ logger.warning(f"No devices found in subnet {subnet or 'auto-detected'}")
return result
except Exception as e:
- raise HTTPException(status_code=500, detail=f"Ошибка сканирования: {str(e)}")
+ logger.error(f"Error in scan_subnet: {str(e)}")
+ raise HTTPException(status_code=500, detail=translations.gettext("Ошибка сканирования подсети: {error}").format(error=str(e)))
@router.get("/wifi", response_model=WifiScanResult)
-def scan_wifi(timeout: int = 30):
+def scan_wifi(timeout: int = 30, translations: Translations = Depends(get_translations)):
"""Сканирует Wi-Fi точки доступа и возвращает данные о них.
Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
Args:
timeout (int, optional): Таймаут сканирования в секундах. По умолчанию 30.
+ translations: Объект переводов для текущего языка.
Returns:
WifiScanResult: Объект с результатами сканирования (список Wi-Fi сетей).
@@ -54,10 +64,10 @@ def scan_wifi(timeout: int = 30):
result = wifi_service.scan_wifi(timeout=timeout)
return result
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")
-def add_note(mac: str, request: NoteRequest):
+def add_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
"""Добавляет заметку для устройства по MAC-адресу.
Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
@@ -65,6 +75,8 @@ def add_note(mac: str, request: NoteRequest):
Args:
mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для текста заметки.
+ subnet_service: Сервис сканирования подсети.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном добавлении заметки.
@@ -74,21 +86,22 @@ def add_note(mac: str, request: NoteRequest):
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
"""
try:
- subnet_service = SubnetService()
- success, error_message = subnet_service.add_note(mac, request.note)
+ success, result = subnet_service.add_note(mac, request.note, translations)
if not success:
- if "не найдено" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "уже существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка '{request.note}' добавлена для устройства {mac}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
+ elif result[0] == "note_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} уже существует").format(mac=mac))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка '{note}' добавлена для устройства {mac}").format(note=request.note, mac=mac)}
except HTTPException as e:
raise e
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")
-def update_note(mac: str, request: NoteRequest):
+def update_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
"""Обновляет заметку для устройства по MAC-адресу.
Изменяет существующую заметку, привязанную к устройству.
@@ -96,6 +109,8 @@ def update_note(mac: str, request: NoteRequest):
Args:
mac (str): MAC-адрес устройства.
request (NoteRequest): Объект с полем "note" для нового текста заметки.
+ subnet_service: Сервис сканирования подсети.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном обновлении заметки.
@@ -105,27 +120,30 @@ def update_note(mac: str, request: NoteRequest):
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
"""
try:
- subnet_service = SubnetService()
- success, error_message = subnet_service.update_note(mac, request.note)
+ success, result = subnet_service.update_note(mac, request.note, translations)
if not success:
- if "не найдено" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "не существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка '{request.note}' обновлена для устройства {mac}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
+ elif result[0] == "note_not_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка '{note}' обновлена для устройства {mac}").format(note=request.note, mac=mac)}
except HTTPException as e:
raise e
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")
-def delete_note(mac: str):
+def delete_note(mac: str, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(get_translations)):
"""Удаляет заметку для устройства по MAC-адресу.
Удаляет привязанную заметку для указанного устройства.
Args:
mac (str): MAC-адрес устройства.
+ subnet_service: Сервис сканирования подсети.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном удалении заметки.
@@ -135,21 +153,22 @@ def delete_note(mac: str):
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
"""
try:
- subnet_service = SubnetService()
- success, error_message = subnet_service.delete_note(mac)
+ success, result = subnet_service.delete_note(mac, translations)
if not success:
- if "не найдено" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "не существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка удалена для устройства {mac}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Устройство с MAC {mac} не найдено").format(mac=mac))
+ elif result[0] == "note_not_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для устройства с MAC {mac} не существует").format(mac=mac))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка удалена для устройства {mac}").format(mac=mac)}
except HTTPException as e:
raise e
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")
-def add_wifi_note(ssid: str, request: NoteRequest):
+def add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations)):
"""Добавляет заметку для сети Wi-Fi по SSID.
Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
@@ -157,6 +176,7 @@ def add_wifi_note(ssid: str, request: NoteRequest):
Args:
ssid (str): Идентификатор Wi-Fi сети.
request (NoteRequest): Объект с полем "note" для текста заметки.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном добавлении заметки.
@@ -167,20 +187,22 @@ def add_wifi_note(ssid: str, request: NoteRequest):
"""
try:
wifi_service = WifiService()
- success, error_message = wifi_service.add_note(ssid, request.note)
+ success, result = wifi_service.add_note(ssid, request.note)
if not success:
- if "не найдена" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "уже существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка '{request.note}' добавлена для сети с SSID {ssid}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
+ elif result[0] == "note_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} уже существует").format(ssid=ssid))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка '{note}' добавлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)}
except HTTPException as e:
raise e
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")
-def update_wifi_note(ssid: str, request: NoteRequest):
+def update_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(get_translations)):
"""Обновляет заметку для сети Wi-Fi по SSID.
Изменяет существующую заметку, привязанную к Wi-Fi сети.
@@ -188,6 +210,7 @@ def update_wifi_note(ssid: str, request: NoteRequest):
Args:
ssid (str): Идентификатор Wi-Fi сети.
request (NoteRequest): Объект с полем "note" для нового текста заметки.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном обновлении заметки.
@@ -198,26 +221,29 @@ def update_wifi_note(ssid: str, request: NoteRequest):
"""
try:
wifi_service = WifiService()
- success, error_message = wifi_service.update_note(ssid, request.note)
+ success, result = wifi_service.update_note(ssid, request.note)
if not success:
- if "не найдена" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "не существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка '{request.note}' обновлена для сети с SSID {ssid}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
+ elif result[0] == "note_not_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка '{note}' обновлена для сети с SSID {ssid}").format(note=request.note, ssid=ssid)}
except HTTPException as e:
raise e
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")
-def delete_wifi_note(ssid: str):
+def delete_wifi_note(ssid: str, translations: Translations = Depends(get_translations)):
"""Удаляет заметку для сети Wi-Fi по SSID.
Удаляет привязанную заметку для указанной Wi-Fi сети.
Args:
ssid (str): Идентификатор Wi-Fi сети.
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном удалении заметки.
@@ -228,20 +254,22 @@ def delete_wifi_note(ssid: str):
"""
try:
wifi_service = WifiService()
- success, error_message = wifi_service.delete_note(ssid)
+ success, result = wifi_service.delete_note(ssid)
if not success:
- if "не найдена" in error_message:
- raise HTTPException(status_code=404, detail=error_message)
- elif "не существует" in error_message:
- raise HTTPException(status_code=400, detail=error_message)
- return {"message": f"Заметка удалена для сети с SSID {ssid}"}
+ if result[0] == "not_found":
+ raise HTTPException(status_code=404, detail=translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid))
+ elif result[0] == "note_not_exists":
+ raise HTTPException(status_code=400, detail=translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid))
+ else:
+ raise HTTPException(status_code=500, detail=translations.gettext("Внутренняя ошибка сервера: {error}").format(error=result[1]["error"]))
+ return {"message": translations.gettext("Заметка удалена для сети с SSID {ssid}").format(ssid=ssid)}
except HTTPException as e:
raise e
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")
-def connect_to_wifi(request: WifiConnectRequest):
+def connect_to_wifi(request: WifiConnectRequest, translations: Translations = Depends(get_translations)):
"""Подключается к Wi-Fi сети по SSID и паролю.
Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
@@ -249,6 +277,7 @@ def connect_to_wifi(request: WifiConnectRequest):
Args:
request (WifiConnectRequest): Объект с полями "ssid" и "password".
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном подключении.
@@ -256,20 +285,26 @@ def connect_to_wifi(request: WifiConnectRequest):
Raises:
HTTPException: Если подключение не удалось (status_code=400).
"""
- wifi_service = WifiConnectionService()
- success, message = wifi_service.connect(request.ssid, request.password)
- if not success:
- raise HTTPException(status_code=400, detail=message)
- return {"message": f"Успешно подключено к {request.ssid}"}
+ try:
+ wifi_service = WifiConnectionService(translations)
+ success, message = wifi_service.connect(request.ssid, request.password)
+ if not success:
+ raise HTTPException(status_code=400, detail=translations.gettext(message))
+ 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")
-def disconnect_from_wifi(request: WifiDisconnectRequest):
+def disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translations = Depends(get_translations)):
"""Отключается от Wi-Fi сети по указанному SSID.
Выполняет отключение от указанной Wi-Fi сети.
Args:
request (WifiDisconnectRequest): Объект с полем "ssid".
+ translations: Объект переводов для текущего языка.
Returns:
dict: Словарь с сообщением об успешном отключении.
@@ -277,18 +312,26 @@ def disconnect_from_wifi(request: WifiDisconnectRequest):
Raises:
HTTPException: Если отключение не удалось (status_code=400).
"""
- wifi_service = WifiConnectionService()
- success, message = wifi_service.disconnect(request.ssid)
- if not success:
- raise HTTPException(status_code=400, detail=message)
- return {"message": message or f"Успешно отключено от {request.ssid}"}
+ try:
+ wifi_service = WifiConnectionService(translations)
+ success, message = wifi_service.disconnect(request.ssid)
+ if not success:
+ raise HTTPException(status_code=400, detail=translations.gettext(message))
+ 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")
-def get_wifi_status():
+def get_wifi_status(translations: Translations = Depends(get_translations)):
"""Проверяет статус текущего подключения к Wi-Fi.
Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Returns:
dict: Словарь с ключами:
- "status" (str): Состояние ("connected" или другое).
@@ -297,8 +340,13 @@ def get_wifi_status():
Raises:
HTTPException: Если проверка статуса не удалась (status_code=400).
"""
- wifi_service = WifiConnectionService()
- success, message = wifi_service.get_status()
- if not success:
- raise HTTPException(status_code=400, detail=message)
- return {"status": "connected", "ssid": message}
\ No newline at end of file
+ try:
+ wifi_service = WifiConnectionService(translations)
+ success, message = wifi_service.get_status()
+ if not success:
+ raise HTTPException(status_code=400, detail=translations.gettext(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)))
diff --git a/SRV/api/v1/ufw.py b/SRV/api/v1/ufw.py
index 067ca9e..06176af 100644
--- a/SRV/api/v1/ufw.py
+++ b/SRV/api/v1/ufw.py
@@ -9,14 +9,29 @@ router = APIRouter()
class RuleNumbers(BaseModel):
+ """Модель запроса на удаление UFW правил.
+
+ Атрибуты:
+
+ - ``numbers`` (List[int]): Список номеров правил для удаления.
+ """
numbers: List[int] = Field(
..., description="Список номеров правил для удаления")
@router.get("/rules", response_model=UFWRulesResponse)
async def get_ufw_rules():
- """
- Получает все правила UFW с помощью сервиса UFWService и возвращает их в формате JSON.
+ """Возвращает все правила UFW.
+
+ Осуществляет обращение к сервису `UFWService` для чтения текущего списка
+ правил брандмауэра и возвращает результат в сериализованном виде для клиента.
+
+ Returns:
+ UFWRulesResponse: Структура с коллекцией правил и сопутствующей
+ информацией о состоянии.
+
+ Raises:
+ HTTPException: В случае ошибок взаимодействия с UFW (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -27,8 +42,13 @@ async def get_ufw_rules():
@router.get("/status", response_model=UFWStatus)
async def get_ufw_status():
- """
- Получает текущий статус UFW.
+ """Возвращает текущий статус UFW.
+
+ Returns:
+ UFWStatus: Объект со сведениями о включенности UFW и деталями статуса.
+
+ Raises:
+ HTTPException: Если не удалось получить статус (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -39,8 +59,13 @@ async def get_ufw_status():
@router.post("/enable")
async def enable_ufw():
- """
- Включает UFW.
+ """Включает UFW.
+
+ Returns:
+ dict: Словарь с ключами `status` и `message` при успешном выполнении.
+
+ Raises:
+ HTTPException: Если включение завершилось неуспешно (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -56,8 +81,13 @@ async def enable_ufw():
@router.post("/disable")
async def disable_ufw():
- """
- Выключает UFW.
+ """Выключает UFW.
+
+ Returns:
+ dict: Словарь с ключами `status` и `message` при успешном выполнении.
+
+ Raises:
+ HTTPException: Если выключение завершилось неуспешно (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -73,8 +103,19 @@ async def disable_ufw():
@router.post("/rules/in")
async def add_ufw_in_rules(rules: List[UFWRule]):
- """
- Добавляет входящие (in) правила UFW.
+ """Добавляет входящие (in) правила UFW.
+
+ Args:
+ rules (List[UFWRule]): Список правил, определяющих направления,
+ порты/диапазоны/протоколы и действия (allow/deny/reject).
+
+ Returns:
+ dict: Итог выполнения с количеством успешно добавленных правил и деталями
+ по каждому правилу.
+
+ Raises:
+ HTTPException: Если UFW отключен (status_code=400) или при внутренних
+ ошибках (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -108,8 +149,17 @@ async def add_ufw_in_rules(rules: List[UFWRule]):
@router.post("/rules/out")
async def add_ufw_out_rules(rules: List[UFWRule]):
- """
- Добавляет исходящие (out) правила UFW.
+ """Добавляет исходящие (out) правила UFW.
+
+ Args:
+ rules (List[UFWRule]): Список правил исходящего трафика для добавления.
+
+ Returns:
+ dict: Объект статуса с сообщением об успешном добавлении.
+
+ Raises:
+ HTTPException: Если UFW отключен (status_code=400) или при внутренних
+ ошибках (status_code=500).
"""
ufw_service = UFWService()
try:
@@ -137,8 +187,18 @@ async def add_ufw_out_rules(rules: List[UFWRule]):
@router.delete("/rules")
async def delete_ufw_rules(rule_numbers: RuleNumbers):
- """
- Удаляет правила UFW по их номерам.
+ """Удаляет правила UFW по их номерам.
+
+ Args:
+ rule_numbers (RuleNumbers): Номера правил, подлежащих удалению (как в
+ выводе `ufw status numbered`).
+
+ Returns:
+ dict: Информация об общем количестве удаляемых правил, числе успешных и
+ возможной ошибке.
+
+ Raises:
+ HTTPException: При ошибках удаления (status_code=500).
"""
ufw_service = UFWService()
try:
diff --git a/SRV/api/v1/websocket.py b/SRV/api/v1/websocket.py
index 271b898..d122645 100644
--- a/SRV/api/v1/websocket.py
+++ b/SRV/api/v1/websocket.py
@@ -2,8 +2,10 @@ import asyncio
import logging
from typing import Set
-from fastapi import APIRouter, WebSocket, WebSocketDisconnect
+from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from starlette.websockets import WebSocketState
+from utils.i18n import get_translations_ws, _, _load_translations
+from babel.support import Translations
from .network import update_container_position, get_network_data
@@ -70,15 +72,13 @@ class WebSocketManager:
except WebSocketDisconnect:
self.disconnect(connection)
except Exception as e:
- logger.error(f"Сообщение об ошибке трансляции: {e}")
+ logger.error(f"Broadcast error: {e}")
self.disconnect(connection)
-
manager = WebSocketManager()
-
@router.websocket("/api/v1")
-async def websocket_endpoint(websocket: WebSocket):
+async def websocket_endpoint(websocket: WebSocket, translations: Translations = Depends(get_translations_ws)):
"""Обработчик WebSocket-соединения.
Управляет соединением клиента, принимает данные для обновления позиций
@@ -86,6 +86,7 @@ async def websocket_endpoint(websocket: WebSocket):
Args:
websocket (WebSocket): Объект WebSocket для обработки соединения.
+ translations: Объект переводов для текущего языка.
Raises:
WebSocketDisconnect: Если клиент отключился.
@@ -95,26 +96,26 @@ async def websocket_endpoint(websocket: WebSocket):
try:
while True:
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"]):
- await update_container_position(data)
+ await update_container_position(data, translations=translations)
- network_data = await get_network_data()
- logger.info(f"Текущие сетевые данные: {network_data}")
+ # Для внутреннего вызова используем переводы из WebSocket зависимостии
+ network_data = await get_network_data(translations=translations)
+ logger.info(f"Current network data: {network_data}")
await manager.broadcast(network_data)
await asyncio.sleep(2)
except WebSocketDisconnect:
- logger.info("Клиент отключен")
+ logger.info("Client disconnected")
except Exception as e:
- logger.error(f"Ошибка в websocket_endpoint:{e}")
+ logger.error(f"Error in websocket_endpoint: {e}")
finally:
manager.disconnect(websocket)
if websocket.client_state != WebSocketState.DISCONNECTED:
await websocket.close()
-
@router.on_event("startup")
async def startup_event():
"""Запуск таска для трансляции сетевых данных при старте приложения.
@@ -126,27 +127,28 @@ async def startup_event():
"""
asyncio.create_task(broadcast_data())
-
-async def broadcast_data():
+async def broadcast_data(translations: Translations = None):
"""Таск для периодической трансляции сетевых данных.
Выполняет сканирование сети и отправляет данные всем подключённым клиентам
с интервалом в 2 секунды.
+ Args:
+ translations: Объект переводов для текущего языка.
+
Raises:
Exception: Если произошла ошибка при получении данных или трансляции.
"""
+ translations = translations or _load_translations("ru")
while True:
try:
- network_data = await get_network_data()
+ network_data = await get_network_data(translations=translations)
if "activation" not in network_data:
- logger.error(
- "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
- )
+ logger.error(translations.gettext("Ошибка: Ключ 'activation' отсутствует в полученных данных сети"))
await asyncio.sleep(2)
continue
await manager.broadcast(network_data)
except Exception as e:
- logger.error(f"Ошибка в broadcast_data:{e}")
- await asyncio.sleep(2)
\ No newline at end of file
+ logger.error(f"Error in broadcast_data: {e}")
+ await asyncio.sleep(2)
diff --git a/SRV/babel.cfg b/SRV/babel.cfg
new file mode 100644
index 0000000..6a7d140
--- /dev/null
+++ b/SRV/babel.cfg
@@ -0,0 +1,3 @@
+[python: **.py]
+encoding = utf-8
+extract_messages = _
diff --git a/SRV/config/__init__.py b/SRV/config/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/SRV/config/cors.py b/SRV/config/cors.py
index 46a11a7..cb06f19 100644
--- a/SRV/config/cors.py
+++ b/SRV/config/cors.py
@@ -1,3 +1,20 @@
+"""
+Конфигурация CORS для FastAPI-приложения GONETS.
+
+Модуль инкапсулирует добавление middleware `CORSMiddleware` со списком
+разрешённых источников (origins), характерных для локальной разработки и
+типовых портов фронтенда (например, Vite/React на 5173/5174, dev-серверы).
+
+Использование:
+ cors_config = CORSMiddlewareConfig(app)
+ cors_config.add_cors_middleware()
+
+Поведение:
+ - Разрешены все методы (`allow_methods=["*"]`) и заголовки (`allow_headers=["*"]`).
+ - Разрешены креденшлы (`allow_credentials=True`) для работы с куками (локаль и др.).
+ - При необходимости список `origins` можно расширить или параметризовать
+ на основе конфигурационного файла окружения.
+"""
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
diff --git a/SRV/config/init_config.py b/SRV/config/init_config.py
index 3a54adb..647c7d2 100644
--- a/SRV/config/init_config.py
+++ b/SRV/config/init_config.py
@@ -1,4 +1,3 @@
-# init_config.py
import configparser
import os
import secrets
@@ -9,7 +8,18 @@ logger = logging.getLogger(__name__)
def init_config():
- """Создаёт конфигурационный файл с SECRET_KEY"""
+ """
+ Инициализация конфигурации приложения (JWT SECRET_KEY).
+
+ Назначение:
+ - Создаёт файл `/etc/gonets/config.ini` с секцией `[JWT]` и случайным
+ `secret_key`, если он отсутствует. Устанавливает права `0o600`.
+
+ Замечания:
+ - Функция предназначена для однократного запуска (в инсталляторе/скрипте
+ подготовки окружения) и безопасно пропускает работу, если файл уже существует.
+ """
+ """Создаёт конфигурационный файл с SECRET_KEY."""
config_path = "/etc/gonets/config.ini"
config_dir = os.path.dirname(config_path)
@@ -34,6 +44,6 @@ def init_config():
)
raise
-
+# Нативный запуск для тестов
if __name__ == "__main__":
init_config()
diff --git a/SRV/config/ports.py b/SRV/config/ports.py
index 1e41272..7110383 100644
--- a/SRV/config/ports.py
+++ b/SRV/config/ports.py
@@ -2,7 +2,13 @@ from typing import Dict, List
class PortConfig:
- """Конфигурация портов для сетевого сканирования."""
+ """Конфигурация известных портов для сетевого анализа.
+
+ Назначение:
+ Предоставляет сопоставление распространённых портов с человекочитаемыми
+ названиями сервисов для отображения в UI и логах. Поддерживает расширение
+ пользовательским словарём.
+ """
_DEFAULT_PORTS: Dict[int, str] = {
20: "FTP (Data)",
diff --git a/SRV/core/cli_executor.py b/SRV/core/cli_executor.py
index a6f9332..19d10a1 100644
--- a/SRV/core/cli_executor.py
+++ b/SRV/core/cli_executor.py
@@ -3,8 +3,11 @@ from typing import Tuple
class CLIExecutor:
- """
- Класс для выполнения CLI-команд
+ """Утилита для безопасного выполнения CLI-команд.
+
+ Назначение:
+ Инкапсулирует вызов `subprocess.run` и приводит результат к удобному
+ кортежу (код возврата, stdout) без генерации исключений при ошибках.
"""
@staticmethod
diff --git a/SRV/docs/_build/.sphinx_tmp/config.ini b/SRV/docs/_build/.sphinx_tmp/config.ini
new file mode 100644
index 0000000..1a159a0
--- /dev/null
+++ b/SRV/docs/_build/.sphinx_tmp/config.ini
@@ -0,0 +1,2 @@
+[JWT]
+secret_key = sphinx-docs-secret
diff --git a/SRV/docs/_build/.sphinx_tmp/sessions.json b/SRV/docs/_build/.sphinx_tmp/sessions.json
new file mode 100644
index 0000000..c00ff1a
--- /dev/null
+++ b/SRV/docs/_build/.sphinx_tmp/sessions.json
@@ -0,0 +1 @@
+{"sessions": {}}
\ No newline at end of file
diff --git a/SRV/docs/_build/.sphinx_tmp/users.json b/SRV/docs/_build/.sphinx_tmp/users.json
new file mode 100644
index 0000000..a045601
--- /dev/null
+++ b/SRV/docs/_build/.sphinx_tmp/users.json
@@ -0,0 +1 @@
+{"users": [], "next_id": 1}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/.buildinfo b/SRV/docs/_build/html/.buildinfo
new file mode 100644
index 0000000..e5b0f18
--- /dev/null
+++ b/SRV/docs/_build/html/.buildinfo
@@ -0,0 +1,4 @@
+# Sphinx build info version 1
+# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
+config: 0de2fdeef2a39cc42113efadfd5a855d
+tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/SRV/docs/_build/html/.doctrees/api/config.doctree b/SRV/docs/_build/html/.doctrees/api/config.doctree
new file mode 100644
index 0000000..f1718f8
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/config.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/config/cors.doctree b/SRV/docs/_build/html/.doctrees/api/config/cors.doctree
new file mode 100644
index 0000000..adbc8a8
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/config/cors.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/config/init_config.doctree b/SRV/docs/_build/html/.doctrees/api/config/init_config.doctree
new file mode 100644
index 0000000..5fc3e7b
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/config/init_config.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/config/ports.doctree b/SRV/docs/_build/html/.doctrees/api/config/ports.doctree
new file mode 100644
index 0000000..2c9ca03
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/config/ports.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/core.doctree b/SRV/docs/_build/html/.doctrees/api/core.doctree
new file mode 100644
index 0000000..7d26919
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/core.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/core/cli_executor.doctree b/SRV/docs/_build/html/.doctrees/api/core/cli_executor.doctree
new file mode 100644
index 0000000..63fadc9
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/core/cli_executor.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints.doctree
new file mode 100644
index 0000000..61c2e61
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/auth.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/auth.doctree
new file mode 100644
index 0000000..058369f
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/auth.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/ip.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/ip.doctree
new file mode 100644
index 0000000..eb55312
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/ip.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/network.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/network.doctree
new file mode 100644
index 0000000..b0b711d
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/network.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/settings.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/settings.doctree
new file mode 100644
index 0000000..568530a
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/settings.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/subnet.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/subnet.doctree
new file mode 100644
index 0000000..5de4fd5
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/subnet.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/ufw.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/ufw.doctree
new file mode 100644
index 0000000..72e0601
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/ufw.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/endpoints/websocket.doctree b/SRV/docs/_build/html/.doctrees/api/endpoints/websocket.doctree
new file mode 100644
index 0000000..a9e651a
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/endpoints/websocket.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models.doctree b/SRV/docs/_build/html/.doctrees/api/models.doctree
new file mode 100644
index 0000000..8b7a591
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/auth.doctree b/SRV/docs/_build/html/.doctrees/api/models/auth.doctree
new file mode 100644
index 0000000..95449bf
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/auth.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/network.doctree b/SRV/docs/_build/html/.doctrees/api/models/network.doctree
new file mode 100644
index 0000000..68d0891
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/network.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/request.doctree b/SRV/docs/_build/html/.doctrees/api/models/request.doctree
new file mode 100644
index 0000000..b554977
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/request.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/subnet.doctree b/SRV/docs/_build/html/.doctrees/api/models/subnet.doctree
new file mode 100644
index 0000000..ef397d2
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/subnet.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/ufw.doctree b/SRV/docs/_build/html/.doctrees/api/models/ufw.doctree
new file mode 100644
index 0000000..ce999f4
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/ufw.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/models/wifi.doctree b/SRV/docs/_build/html/.doctrees/api/models/wifi.doctree
new file mode 100644
index 0000000..4fcaf7e
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/models/wifi.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services.doctree b/SRV/docs/_build/html/.doctrees/api/services.doctree
new file mode 100644
index 0000000..05fe385
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/arp_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/arp_service.doctree
new file mode 100644
index 0000000..423411b
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/arp_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/auth_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/auth_service.doctree
new file mode 100644
index 0000000..3d69920
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/auth_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/connect_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/connect_service.doctree
new file mode 100644
index 0000000..e4eb4e2
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/connect_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/firewalld_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/firewalld_service.doctree
new file mode 100644
index 0000000..772c341
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/firewalld_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/ip_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/ip_service.doctree
new file mode 100644
index 0000000..84882fe
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/ip_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/network_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/network_service.doctree
new file mode 100644
index 0000000..b3d4bfd
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/network_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/settings_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/settings_service.doctree
new file mode 100644
index 0000000..e71df78
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/settings_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/subnet_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/subnet_service.doctree
new file mode 100644
index 0000000..66a0b81
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/subnet_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/ufw_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/ufw_service.doctree
new file mode 100644
index 0000000..eff61ba
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/ufw_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/services/wifi_service.doctree b/SRV/docs/_build/html/.doctrees/api/services/wifi_service.doctree
new file mode 100644
index 0000000..f3dacae
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/services/wifi_service.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/storage.doctree b/SRV/docs/_build/html/.doctrees/api/storage.doctree
new file mode 100644
index 0000000..6612d61
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/storage.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/storage/json_storage.doctree b/SRV/docs/_build/html/.doctrees/api/storage/json_storage.doctree
new file mode 100644
index 0000000..9155e6d
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/storage/json_storage.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/storage/session_storage.doctree b/SRV/docs/_build/html/.doctrees/api/storage/session_storage.doctree
new file mode 100644
index 0000000..811b851
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/storage/session_storage.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/utils.doctree b/SRV/docs/_build/html/.doctrees/api/utils.doctree
new file mode 100644
index 0000000..8b267dc
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/utils.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/api/utils/i18n.doctree b/SRV/docs/_build/html/.doctrees/api/utils/i18n.doctree
new file mode 100644
index 0000000..4960a5b
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/api/utils/i18n.doctree differ
diff --git a/SRV/docs/_build/html/.doctrees/environment.pickle b/SRV/docs/_build/html/.doctrees/environment.pickle
new file mode 100644
index 0000000..e069267
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/environment.pickle differ
diff --git a/SRV/docs/_build/html/.doctrees/index.doctree b/SRV/docs/_build/html/.doctrees/index.doctree
new file mode 100644
index 0000000..f79457a
Binary files /dev/null and b/SRV/docs/_build/html/.doctrees/index.doctree differ
diff --git a/SRV/docs/_build/html/_modules/api/v1/auth.html b/SRV/docs/_build/html/_modules/api/v1/auth.html
new file mode 100644
index 0000000..e765d5a
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/auth.html
@@ -0,0 +1,513 @@
+
+
+
+
+
+
+
+ api.v1.auth - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.auth
+from fastapi import APIRouter , Depends , HTTPException , Request
+from fastapi.security import OAuth2PasswordBearer
+from services.auth_service import AuthService
+from models.auth import UserCreate , UserLogin , UserResponse , Token
+from storage.json_storage import JsonStorage
+from utils.i18n import get_translations , _
+from babel.support import Translations
+import logging
+import os
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+router = APIRouter ()
+auth_service = AuthService ()
+oauth2_scheme = OAuth2PasswordBearer ( tokenUrl = "/auth/login" )
+
+
+
[docs]
+
@router . post ( "/register" , response_model = UserResponse )
+
async def register ( user : UserCreate , translations : Translations = Depends ( get_translations ())):
+
"""Регистрирует нового пользователя.
+
+
Создаёт нового пользователя на основе предоставленных данных и возвращает
+
информацию о зарегистрированном пользователе.
+
+
Args:
+
user (UserCreate): Объект с данными нового пользователя (username, password, is_admin).
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
UserResponse: Объект с данными зарегистрированного пользователя (id, username, is_admin).
+
+
Raises:
+
HTTPException: Если пользователь уже существует (status_code=400) или произошла
+
внутренняя ошибка (status_code=500).
+
"""
+
try :
+
db_user = auth_service . register ( user )
+
logger . info ( f "Пользователь { user . username } успешно зарегистрирован" )
+
return db_user
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
logger . error ( f "Error in register: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось зарегистрировать пользователя" ))
+
+
+
+
[docs]
+
@router . post ( "/login" , response_model = Token )
+
async def login ( user : UserLogin , request : Request , translations : Translations = Depends ( get_translations ())):
+
"""Авторизует пользователя и создаёт сессию.
+
+
Проверяет учетные данные пользователя и возвращает токен и session_id для
+
последующей авторизации.
+
+
Args:
+
user (UserLogin): Объект с данными для входа (username, password).
+
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
Token: Объект с access_token и session_id для авторизации.
+
+
Raises:
+
HTTPException: Если учетные данные неверны (status_code=401) или произошла
+
внутренняя ошибка (status_code=500).
+
"""
+
try :
+
client_id = request . client . host
+
logger . debug ( f "Попытка входа в систему клиента { client_id } " )
+
result = auth_service . login ( user , client_id )
+
logger . info ( f "Успешная авторизация для клиента { client_id } , Идентификатор сессии: { result [ 'session_id' ] } " )
+
return result
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
logger . error ( f "Ошибка при авторизации: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось авторизовать пользователя" ))
+
+
+
+
[docs]
+
@router . get ( "/me" )
+
async def get_current_user ( request : Request , translations : Translations = Depends ( get_translations ())):
+
"""Возвращает данные текущего пользователя и токен.
+
+
Проверяет активную сессию по client_id и возвращает информацию о пользователе
+
и токен для дальнейшего использования.
+
+
Args:
+
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключами "user" (UserResponse), "access_token" (str) и
+
"token_type" (str, всегда "bearer").
+
+
Raises:
+
HTTPException: Если сессия не найдена или истекла (status_code=401) или
+
произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
client_id = request . client . host
+
logger . debug ( f "Запрос /me для клиента { client_id } " )
+
result = auth_service . get_user_by_session ( client_id )
+
return {
+
"user" : result [ "user" ],
+
"access_token" : result [ "access_token" ],
+
"token_type" : translations . gettext ( "bearer" ),
+
}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
logger . error ( f "Ошибка при get_current_user: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось получить данные пользователя" ))
+
+
+
+
[docs]
+
@router . get ( "/logout" )
+
async def logout ( request : Request , translations : Translations = Depends ( get_translations ())):
+
"""Выход из системы, удаление сессии.
+
+
Удаляет активную сессию пользователя по client_id.
+
+
Args:
+
request (Request): Объект запроса для получения client_id (IP-адрес клиента).
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Пустой словарь или сообщение об успешном выходе.
+
+
Raises:
+
HTTPException: Если сессия не найдена (status_code=401) или произошла
+
внутренняя ошибка (status_code=500).
+
"""
+
try :
+
client_id = request . client . host
+
logger . debug ( f "Запрос на выход из системы для клиента { client_id } " )
+
return auth_service . logout ( client_id )
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
logger . error ( f "Не удалось выйти из системы: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось выйти из системы" ))
+
+
+
+
[docs]
+
@router . get ( "/check_json" )
+
async def check_users_file ( translations : Translations = Depends ( get_translations ())):
+
"""Проверяет, существует ли файл users.json и содержит ли он пользователей.
+
+
Возвращает информацию о состоянии файла и наличии пользователей.
+
+
Returns:
+
dict: Словарь с ключами:
+
- "exists" (bool): Существует ли файл.
+
- "has_users" (bool): Содержит ли файл пользователей.
+
- "message" (str): Сообщение с описанием результата.
+
+
Raises:
+
HTTPException: Если произошла ошибка при проверке файла (status_code=500).
+
"""
+
try :
+
file_path = "/etc/gonets/users.json"
+
storage = JsonStorage ( file_path = file_path )
+
+
if not os . path . exists ( file_path ):
+
logger . info ( f "Файл { file_path } не существует" )
+
return {
+
"exists" : False ,
+
"has_users" : False ,
+
"message" : translations . gettext ( "Файл users.json не существует" ),
+
}
+
+
data = storage . _read_data ()
+
has_users = len ( data . get ( "users" , [])) > 0
+
if has_users :
+
logger . info ( f "Файл { file_path } существует и содержит пользователей" )
+
return {
+
"exists" : True ,
+
"has_users" : True ,
+
"message" : translations . gettext ( "Файл users.json существует и содержит пользователей" ),
+
}
+
else :
+
logger . info ( f "Файл { file_path } существует, но не содержит пользователей" )
+
return {
+
"exists" : True ,
+
"has_users" : False ,
+
"message" : translations . gettext ( "Файл users.json существует, но не содержит пользователей" ),
+
}
+
except Exception as e :
+
logger . error ( f "Ошибка при проверке файла: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка при проверке файла" ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/ip.html b/SRV/docs/_build/html/_modules/api/v1/ip.html
new file mode 100644
index 0000000..4ca4a98
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/ip.html
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+ api.v1.ip - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.ip
+from typing import Dict , Any
+from fastapi import APIRouter , Body , HTTPException , Depends
+from services.ip_service import IPService
+from utils.i18n import get_translations
+from babel.support import Translations
+
+router = APIRouter ()
+ip_service = IPService ()
+
+
+
[docs]
+
@router . post ( "/check_ip" )
+
async def check_ip_config ( data : Dict [ str , Any ] = Body ( ... ), translations : Translations = Depends ( get_translations ())):
+
"""Проверяет доступность IP-адреса перед его применением.
+
+
Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
+
и возвращает результат проверки.
+
+
Args:
+
data (Dict[str, Any]): Словарь с данными конфигурации, включая
+
"ipv4_updates" с полем "ipv4.address-data" для нового IP-адреса.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
Dict[str, Any]: Словарь с ключами:
+
- "available" (bool): Доступен ли IP-адрес.
+
- "message" (str): Сообщение о результате проверки.
+
+
Raises:
+
HTTPException: Если произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
result = await ip_service . check_ip_config ( data , translations )
+
return result
+
except Exception as e :
+
raise HTTPException (
+
status_code = 500 ,
+
detail = translations . gettext ( "Ошибка проверки IP конфигурации: {error} " ) . format ( error = str ( e ))
+
)
+
+
+
+
[docs]
+
@router . post ( "/apply_ip" )
+
async def apply_ip_config ( data : Dict [ str , Any ] = Body ( ... ), translations : Translations = Depends ( get_translations ())):
+
"""Применяет проверенные IP-настройки к указанному сетевому профилю.
+
+
Обновляет настройки соединения через NetworkManager, отключает и
+
переподключает устройство к сети.
+
+
Args:
+
data (Dict[str, Any]): Словарь с данными конфигурации, включая
+
"connection_id" и "ipv4_updates" с обновлениями IP-настроек.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключами:
+
- "success" (bool): Успешность операции.
+
- "message" (str): Сообщение об успешном применении настроек.
+
+
Raises:
+
HTTPException: Если подключение не найдено (status_code=404) или
+
произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
await ip_service . apply_ip_config ( data , translations )
+
return {
+
"success" : True ,
+
"message" : translations . gettext ( "Настройки сетевого профиля для {connection_id} применены" ) . format (
+
connection_id = data [ "connection_id" ]
+
),
+
}
+
except ValueError as e :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( str ( e )))
+
except Exception as e :
+
raise HTTPException (
+
status_code = 500 ,
+
detail = translations . gettext ( "Ошибка проверки IP конфигурации: {error} " ) . format ( error = str ( e ))
+
)
+
+
+
+
[docs]
+
@router . post ( "/save_ip" )
+
async def save_ip_config ( data : Dict [ str , Any ] = Body ( ... ), translations : Translations = Depends ( get_translations ())):
+
"""Применяет проверенные IP-настройки для отката сетевого профиля.
+
+
Используется для сохранения и отката настроек сетевого профиля к предыдущему
+
состоянию через NetworkManager.
+
+
Args:
+
data (Dict[str, Any]): Словарь с данными конфигурации, включая
+
"connection_id" и "ipv4_updates" с обновлениями IP-настроек.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключами:
+
- "success" (bool): Успешность операции.
+
- "message" (str): Сообщение об успешном применении настроек.
+
+
Raises:
+
HTTPException: Если подключение не найдено (status_code=404) или
+
произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
await ip_service . apply_ip_config ( data , translations )
+
return {
+
"success" : True ,
+
"message" : translations . gettext ( "Настройки сетевого профиля для {connection_id} применены" ) . format (
+
connection_id = data [ "connection_id" ]
+
),
+
}
+
except ValueError as e :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( str ( e )))
+
except Exception as e :
+
raise HTTPException (
+
status_code = 500 ,
+
detail = translations . gettext ( "Ошибка проверки IP конфигурации: {error} " ) . format ( error = str ( e ))
+
)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/network.html b/SRV/docs/_build/html/_modules/api/v1/network.html
new file mode 100644
index 0000000..a213c29
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/network.html
@@ -0,0 +1,558 @@
+
+
+
+
+
+
+
+ api.v1.network - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.network
+from fastapi import APIRouter , Body , HTTPException , Depends
+import logging
+from typing import Dict , Tuple , List , Union
+from services.network_service import NetworkService
+from models.network import (
+ NetworkInterfacesContainer ,
+ Activation ,
+ Deactivation ,
+ NetworkInterface ,
+ WiFiDevice ,
+)
+from utils.i18n import get_translations , _
+from babel.support import Translations
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+router = APIRouter ()
+network_service = NetworkService ()
+
+
+
[docs]
+
class ContainerPositionManager :
+
"""Класс для управления позициями и состоянием контейнеров.
+
+
Хранит и обновляет координаты (x, y) и состояние (isOpen) для контейнеров
+
активации и деактивации.
+
"""
+
+
def __init__ ( self ):
+
"""Инициализирует менеджер позиций контейнеров.
+
+
Создаёт начальные позиции и состояния для контейнеров "activation" и
+
"deactivation" с координатами (0, 0) и закрытым состоянием (False).
+
"""
+
self . positions : Dict [ str , Dict [ str , Union [ int , bool ]]] = {
+
"activation" : {
+
"x" : 0 ,
+
"y" : 0 ,
+
"isOpen" : False
+
},
+
"deactivation" : {
+
"x" : 0 ,
+
"y" : 0 ,
+
"isOpen" : False
+
},
+
}
+
+
+
[docs]
+
def get_position_and_state ( self , container_name : str , translations : Translations = Depends ( get_translations ())) -> Tuple [ int , int , bool ]:
+
"""Получает текущие координаты и состояние указанного контейнера.
+
+
Args:
+
container_name (str): Имя контейнера ("activation" или "deactivation").
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
Tuple[int, int, bool]: Кортеж с координатами (x, y) и состоянием (isOpen).
+
+
Raises:
+
ValueError: Если указан неверный идентификатор контейнера.
+
"""
+
container = self . positions . get ( container_name )
+
if not container :
+
raise ValueError ( translations . gettext ( "Неверное имя контейнера: {container_name} " ) . format ( container_name = container_name ))
+
return container [ "x" ], container [ "y" ], container [ "isOpen" ]
+
+
+
+
[docs]
+
def update_position ( self , container_id : int , x : int , y : int , isOpen : bool ) -> None :
+
"""Обновляет позицию и состояние контейнера по его идентификатору.
+
+
Args:
+
container_id (int): Идентификатор контейнера (1 для "activation", 2 для "deactivation").
+
x (int): Новая координата X.
+
y (int): Новая координата Y.
+
isOpen (bool): Новое состояние контейнера (открыт/закрыт).
+
+
Raises:
+
ValueError: Если container_id не соответствует ни одному контейнеру.
+
"""
+
container_name = "activation" if container_id == 1 else "deactivation"
+
self . positions [ container_name ] = { "x" : x , "y" : y , "isOpen" : isOpen }
+
+
+
+container_manager = ContainerPositionManager ()
+
+
+
[docs]
+
async def get_network_data ( translations : Translations = Depends ( get_translations ())) -> Dict [ str , Dict ]:
+
"""Возвращает текущие сетевые данные в виде словаря.
+
+
Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
+
и возвращает их в формате Activation и Deactivation с учётом позиций и состояний
+
контейнеров.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
Dict[str, Dict]: Словарь с ключами "activation" и "deactivation", содержащими
+
данные о соответствующих контейнерах.
+
+
Raises:
+
HTTPException: Если произошла ошибка при получении данных (status_code=500).
+
"""
+
logger . debug ( "Calling get_network_data()" )
+
try :
+
network_data = await network_service . get_network_interfaces_container ()
+
+
activation_interfaces , deactivation_interfaces = _split_interfaces ( network_data . interfaces )
+
+
activation_x , activation_y , activation_is_open = ( container_manager . get_position_and_state ( "activation" ))
+
deactivation_x , deactivation_y , deactivation_is_open = ( container_manager . get_position_and_state ( "deactivation" ))
+
+
activation = Activation (
+
id = 1 ,
+
interfaces = activation_interfaces ,
+
container_x = activation_x ,
+
container_y = activation_y ,
+
isOpen = activation_is_open ,
+
)
+
deactivation = Deactivation (
+
id = 2 ,
+
interfaces = deactivation_interfaces ,
+
container_x = deactivation_x ,
+
container_y = deactivation_y ,
+
isOpen = deactivation_is_open ,
+
)
+
+
return {
+
"activation" : activation . model_dump (),
+
"deactivation" : deactivation . model_dump (),
+
}
+
except Exception as e :
+
logger . error ( f "Error in get_network_data: { e } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось получить сетевые данные" ))
+
+
+def _split_interfaces (
+ interfaces : List [ Union [ NetworkInterface , WiFiDevice ]],
+) -> Tuple [ List [ Union [ NetworkInterface , WiFiDevice ]], List [ Union [ NetworkInterface , WiFiDevice ]]]:
+ """Разделяет интерфейсы на активные и неактивные.
+
+ Args:
+ interfaces (List[Union[NetworkInterface, WiFiDevice]]): Список сетевых интерфейсов.
+
+ Returns:
+ Tuple[List[Union[NetworkInterface, WiFiDevice]], List[Union[NetworkInterface, WiFiDevice]]]:
+ Кортеж из списков активных и неактивных интерфейсов.
+ """
+ activation_interfaces = []
+ deactivation_interfaces = []
+ for interface in interfaces :
+ if interface . device_state == "100" :
+ activation_interfaces . append ( interface )
+ else :
+ deactivation_interfaces . append ( interface )
+ return activation_interfaces , deactivation_interfaces
+
+
+
[docs]
+
@router . post ( "/update_container_position" )
+
async def update_container_position ( data : Dict [ str , Union [ int , bool ]] = Body ( ... ), translations : Translations = Depends ( get_translations ())):
+
"""Обновляет позицию и состояние контейнера.
+
+
Принимает данные о новом положении и состоянии контейнера и обновляет их
+
в менеджере позиций.
+
+
Args:
+
data (Dict[str, Union[int, bool]]): Словарь с ключами:
+
- "container_id" (int): Идентификатор контейнера (1 или 2).
+
- "container_x" (int): Новая координата X.
+
- "container_y" (int): Новая координата Y.
+
- "isOpen" (bool): Новое состояние контейнера (открыт/закрыт).
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключами:
+
- "success" (bool): Успешность операции.
+
- "message" (str): Сообщение об успешном обновлении.
+
+
Raises:
+
HTTPException: Если отсутствуют обязательные поля (status_code=400) или
+
типы данных неверны.
+
"""
+
try :
+
required_keys = [
+
"container_id" , "container_x" , "container_y" , "isOpen"
+
]
+
if not all ( key in data for key in required_keys ):
+
raise ValueError (
+
translations . gettext ( "Отсутствуют обязательные поля: container_id, container_x, container_y, isOpen" )
+
)
+
+
container_id = data [ "container_id" ]
+
container_x = data [ "container_x" ]
+
container_y = data [ "container_y" ]
+
is_open = data [ "isOpen" ]
+
+
if ( not isinstance ( container_id , int )
+
or not isinstance ( container_x , int )
+
or not isinstance ( container_y , int )
+
or not isinstance ( is_open , bool )):
+
raise ValueError ( translations . gettext ( "Неверный тип данных в запросе" ))
+
+
container_manager . update_position ( container_id , container_x , container_y , is_open )
+
return {
+
"success" : True ,
+
"message" : translations . gettext ( "Позиция и состояние контейнера обновлены" )
+
}
+
except Exception as e :
+
logger . error ( f "Error updating container position: { e } " )
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Не удалось обновить положение контейнера: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . get ( "/get_hostname" )
+
async def get_hostname ( translations : Translations = Depends ( get_translations ())):
+
"""Возвращает имя хоста системы.
+
+
Выполняет запрос к сервису для получения имени хоста.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключом "hostname" и значением имени хоста.
+
+
Raises:
+
HTTPException: Если произошла ошибка при получении имени хоста (status_code=500).
+
"""
+
try :
+
hostname_info = await network_service . get_hostname ()
+
return hostname_info
+
except Exception as e :
+
logger . error ( f "Error in get_hostname: { e } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось получить имя хоста" ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/settings.html b/SRV/docs/_build/html/_modules/api/v1/settings.html
new file mode 100644
index 0000000..a3a6f4e
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/settings.html
@@ -0,0 +1,545 @@
+
+
+
+
+
+
+
+ api.v1.settings - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.settings
+import os
+import logging
+import subprocess
+from typing import Optional
+from fastapi import APIRouter , HTTPException , Depends , Response , Request , Body
+from starlette.responses import FileResponse
+from services.settings_service import SettingsService
+from utils.i18n import get_translations
+from babel.support import Translations
+from models.subnet import SubnetScanRequest # Предполагается, что модель определена
+
+logging . basicConfig ( level = logging . DEBUG )
+logger = logging . getLogger ( __name__ )
+
+router = APIRouter ()
+settings_service = SettingsService ()
+
+
+
[docs]
+
@router . post ( "/clear_subnet" )
+
def clear_subnet ( subnet : Optional [ str ] = None , translations : Translations = Depends ( get_translations ())):
+
"""Очищает данные узлов подсети, сохранённые в devices.json.
+
+
Удаляет все записи об узлах подсети, оставляя файл пустым.
+
+
Args:
+
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+
Если None, очищает все подсети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешной очистке.
+
+
Raises:
+
HTTPException: Если произошла ошибка очистки (status_code=500).
+
"""
+
try :
+
success = settings_service . clear_subnet ( subnet )
+
if success :
+
if subnet :
+
return { "message" : translations . gettext ( "Данные узлов подсети {subnet} успешно очищены" ) . format ( subnet = subnet )}
+
else :
+
return { "message" : translations . gettext ( "Данные всех подсетей успешно очищены" )}
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось очистить данные узлов подсети" ))
+
except Exception as e :
+
logger . error ( f "Error clearing subnet data: { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка очистки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . post ( "/clear_wifi" )
+
def clear_wifi ( translations : Translations = Depends ( get_translations ())):
+
"""Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
+
+
Удаляет все записи о точках доступа, оставляя файл пустым.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешной очистке.
+
+
Raises:
+
HTTPException: Если произошла ошибка очистки (status_code=500).
+
"""
+
try :
+
success = settings_service . clear_wifi ()
+
if success :
+
return { "message" : translations . gettext ( "Данные точек доступа Wi-Fi успешно очищены" )}
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось очистить данные точек доступа Wi-Fi" ))
+
except Exception as e :
+
logger . error ( f "Error clearing wifi data: { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка очистки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . post ( "/clear_all_data" )
+
def clear_all_data ( translations : Translations = Depends ( get_translations ())):
+
"""Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
+
+
Удаляет все записи из devices.json и wifi.json.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешной очистке.
+
+
Raises:
+
HTTPException: Если произошла ошибка очистки (status_code=500).
+
"""
+
try :
+
success = settings_service . clear_all_data ()
+
if success :
+
return { "message" : translations . gettext ( "Все сетевые данные успешно очищены" )}
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось очистить все сетевые данные" ))
+
except Exception as e :
+
logger . error ( f "Error clearing all data: { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка очистки: {error} " ) . format ( error = str ( e )))
+
+
+WIFI_PATH = "storage/wifi/wifi.json"
+
+
+
[docs]
+
@router . post ( "/export_devices" , response_class = FileResponse )
+
async def export_devices ( request : SubnetScanRequest = Body ( ... ), translations : Translations = Depends ( get_translations ())):
+
"""
+
Экспортирует файл с данными устройств в формате JSON для указанной подсети.
+
+
Args:
+
request (SubnetScanRequest): Объект с полем subnet в формате CIDR.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
FileResponse: Файл <subnet>.json для скачивания.
+
+
Raises:
+
HTTPException: Если файл не найден (status_code=404).
+
"""
+
subnet = request . subnet
+
file_path = f "storage/subnets/ { subnet . replace ( '/' , '_' ) } .json"
+
if not os . path . exists ( file_path ):
+
logger . warning ( f "Export file not found: { file_path } " )
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Файл для подсети {subnet} не найден" ) . format ( subnet = subnet ))
+
logger . info ( f "Exporting file: { file_path } " )
+
return FileResponse (
+
path = file_path ,
+
filename = f " { subnet . replace ( '/' , '_' ) } .json" ,
+
media_type = "application/json"
+
)
+
+
+
+
[docs]
+
@router . get ( "/export_wifi" , response_class = FileResponse )
+
async def export_wifi ( translations : Translations = Depends ( get_translations ())):
+
"""
+
Экспортирует файл с данными устройств в формате JSON.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
FileResponse: Файл wifi.json для скачивания.
+
+
Raises:
+
HTTPException: Если файл не найден (status_code=404).
+
"""
+
if not os . path . exists ( WIFI_PATH ):
+
logger . warning ( f "Export file not found: { WIFI_PATH } " )
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Файл не найден" ))
+
logger . info ( f "Exporting file: { WIFI_PATH } " )
+
return FileResponse (
+
path = WIFI_PATH ,
+
filename = "wifi.json" ,
+
media_type = "application/json"
+
)
+
+
+
+
[docs]
+
@router . post ( "/set_language" )
+
def set_language ( locale : str , response : Response , translations : Translations = Depends ( get_translations ())):
+
"""
+
Устанавливает куку locale для смены языка пользователя.
+
+
Args:
+
locale (str): Используемый язык.
+
response (Response): Запрос.
+
translations: Объект переводов текущего языка.
+
+
Returns:
+
dict: Сообщение об успешной установке языка.
+
"""
+
lc_messages_path = os . path . join ( os . path . dirname ( __file__ ), '..' , '..' , 'locales' , locale , 'LC_MESSAGES' )
+
if not os . path . isdir ( lc_messages_path ):
+
raise HTTPException (
+
status_code = 400 ,
+
detail = translations . gettext ( "Язык ' {locale} ' не поддерживается." ) . format ( locale = locale )
+
)
+
has_mo_or_po = any ( f . endswith ( '.mo' ) or f . endswith ( '.po' ) for f in os . listdir ( lc_messages_path ))
+
if not has_mo_or_po :
+
raise HTTPException (
+
status_code = 400 ,
+
detail = translations . gettext ( "Язык ' {locale} ' не поддерживается (нет файлов переводов)." ) . format ( locale = locale )
+
)
+
response . set_cookie ( key = "locale" , value = locale , path = "/" , httponly = True )
+
return { "message" : translations . gettext ( "Язык успешно установлен: {locale} " ) . format ( locale = locale )}
+
+
+
+
[docs]
+
@router . get ( "/get_system_language" )
+
async def get_system_language ( request : Request , translations : Translations = Depends ( get_translations ())):
+
"""Выводит текущий язык, установленный в системе на основе locale.
+
+
Выполняет команду 'locale' в системе и извлекает параметры LANG
+
+
Args:
+
request (Request): Объект запроса для доступа к параметрам.
+
translations: Объект переводов текущего языка.
+
+
Returns:
+
dict: Словарь с текущим языком системы.
+
+
Raises:
+
HTTPException: Если не удалось извлечь системный язык (status_code=500).
+
"""
+
try :
+
result = subprocess . run ([ 'locale' ], capture_output = True , text = True , check = True )
+
output = result . stdout
+
lang = next (( line . split ( '=' )[ 1 ] . strip () for line in output . splitlines () if line . startswith ( 'LANG=' )), None )
+
if lang :
+
return { "language" : lang }
+
else :
+
logger . warning ( f "No locale found: { output } " )
+
return { "language" : 'unknown' }
+
except subprocess . CalledProcessError as e :
+
logger . error ( f "Error executing locale command: { e } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Не удалось определить язык в системе" ))
+
except Exception as e :
+
logger . error ( f "Unexpected error getting system language: { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка при определении языка в системе {error} " ) . format ( error = str ( e )))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/subnet.html b/SRV/docs/_build/html/_modules/api/v1/subnet.html
new file mode 100644
index 0000000..a7ae76d
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/subnet.html
@@ -0,0 +1,700 @@
+
+
+
+
+
+
+
+ api.v1.subnet - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.subnet
+from fastapi import APIRouter , HTTPException , Body , Depends
+from models.subnet import SubnetScanResult , SubnetScanRequest
+from models.wifi import WifiScanResult
+from models.request import NoteRequest , WifiConnectRequest , WifiDisconnectRequest
+from services.subnet_service import SubnetService , get_subnet_service
+from services.wifi_service import WifiService
+from services.connect_service import WifiConnectionService
+from utils.i18n import get_translations , _
+from babel.support import Translations
+import logging
+
+router = APIRouter ()
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
@router . post ( "/devices" , response_model = SubnetScanResult )
+
def scan_subnet ( request : SubnetScanRequest = Body ( ... ), subnet_service : SubnetService = Depends ( get_subnet_service ), translations : Translations = Depends ( get_translations ())):
+
"""Сканирует локальную подсеть и возвращает данные об устройствах.
+
+
Выполняет сканирование указанной подсети для обнаружения активных устройств.
+
+
Args:
+
request (SubnetScanRequest): Объект с полем subnet.
+
subnet_service: Сервис сканирования подсети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
SubnetScanResult: Объект с результатами сканирования (устройства и их данные).
+
+
Raises:
+
HTTPException: Если произошла ошибка сканирования (status_code=500).
+
"""
+
try :
+
# Если subnet пустая строка, определяем автоматически
+
subnet = request . subnet . strip () if request . subnet else None
+
logger . info ( f "Requested subnet scan for: { subnet or 'auto-detected' } " )
+
result = subnet_service . scan_subnet ( subnet )
+
if not result . devices and result . error :
+
logger . warning ( f "No devices found in subnet { subnet or 'auto-detected' } " )
+
return result
+
except Exception as e :
+
logger . error ( f "Error in scan_subnet: { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка сканирования подсети: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . get ( "/wifi" , response_model = WifiScanResult )
+
def scan_wifi ( timeout : int = 30 , translations : Translations = Depends ( get_translations ())):
+
"""Сканирует Wi-Fi точки доступа и возвращает данные о них.
+
+
Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
+
+
Args:
+
timeout (int, optional): Таймаут сканирования в секундах. По умолчанию 30.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
WifiScanResult: Объект с результатами сканирования (список Wi-Fi сетей).
+
+
Raises:
+
HTTPException: Если произошла ошибка сканирования Wi-Fi (status_code=500).
+
"""
+
try :
+
wifi_service = WifiService ()
+
result = wifi_service . scan_wifi ( timeout = timeout )
+
return result
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка сканирования Wi-Fi: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . post ( "/devices/ {mac} /note" )
+
def add_note ( mac : str , request : NoteRequest , subnet_service : SubnetService = Depends ( get_subnet_service ), translations : Translations = Depends ( get_translations ())):
+
"""Добавляет заметку для устройства по MAC-адресу.
+
+
Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
request (NoteRequest): Объект с полем "note" для текста заметки.
+
subnet_service: Сервис сканирования подсети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном добавлении заметки.
+
+
Raises:
+
HTTPException: Если устройство не найдено (status_code=404), заметка уже
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
success , result = subnet_service . add_note ( mac , request . note , translations )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Устройство с MAC {mac} не найдено" ) . format ( mac = mac ))
+
elif result [ 0 ] == "note_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для устройства с MAC {mac} уже существует" ) . format ( mac = mac ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка ' {note} ' добавлена для устройства {mac} " ) . format ( note = request . note , mac = mac )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка добавления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . put ( "/devices/ {mac} /note" )
+
def update_note ( mac : str , request : NoteRequest , subnet_service : SubnetService = Depends ( get_subnet_service ), translations : Translations = Depends ( get_translations ())):
+
"""Обновляет заметку для устройства по MAC-адресу.
+
+
Изменяет существующую заметку, привязанную к устройству.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
request (NoteRequest): Объект с полем "note" для нового текста заметки.
+
subnet_service: Сервис сканирования подсети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном обновлении заметки.
+
+
Raises:
+
HTTPException: Если устройство не найдено (status_code=404), заметка не
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
success , result = subnet_service . update_note ( mac , request . note , translations )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Устройство с MAC {mac} не найдено" ) . format ( mac = mac ))
+
elif result [ 0 ] == "note_not_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для устройства с MAC {mac} не существует" ) . format ( mac = mac ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка ' {note} ' обновлена для устройства {mac} " ) . format ( note = request . note , mac = mac )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка обновления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . delete ( "/devices/ {mac} /note" )
+
def delete_note ( mac : str , subnet_service : SubnetService = Depends ( get_subnet_service ), translations : Translations = Depends ( get_translations ())):
+
"""Удаляет заметку для устройства по MAC-адресу.
+
+
Удаляет привязанную заметку для указанного устройства.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
subnet_service: Сервис сканирования подсети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном удалении заметки.
+
+
Raises:
+
HTTPException: Если устройство не найдено (status_code=404), заметка не
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
success , result = subnet_service . delete_note ( mac , translations )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Устройство с MAC {mac} не найдено" ) . format ( mac = mac ))
+
elif result [ 0 ] == "note_not_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для устройства с MAC {mac} не существует" ) . format ( mac = mac ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка удалена для устройства {mac} " ) . format ( mac = mac )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка удаления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . post ( "/wifi/ {ssid} /note" )
+
def add_wifi_note ( ssid : str , request : NoteRequest , translations : Translations = Depends ( get_translations ())):
+
"""Добавляет заметку для сети Wi-Fi по SSID.
+
+
Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
+
+
Args:
+
ssid (str): Идентификатор Wi-Fi сети.
+
request (NoteRequest): Объект с полем "note" для текста заметки.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном добавлении заметки.
+
+
Raises:
+
HTTPException: Если сеть не найдена (status_code=404), заметка уже
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
wifi_service = WifiService ()
+
success , result = wifi_service . add_note ( ssid , request . note )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Сеть с SSID {ssid} не найдена" ) . format ( ssid = ssid ))
+
elif result [ 0 ] == "note_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для сети с SSID {ssid} уже существует" ) . format ( ssid = ssid ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка ' {note} ' добавлена для сети с SSID {ssid} " ) . format ( note = request . note , ssid = ssid )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка добавления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . put ( "/wifi/ {ssid} /note" )
+
def update_wifi_note ( ssid : str , request : NoteRequest , translations : Translations = Depends ( get_translations ())):
+
"""Обновляет заметку для сети Wi-Fi по SSID.
+
+
Изменяет существующую заметку, привязанную к Wi-Fi сети.
+
+
Args:
+
ssid (str): Идентификатор Wi-Fi сети.
+
request (NoteRequest): Объект с полем "note" для нового текста заметки.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном обновлении заметки.
+
+
Raises:
+
HTTPException: Если сеть не найдена (status_code=404), заметка не
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
wifi_service = WifiService ()
+
success , result = wifi_service . update_note ( ssid , request . note )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Сеть с SSID {ssid} не найдена" ) . format ( ssid = ssid ))
+
elif result [ 0 ] == "note_not_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для сети с SSID {ssid} не существует" ) . format ( ssid = ssid ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка ' {note} ' обновлена для сети с SSID {ssid} " ) . format ( note = request . note , ssid = ssid )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка обновления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . delete ( "/wifi/ {ssid} /note" )
+
def delete_wifi_note ( ssid : str , translations : Translations = Depends ( get_translations ())):
+
"""Удаляет заметку для сети Wi-Fi по SSID.
+
+
Удаляет привязанную заметку для указанной Wi-Fi сети.
+
+
Args:
+
ssid (str): Идентификатор Wi-Fi сети.
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном удалении заметки.
+
+
Raises:
+
HTTPException: Если сеть не найдена (status_code=404), заметка не
+
существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
"""
+
try :
+
wifi_service = WifiService ()
+
success , result = wifi_service . delete_note ( ssid )
+
if not success :
+
if result [ 0 ] == "not_found" :
+
raise HTTPException ( status_code = 404 , detail = translations . gettext ( "Сеть с SSID {ssid} не найдена" ) . format ( ssid = ssid ))
+
elif result [ 0 ] == "note_not_exists" :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( "Заметка для сети с SSID {ssid} не существует" ) . format ( ssid = ssid ))
+
else :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = result [ 1 ][ "error" ]))
+
return { "message" : translations . gettext ( "Заметка удалена для сети с SSID {ssid} " ) . format ( ssid = ssid )}
+
except HTTPException as e :
+
raise e
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = translations . gettext ( "Ошибка удаления заметки: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
@router . post ( "/wifi/connect" )
+
def connect_to_wifi ( request : WifiConnectRequest , translations : Translations = Depends ( get_translations ())):
+
"""Подключается к Wi-Fi сети по SSID и паролю.
+
+
Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
+
учетных данных.
+
+
Args:
+
request (WifiConnectRequest): Объект с полями "ssid" и "password".
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном подключении.
+
+
Raises:
+
HTTPException: Если подключение не удалось (status_code=400).
+
"""
+
try :
+
wifi_service = WifiConnectionService ( translations )
+
success , message = wifi_service . connect ( request . ssid , request . password )
+
if not success :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( message ))
+
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 )))
+
+
+
+
[docs]
+
@router . post ( "/wifi/disconnect" )
+
def disconnect_from_wifi ( request : WifiDisconnectRequest , translations : Translations = Depends ( get_translations ())):
+
"""Отключается от Wi-Fi сети по указанному SSID.
+
+
Выполняет отключение от указанной Wi-Fi сети.
+
+
Args:
+
request (WifiDisconnectRequest): Объект с полем "ssid".
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с сообщением об успешном отключении.
+
+
Raises:
+
HTTPException: Если отключение не удалось (status_code=400).
+
"""
+
try :
+
wifi_service = WifiConnectionService ( translations )
+
success , message = wifi_service . disconnect ( request . ssid )
+
if not success :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( message ))
+
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 )))
+
+
+
+
[docs]
+
@router . get ( "/wifi/status" )
+
def get_wifi_status ( translations : Translations = Depends ( get_translations ())):
+
"""Проверяет статус текущего подключения к Wi-Fi.
+
+
Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Returns:
+
dict: Словарь с ключами:
+
- "status" (str): Состояние ("connected" или другое).
+
- "ssid" (str, optional): SSID текущей сети, если подключено.
+
+
Raises:
+
HTTPException: Если проверка статуса не удалась (status_code=400).
+
"""
+
try :
+
wifi_service = WifiConnectionService ( translations )
+
success , message = wifi_service . get_status ()
+
if not success :
+
raise HTTPException ( status_code = 400 , detail = translations . gettext ( 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 )))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/ufw.html b/SRV/docs/_build/html/_modules/api/v1/ufw.html
new file mode 100644
index 0000000..edb6aa6
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/ufw.html
@@ -0,0 +1,555 @@
+
+
+
+
+
+
+
+ api.v1.ufw - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.ufw
+from fastapi import APIRouter , HTTPException
+from typing import List
+from pydantic import BaseModel , Field
+
+from models.ufw import UFWRulesResponse , UFWStatus , UFWRule
+from services.ufw_service import UFWService
+
+router = APIRouter ()
+
+
+
+
[docs]
+
class RuleNumbers ( BaseModel ):
+
"""Модель запроса на удаление UFW правил.
+
+
Атрибуты:
+
+
- ``numbers`` (List[int]): Список номеров правил для удаления.
+
"""
+
numbers : List [ int ] = Field (
+
... , description = "Список номеров правил для удаления" )
+
+
+
+
+
[docs]
+
@router . get ( "/rules" , response_model = UFWRulesResponse )
+
async def get_ufw_rules ():
+
"""Возвращает все правила UFW.
+
+
Осуществляет обращение к сервису `UFWService` для чтения текущего списка
+
правил брандмауэра и возвращает результат в сериализованном виде для клиента.
+
+
Returns:
+
UFWRulesResponse: Структура с коллекцией правил и сопутствующей
+
информацией о состоянии.
+
+
Raises:
+
HTTPException: В случае ошибок взаимодействия с UFW (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
return ufw_service . get_ufw_rules ()
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . get ( "/status" , response_model = UFWStatus )
+
async def get_ufw_status ():
+
"""Возвращает текущий статус UFW.
+
+
Returns:
+
UFWStatus: Объект со сведениями о включенности UFW и деталями статуса.
+
+
Raises:
+
HTTPException: Если не удалось получить статус (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
return ufw_service . get_ufw_status ()
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . post ( "/enable" )
+
async def enable_ufw ():
+
"""Включает UFW.
+
+
Returns:
+
dict: Словарь с ключами `status` и `message` при успешном выполнении.
+
+
Raises:
+
HTTPException: Если включение завершилось неуспешно (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
success = ufw_service . enable_ufw ()
+
if success :
+
return { "status" : "success" , "message" : "UFW успешно включен" }
+
else :
+
raise HTTPException ( status_code = 500 ,
+
detail = "Не удалось включить UFW" )
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . post ( "/disable" )
+
async def disable_ufw ():
+
"""Выключает UFW.
+
+
Returns:
+
dict: Словарь с ключами `status` и `message` при успешном выполнении.
+
+
Raises:
+
HTTPException: Если выключение завершилось неуспешно (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
success = ufw_service . disable_ufw ()
+
if success :
+
return { "status" : "success" , "message" : "UFW успешно выключен" }
+
else :
+
raise HTTPException ( status_code = 500 ,
+
detail = "Не удалось выключить UFW" )
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . post ( "/rules/in" )
+
async def add_ufw_in_rules ( rules : List [ UFWRule ]):
+
"""Добавляет входящие (in) правила UFW.
+
+
Args:
+
rules (List[UFWRule]): Список правил, определяющих направления,
+
порты/диапазоны/протоколы и действия (allow/deny/reject).
+
+
Returns:
+
dict: Итог выполнения с количеством успешно добавленных правил и деталями
+
по каждому правилу.
+
+
Raises:
+
HTTPException: Если UFW отключен (status_code=400) или при внутренних
+
ошибках (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
# Проверяем статус UFW
+
status = ufw_service . get_ufw_status ()
+
if not status . enabled :
+
raise HTTPException (
+
status_code = 400 ,
+
detail = "UFW отключен. Включите UFW перед добавлением правил." ,
+
)
+
+
result = ufw_service . add_rules ( rules )
+
if result [ "success" ]:
+
successful_rules = len (
+
[ r for r in result [ "results" ] if r [ "success" ]])
+
total_rules = len ( result [ "results" ])
+
return {
+
"status" : "success" ,
+
"message" :
+
f "Добавлено { successful_rules } из { total_rules } правил" ,
+
"details" : result ,
+
}
+
else :
+
raise HTTPException (
+
status_code = 500 ,
+
detail = result . get ( "error" , "Не удалось добавить правила" ),
+
)
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . post ( "/rules/out" )
+
async def add_ufw_out_rules ( rules : List [ UFWRule ]):
+
"""Добавляет исходящие (out) правила UFW.
+
+
Args:
+
rules (List[UFWRule]): Список правил исходящего трафика для добавления.
+
+
Returns:
+
dict: Объект статуса с сообщением об успешном добавлении.
+
+
Raises:
+
HTTPException: Если UFW отключен (status_code=400) или при внутренних
+
ошибках (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
# Проверяем статус UFW
+
status = ufw_service . get_ufw_status ()
+
if not status . enabled :
+
raise HTTPException (
+
status_code = 400 ,
+
detail = "UFW отключен. Включите UFW перед добавлением правил." ,
+
)
+
+
success = ufw_service . add_ufw_out_rules ( rules )
+
if success :
+
return {
+
"status" : "success" ,
+
"message" : "Исходящие правила UFW успешно добавлены" ,
+
}
+
else :
+
raise HTTPException (
+
status_code = 500 ,
+
detail = "Не удалось добавить исходящие правила UFW" )
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
[docs]
+
@router . delete ( "/rules" )
+
async def delete_ufw_rules ( rule_numbers : RuleNumbers ):
+
"""Удаляет правила UFW по их номерам.
+
+
Args:
+
rule_numbers (RuleNumbers): Номера правил, подлежащих удалению (как в
+
выводе `ufw status numbered`).
+
+
Returns:
+
dict: Информация об общем количестве удаляемых правил, числе успешных и
+
возможной ошибке.
+
+
Raises:
+
HTTPException: При ошибках удаления (status_code=500).
+
"""
+
ufw_service = UFWService ()
+
try :
+
result = ufw_service . delete_rules ( rule_numbers . numbers )
+
if result [ "success" ]:
+
return {
+
"status" : "success" ,
+
"message" :
+
f "Удалено { result [ 'successful' ] } из { result [ 'total' ] } правил" ,
+
"details" : result ,
+
}
+
else :
+
raise HTTPException ( status_code = 500 , detail = result [ "error" ])
+
except Exception as e :
+
raise HTTPException ( status_code = 500 , detail = f "Error: { str ( e ) } " )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/api/v1/websocket.html b/SRV/docs/_build/html/_modules/api/v1/websocket.html
new file mode 100644
index 0000000..de6a1be
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/api/v1/websocket.html
@@ -0,0 +1,488 @@
+
+
+
+
+
+
+
+ api.v1.websocket - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for api.v1.websocket
+import asyncio
+import logging
+from typing import Set
+
+from fastapi import APIRouter , WebSocket , WebSocketDisconnect , Depends
+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
+
+router = APIRouter ()
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
class WebSocketManager :
+
"""Класс для управления подключениями к WebSocket-серверу.
+
+
Обрабатывает подключение, отключение и трансляцию сообщений для клиентов,
+
подключённых через WebSocket.
+
"""
+
+
def __init__ ( self ):
+
"""Инициализирует менеджер подключений к WebSocket-серверу.
+
+
Создаёт пустое множество для хранения активных соединений.
+
"""
+
self . active_connections : Set [ WebSocket ] = set ()
+
+
+
[docs]
+
async def connect ( self , websocket : WebSocket ):
+
"""Подключает клиента к WebSocket-серверу.
+
+
Принимает подключение и добавляет WebSocket в список активных соединений.
+
+
Args:
+
websocket (WebSocket): Объект WebSocket для подключения.
+
+
Raises:
+
Exception: Если произошла ошибка при принятии подключения.
+
"""
+
await websocket . accept ()
+
self . active_connections . add ( websocket )
+
+
+
+
[docs]
+
def disconnect ( self , websocket : WebSocket ):
+
"""Отключает клиента от WebSocket-серверу.
+
+
Удаляет WebSocket из списка активных соединений.
+
+
Args:
+
websocket (WebSocket): Объект WebSocket для отключения.
+
"""
+
self . active_connections . remove ( websocket )
+
+
+
+
[docs]
+
async def broadcast ( self , message : dict ):
+
"""Транслирует сообщения всем подключённым клиентам.
+
+
Отправляет JSON-данные всем активным соединениям, обрабатывая
+
отключения клиентов.
+
+
Args:
+
message (dict): Сообщение для трансляции в формате JSON.
+
+
Raises:
+
WebSocketDisconnect: Если клиент неожиданно отключился.
+
Exception: Если произошла другая ошибка при отправке.
+
"""
+
for connection in list ( self . active_connections ):
+
try :
+
if connection . client_state == WebSocketState . CONNECTED :
+
await connection . send_json ( message )
+
except WebSocketDisconnect :
+
self . disconnect ( connection )
+
except Exception as e :
+
logger . error ( f "Broadcast error: { e } " )
+
self . disconnect ( connection )
+
+
+
+manager = WebSocketManager ()
+
+
+
[docs]
+
@router . websocket ( "/api/v1" )
+
async def websocket_endpoint ( websocket : WebSocket , translations : Translations = Depends ( get_translations ())):
+
"""Обработчик WebSocket-соединения.
+
+
Управляет соединением клиента, принимает данные для обновления позиций
+
контейнеров и транслирует сетевые данные.
+
+
Args:
+
websocket (WebSocket): Объект WebSocket для обработки соединения.
+
translations: Объект переводов для текущего языка.
+
+
Raises:
+
WebSocketDisconnect: Если клиент отключился.
+
Exception: Если произошла ошибка при обработке данных.
+
"""
+
await manager . connect ( websocket )
+
try :
+
while True :
+
data = await websocket . receive_json ()
+
logger . info ( f "Received data: { data } " )
+
+
if all ( key in data for key in [ "container_id" , "container_x" , "container_y" ]):
+
await update_container_position ( data )
+
+
network_data = await get_network_data ()
+
logger . info ( f "Current network data: { network_data } " )
+
+
await manager . broadcast ( network_data )
+
await asyncio . sleep ( 2 )
+
except WebSocketDisconnect :
+
logger . info ( "Client disconnected" )
+
except Exception as e :
+
logger . error ( f "Error in websocket_endpoint: { e } " )
+
finally :
+
manager . disconnect ( websocket )
+
if websocket . client_state != WebSocketState . DISCONNECTED :
+
await websocket . close ()
+
+
+
+
[docs]
+
@router . on_event ( "startup" )
+
async def startup_event ():
+
"""Запуск таска для трансляции сетевых данных при старте приложения.
+
+
Создаёт асинхронную задачу для периодической трансляции данных.
+
+
Raises:
+
Exception: Если произошла ошибка при запуске таска.
+
"""
+
asyncio . create_task ( broadcast_data ())
+
+
+
+
[docs]
+
async def broadcast_data ( translations : Translations = Depends ( get_translations ())):
+
"""Таск для периодической трансляции сетевых данных.
+
+
Выполняет сканирование сети и отправляет данные всем подключённым клиентам
+
с интервалом в 2 секунды.
+
+
Args:
+
translations: Объект переводов для текущего языка.
+
+
Raises:
+
Exception: Если произошла ошибка при получении данных или трансляции.
+
"""
+
while True :
+
try :
+
network_data = await get_network_data ()
+
if "activation" not in network_data :
+
logger . error ( translations . gettext ( "Ошибка: Ключ 'activation' отсутствует в полученных данных сети" ))
+
await asyncio . sleep ( 2 )
+
continue
+
+
await manager . broadcast ( network_data )
+
except Exception as e :
+
logger . error ( f "Error in broadcast_data: { e } " )
+
await asyncio . sleep ( 2 )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/config/cors.html b/SRV/docs/_build/html/_modules/config/cors.html
new file mode 100644
index 0000000..5288c7d
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/config/cors.html
@@ -0,0 +1,368 @@
+
+
+
+
+
+
+
+ config.cors - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for config.cors
+"""
+Конфигурация CORS для FastAPI-приложения GONETS.
+
+Модуль инкапсулирует добавление middleware `CORSMiddleware` со списком
+разрешённых источников (origins), характерных для локальной разработки и
+типовых портов фронтенда (например, Vite/React на 5173/5174, dev-серверы).
+
+Использование:
+ cors_config = CORSMiddlewareConfig(app)
+ cors_config.add_cors_middleware()
+
+Поведение:
+ - Разрешены все методы (`allow_methods=["*"]`) и заголовки (`allow_headers=["*"]`).
+ - Разрешены креденшлы (`allow_credentials=True`) для работы с куками (локаль и др.).
+ - При необходимости список `origins` можно расширить или параметризовать
+ на основе конфигурационного файла окружения.
+"""
+from fastapi import FastAPI
+from starlette.middleware.cors import CORSMiddleware
+
+
+
+
[docs]
+
class CORSMiddlewareConfig :
+
"""
+
Класс для настройки CORS политики в FastAPI
+
"""
+
+
def __init__ ( self , app : FastAPI ) -> None :
+
"""Инициализируем приложение FastAPI и список допустимых CORS-оригинов"""
+
self . app = app
+
self . origins = [
+
"http://localhost.tiangolo.com" ,
+
"https://localhost.tiangolo.com" ,
+
"http://localhost" ,
+
"http://localhost:8080" ,
+
"http://localhost:5174" ,
+
"http://localhost:5173" ,
+
]
+
+
+
[docs]
+
def add_cors_middleware ( self ) -> None :
+
"""Добавляем CORS в приложение FastAPI"""
+
self . app . add_middleware (
+
CORSMiddleware ,
+
allow_origins = self . origins ,
+
allow_credentials = True ,
+
allow_methods = [ "*" ],
+
allow_headers = [ "*" ],
+
)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/config/init_config.html b/SRV/docs/_build/html/_modules/config/init_config.html
new file mode 100644
index 0000000..707f1dd
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/config/init_config.html
@@ -0,0 +1,366 @@
+
+
+
+
+
+
+
+ config.init_config - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for config.init_config
+import configparser
+import os
+import secrets
+import logging
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+
+
+
[docs]
+
def init_config ():
+
"""
+
Инициализация конфигурации приложения (JWT SECRET_KEY).
+
+
Назначение:
+
- Создаёт файл `/etc/gonets/config.ini` с секцией `[JWT]` и случайным
+
`secret_key`, если он отсутствует. Устанавливает права `0o600`.
+
+
Замечания:
+
- Функция предназначена для однократного запуска (в инсталляторе/скрипте
+
подготовки окружения) и безопасно пропускает работу, если файл уже существует.
+
"""
+
config_path = "/etc/gonets/config.ini"
+
config_dir = os . path . dirname ( config_path )
+
+
if os . path . exists ( config_path ):
+
logger . info (
+
f "Конфигурационный файл { config_path } уже существует, пропускаем инициализацию"
+
)
+
return
+
+
try :
+
secret_key = secrets . token_urlsafe ( 32 )
+
os . makedirs ( config_dir , exist_ok = True )
+
config = configparser . ConfigParser ()
+
config [ "JWT" ] = { "secret_key" : secret_key }
+
with open ( config_path , "w" ) as config_file :
+
config . write ( config_file )
+
os . chmod ( config_path , 0o600 )
+
logger . info ( f "Создан конфигурационный файл { config_path } " )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при создании конфигурационного файла: { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+# Нативный запуск для тестов
+if __name__ == "__main__" :
+ init_config ()
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/config/ports.html b/SRV/docs/_build/html/_modules/config/ports.html
new file mode 100644
index 0000000..0bb3985
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/config/ports.html
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
+ config.ports - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for config.ports
+from typing import Dict , List
+
+
+
+
[docs]
+
class PortConfig :
+
"""Конфигурация известных портов для сетевого анализа.
+
+
Назначение:
+
Предоставляет сопоставление распространённых портов с человекочитаемыми
+
названиями сервисов для отображения в UI и логах. Поддерживает расширение
+
пользовательским словарём.
+
"""
+
+
_DEFAULT_PORTS : Dict [ int , str ] = {
+
20 : "FTP (Data)" ,
+
21 : "FTP (Control)" ,
+
22 : "SSH" ,
+
23 : "Telnet" ,
+
25 : "SMTP" ,
+
53 : "DNS" ,
+
67 : "DHCP (Server)" ,
+
68 : "DHCP (Client)" ,
+
69 : "TFTP" ,
+
80 : "HTTP" ,
+
110 : "POP3" ,
+
123 : "NTP (Network Time Protocol)" ,
+
137 : "NetBIOS Name Service" ,
+
138 : "NetBIOS Datagram Service" ,
+
139 : "NetBIOS Session Service" ,
+
143 : "IMAP" ,
+
161 : "SNMP" ,
+
162 : "SNMP Trap" ,
+
179 : "BGP (Border Gateway Protocol)" ,
+
443 : "HTTPS" ,
+
445 : "SMB (Microsoft-DS)" ,
+
465 : "SMTPS" ,
+
514 : "Syslog (UDP)" ,
+
515 : "LPD (Printer)" ,
+
587 : "SMTP Submission" ,
+
631 : "IPP (Printing)" ,
+
993 : "IMAPS" ,
+
995 : "POP3S" ,
+
# --- Базы данных ---
+
1433 : "Microsoft SQL Server" ,
+
1521 : "Oracle DB" ,
+
3306 : "MySQL/MariaDB" ,
+
5432 : "PostgreSQL" ,
+
6379 : "Redis" ,
+
27017 : "MongoDB" ,
+
# --- VPN и удалённый доступ ---
+
1723 : "PPTP VPN" ,
+
1701 : "L2TP" ,
+
500 : "IPSec" ,
+
4500 : "IPSec NAT-T" ,
+
1194 : "OpenVPN" ,
+
3389 : "RDP (Remote Desktop Protocol)" ,
+
5900 : "VNC" ,
+
# --- Прокси / Веб ---
+
1080 : "SOCKS Proxy" ,
+
8000 : "HTTP-Alt" ,
+
8080 : "HTTP Proxy" ,
+
8443 : "HTTPS-Alt" ,
+
# --- DevOps / Monitoring / Tools ---
+
9000 : "SonarQube / PHP-FPM" ,
+
9100 : "Prometheus Node Exporter" ,
+
9200 : "Elasticsearch HTTP" ,
+
9300 : "Elasticsearch Cluster" ,
+
11211 : "Memcached" ,
+
# --- Прочее ---
+
6667 : "IRC" ,
+
4000 : "ICQ / Custom apps" ,
+
25565 : "Minecraft Server" ,
+
53 : "DNS (UDP & TCP)" ,
+
}
+
+
def __init__ ( self , custom_ports : Dict [ int , str ] = None ):
+
"""
+
Инициализирует конфигурацию портов.
+
+
Args:
+
custom_ports: Пользовательский словарь портов для добавления или переопределения.
+
"""
+
self . _ports = self . _DEFAULT_PORTS . copy ()
+
if custom_ports :
+
self . _ports . update ( custom_ports )
+
+
+
[docs]
+
def get_port_name ( self , port : int ) -> str :
+
"""
+
Возвращает название сервиса для указанного порта.
+
+
Args:
+
port: Номер порта.
+
+
Returns:
+
Название сервиса или строка вида 'Port-<port>'.
+
"""
+
return self . _ports . get ( port , f "Port- { port } " )
+
+
+
+
[docs]
+
def get_ports ( self ) -> List [ int ]:
+
"""
+
Возвращает список всех номеров портов.
+
+
Returns:
+
Список номеров портов.
+
"""
+
return list ( self . _ports . keys ())
+
+
+
+
+port_config = PortConfig ()
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/core/cli_executor.html b/SRV/docs/_build/html/_modules/core/cli_executor.html
new file mode 100644
index 0000000..90456d7
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/core/cli_executor.html
@@ -0,0 +1,351 @@
+
+
+
+
+
+
+
+ core.cli_executor - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for core.cli_executor
+import subprocess
+from typing import Tuple
+
+
+
+
[docs]
+
class CLIExecutor :
+
"""Утилита для безопасного выполнения CLI-команд.
+
+
Назначение:
+
Инкапсулирует вызов `subprocess.run` и приводит результат к удобному
+
кортежу (код возврата, stdout) без генерации исключений при ошибках.
+
"""
+
+
+
[docs]
+
@staticmethod
+
def run ( command : list ) -> Tuple [ int , str ]:
+
"""
+
Выполняет команду в системе и возвращает код и вывод.
+
+
Args:
+
command (list): Список аргументов команды для выполнения.
+
+
Returns:
+
tuple: Код возврата и вывод команды.
+
"""
+
result = subprocess . run (
+
command ,
+
stdout = subprocess . PIPE ,
+
stderr = subprocess . PIPE ,
+
check = False , # Не вызываем исключение при ошибке
+
)
+
return result . returncode , result . stdout . decode ( "utf-8" )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/index.html b/SRV/docs/_build/html/_modules/index.html
new file mode 100644
index 0000000..a68c954
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/index.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+ Overview: module code - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ All modules for which code is available
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/auth.html b/SRV/docs/_build/html/_modules/models/auth.html
new file mode 100644
index 0000000..afe07e1
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/auth.html
@@ -0,0 +1,389 @@
+
+
+
+
+
+
+
+ models.auth - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.auth
+from pydantic import BaseModel
+from typing import Optional
+
+
+
+
[docs]
+
class UserCreate ( BaseModel ):
+
"""Модель созданного пользователя.
+
+
Атрибуты:
+
+
- ``username`` (str): Имя пользователя.
+
- ``password`` (str): Пароль пользователя.
+
- ``is_admin`` (bool): Флаг административного доступа.
+
"""
+
+
username : str
+
password : str
+
is_admin : bool = False
+
+
+
+
+
[docs]
+
class UserLogin ( BaseModel ):
+
"""Модель авторизации пользователя.
+
+
Атрибуты:
+
+
- ``username`` (str): Имя пользователя.
+
- ``password`` (str): Пароль пользователя.
+
"""
+
+
username : str
+
password : str
+
+
+
+
+
[docs]
+
class UserResponse ( BaseModel ):
+
"""Модель, используемая при возврате сведений о пользователе.
+
+
Атрибуты:
+
+
- ``id`` (int): Идентификатор пользователя.
+
- ``username`` (str): Имя пользователя.
+
- ``is_admin`` (bool): Флаг административного доступа.
+
"""
+
+
id : int
+
username : str
+
is_admin : bool
+
+
+
+
+
[docs]
+
class Token ( BaseModel ):
+
"""Модель JWT-токена.
+
+
Атрибуты:
+
+
- ``access_token`` (str): Значение токена (в формате JWT).
+
- ``token_type`` (str): Тип токена (обычно ``bearer``).
+
- ``session_id`` (Optional[str]): Идентификатор сессии.
+
- ``user`` (Optional[UserResponse]): Данные пользователя, если включены в ответ.
+
"""
+
+
access_token : str
+
token_type : str
+
session_id : Optional [ str ] = None
+
user : Optional [ UserResponse ] = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/network.html b/SRV/docs/_build/html/_modules/models/network.html
new file mode 100644
index 0000000..58d8e9e
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/network.html
@@ -0,0 +1,511 @@
+
+
+
+
+
+
+
+ models.network - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.network
+from typing import List , Optional , Union
+from pydantic import BaseModel
+
+
+
+
[docs]
+
class IPv4Route ( BaseModel ):
+
"""Модель маршрута IPv4.
+
+
Атрибуты:
+
+
- ``destination`` (str): IP-адрес назначения.
+
- ``prefix`` (int): Префикс подсети в формате CIDR.
+
- ``next_hop`` (str): Адрес следующего узла (маршрутизатор).
+
- ``metric`` (int): Метрика маршрута.
+
"""
+
+
destination : str
+
prefix : int
+
next_hop : str
+
metric : int
+
+
+
+
+
[docs]
+
class IPv6Route ( BaseModel ):
+
"""Модель маршрута IPv6.
+
+
Атрибуты:
+
+
- ``destination`` (str): IP-адрес назначения.
+
- ``prefix`` (int): Префикс подсети в формате CIDR.
+
- ``next_hop`` (str): Адрес следующего узла.
+
- ``metric`` (int): Метрика маршрута.
+
"""
+
+
destination : str
+
prefix : int
+
next_hop : str
+
metric : int
+
+
+
+
+
[docs]
+
class IndividualIPConfig ( BaseModel ):
+
"""Модель индивидуальной конфигурации IP-адреса.
+
+
Атрибуты:
+
+
- ``address`` (str): IP-адрес интерфейса.
+
- ``routes`` (List[Union[IPv4Route, IPv6Route]]): Список маршрутов.
+
- ``method`` (Optional[str]): Метод назначения IP.
+
- ``gateway`` (str): Шлюз по умолчанию.
+
- ``dns`` (List[str]): Список DNS-серверов.
+
"""
+
+
address : str
+
routes : List [ Union [ IPv4Route , IPv6Route ]]
+
method : Optional [ str ]
+
gateway : str
+
dns : List [ str ]
+
+
+
+
+
[docs]
+
class IPConfig ( BaseModel ):
+
"""Групповая конфигурация IP-адресов.
+
+
Атрибуты:
+
+
- ``configurations`` (List[IndividualIPConfig]): Индивидуальные IP-конфигурации.
+
"""
+
+
configurations : List [ IndividualIPConfig ]
+
+
+
+
+
[docs]
+
class NetworkInterface ( BaseModel ):
+
"""Модель базового сетевого интерфейса.
+
+
Атрибуты:
+
+
- ``name`` (str): Название интерфейса.
+
- ``type`` (str): Тип устройства.
+
- ``mac_address`` (str): MAC-адрес.
+
- ``device_state`` (str): Текущее состояние.
+
- ``active_connection`` (str): Название активного соединения.
+
- ``device_mtu`` (str): MTU устройства.
+
- ``ipv4`` (IPConfig): IPv4-конфигурация.
+
- ``ipv6`` (IPConfig): IPv6-конфигурация.
+
- ``container_x`` (int): Положение по оси X на холсте.
+
- ``container_y`` (int): Положение по оси Y на холсте.
+
"""
+
+
name : str
+
type : str
+
mac_address : str
+
device_state : str
+
active_connection : str
+
device_mtu : str
+
ipv4 : IPConfig
+
ipv6 : IPConfig
+
container_x : int = 0
+
container_y : int = 0
+
+
+
+
+
[docs]
+
class WiFiDevice ( NetworkInterface ):
+
"""Расширенная модель Wi-Fi устройства.
+
+
Атрибуты:
+
+
- ``ssid`` (Optional[str]): Название Wi-Fi сети.
+
- ``bssid`` (Optional[str]): MAC-адрес точки доступа.
+
- ``bitrate`` (Optional[int]): Скорость соединения.
+
- ``strength`` (Optional[int]): Уровень сигнала.
+
- ``frequency`` (Optional[float]): Рабочая частота.
+
- ``mode`` (Union[str, int]): Режим работы.
+
"""
+
+
ssid : Optional [ str ] = None
+
bssid : Optional [ str ] = None
+
bitrate : Optional [ int ] = None
+
strength : Optional [ int ] = None
+
frequency : Optional [ float ] = None
+
mode : Union [ str , int ] = None
+
+
+
+
+
[docs]
+
class NetworkInterfacesContainer ( BaseModel ):
+
"""Контейнер для набора сетевых интерфейсов.
+
+
Атрибуты:
+
+
- ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Список интерфейсов.
+
- ``container_x`` (int): Положение по оси X.
+
- ``container_y`` (int): Положение по оси Y.
+
"""
+
+
interfaces : List [ Union [ NetworkInterface , WiFiDevice ]]
+
container_x : int
+
container_y : int
+
+
+
+
+
[docs]
+
class Activation ( BaseModel ):
+
"""Модель узла активных интерфейсов.
+
+
Атрибуты:
+
+
- ``id`` (int): Идентификатор ноды.
+
- ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Активные интерфейсы.
+
- ``container_x`` (int): Координата X на холсте.
+
- ``container_y`` (int): Координата Y на холсте.
+
- ``isOpen`` (bool): Флаг отображения узла на холсте.
+
"""
+
+
id : int
+
interfaces : List [ Union [ NetworkInterface , WiFiDevice ]]
+
container_x : int
+
container_y : int
+
isOpen : bool = True
+
+
+
+
+
[docs]
+
class Deactivation ( BaseModel ):
+
"""Модель узла отключённых интерфейсов.
+
+
Атрибуты:
+
+
- ``id`` (int): Идентификатор ноды.
+
- ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Список отключённых интерфейсов.
+
- ``container_x`` (int): Координата X на холсте.
+
- ``container_y`` (int): Координата Y на холсте.
+
- ``isOpen`` (bool): Флаг отображения узла на холсте.
+
"""
+
+
id : int
+
interfaces : List [ Union [ NetworkInterface , WiFiDevice ]]
+
container_x : int
+
container_y : int
+
isOpen : bool = True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/request.html b/SRV/docs/_build/html/_modules/models/request.html
new file mode 100644
index 0000000..ea70831
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/request.html
@@ -0,0 +1,356 @@
+
+
+
+
+
+
+
+ models.request - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.request
+from pydantic import BaseModel
+
+
+
+
[docs]
+
class NoteRequest ( BaseModel ):
+
"""Модель запроса на добавление заметки к устройству.
+
+
Атрибуты:
+
+
- ``note`` (str): Текст заметки.
+
"""
+
+
note : str
+
+
+
+
[docs]
+
class WifiConnectRequest ( BaseModel ):
+
"""Модель запроса на подключение к Wi-Fi.
+
+
Атрибуты:
+
+
- ``ssid`` (str): Название точки доступа.
+
- ``password`` (str): Пароль доступа.
+
"""
+
ssid : str
+
password : str
+
+
+
+
[docs]
+
class WifiDisconnectRequest ( BaseModel ):
+
"""Модель запроса на отключение от Wi-Fi.
+
+
Атрибуты:
+
+
- ``ssid`` (str): Название точки доступа.
+
"""
+
ssid : str
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/subnet.html b/SRV/docs/_build/html/_modules/models/subnet.html
new file mode 100644
index 0000000..751d67c
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/subnet.html
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+ models.subnet - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.subnet
+from pydantic import BaseModel
+from typing import Optional , List
+
+
+
[docs]
+
class Device ( BaseModel ):
+
"""Модель устройства, обнаруженного в подсети.
+
+
Атрибуты:
+
+
- ``ip`` (str): IP-адрес устройства.
+
- ``mac`` (Optional[str]): MAC-адрес устройства.
+
- ``hostname`` (Optional[str]): Имя хоста.
+
- ``vendor`` (Optional[str]): Производитель оборудования.
+
- ``open_ports`` (List[str]): Открытые порты.
+
- ``status`` (str): Состояние устройства (online/offline).
+
- ``last_seen`` (Optional[str]): Время последнего обнаружения.
+
- ``note`` (Optional[str]): Пользовательская заметка.
+
"""
+
+
ip : str
+
mac : Optional [ str ] = None
+
hostname : Optional [ str ] = None
+
vendor : Optional [ str ] = None
+
open_ports : List [ str ] = []
+
status : str
+
last_seen : Optional [ str ] = None
+
note : Optional [ str ] = None
+
+
+
+
[docs]
+
class SubnetScanResult ( BaseModel ):
+
"""Результат сканирования подсети.
+
+
Атрибуты:
+
+
- ``devices`` (List[Device]): Найденные устройства.
+
- ``error`` (Optional[str]): Сообщение об ошибке сканирования.
+
"""
+
+
devices : List [ Device ] = []
+
error : Optional [ str ] = None
+
+
+
+
[docs]
+
class SubnetScanRequest ( BaseModel ):
+
"""Параметры запроса на сканирование подсети.
+
+
Атрибуты:
+
+
- ``subnet`` (Optional[str]): CIDR подсети (например, ``192.168.1.0/24``).
+
"""
+
subnet : Optional [ str ] = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/ufw.html b/SRV/docs/_build/html/_modules/models/ufw.html
new file mode 100644
index 0000000..6028b30
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/ufw.html
@@ -0,0 +1,635 @@
+
+
+
+
+
+
+
+ models.ufw - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.ufw
+from typing import List , Optional , Annotated , Union
+from enum import Enum
+from pydantic import BaseModel , Field , validator
+import ipaddress
+
+
+
+
[docs]
+
class Protocol ( str , Enum ):
+
"""
+
Enum для сетевых протоколов UFW.
+
"""
+
+
TCP = "tcp"
+
UDP = "udp"
+
BOTH = "both"
+
+
@property
+
def protocols ( self ) -> List [ str ]:
+
"""Возвращает список протоколов для использования в команде UFW."""
+
return [ "tcp" , "udp" ] if self == Protocol . BOTH else [ self . value ]
+
+
+
+
+
[docs]
+
class Action ( str , Enum ):
+
"""
+
Enum для действий UFW.
+
+
Attributes:
+
ALLOW: Разрешить трафик
+
DENY: Запретить трафик без уведомления
+
REJECT: Запретить трафик с уведомлением отправителю
+
"""
+
+
ALLOW = "allow"
+
DENY = "deny"
+
REJECT = "reject"
+
+
+
+
+
[docs]
+
class Direction ( str , Enum ):
+
"""
+
Enum для направления трафика UFW.
+
+
Attributes:
+
IN: Входящий трафик
+
OUT: Исходящий трафик
+
"""
+
+
IN = "in"
+
OUT = "out"
+
+
+
+
+
[docs]
+
class UFWRule ( BaseModel ):
+
"""Модель правила UFW"""
+
+
action : Action
+
direction : Direction
+
from_ip : str
+
to_ip : str
+
port : Union [ int , str ]
+
protocol : Protocol
+
comment : Optional [ str ] = None
+
+
+
[docs]
+
@validator ( "port" )
+
def validate_port ( cls , v ):
+
"""
+
Проверяет корректность порта или диапазона портов.
+
+
Args:
+
v: Порт (число) или диапазон портов (строка вида "3000:3999")
+
+
Returns:
+
Union[int, str]: Валидированное значение порта
+
+
Raises:
+
ValueError: Если порт или диапазон портов некорректен
+
"""
+
if isinstance ( v , int ):
+
if not ( 1 <= v <= 65535 ):
+
raise ValueError (
+
f "Порт должен быть в диапазоне 1-65535, получено: { v } " )
+
return v
+
elif isinstance ( v , str ):
+
try :
+
start , end = map ( int , v . split ( ":" ))
+
if not ( 1 <= start <= end <= 65535 ):
+
raise ValueError
+
return v
+
except ( ValueError , TypeError ):
+
raise ValueError (
+
f "Некорректный диапазон портов: { v } . Формат: start:end, где 1 <= start <= end <= 65535"
+
)
+
else :
+
raise ValueError (
+
f "Порт должен быть числом или строкой с диапазоном, получено: { type ( v ) } "
+
)
+
+
+
+
[docs]
+
@validator ( "from_ip" , "to_ip" )
+
def validate_ip ( cls , v ):
+
"""Проверяет корректность IP адреса"""
+
try :
+
# Пробуем создать объект IP-сети, что автоматически проверит корректность адреса
+
ipaddress . ip_network ( v , strict = False )
+
return v
+
except ValueError :
+
raise ValueError ( f "Некорректный IP адрес или подсеть: { v } " )
+
+
+
+
[docs]
+
@validator ( "to_ip" )
+
def validate_ip_version_compatibility ( cls , v , values ):
+
"""Проверяет совместимость версий IP адресов"""
+
if "from_ip" not in values :
+
return v
+
+
# Определяем версии IP
+
from_ip = ipaddress . ip_network ( values [ "from_ip" ], strict = False )
+
to_ip = ipaddress . ip_network ( v , strict = False )
+
+
if isinstance ( from_ip , ipaddress . IPv6Network ) != isinstance (
+
to_ip , ipaddress . IPv6Network ):
+
raise ValueError (
+
"Несовместимые версии IP: нельзя смешивать IPv4 и IPv6 в одном правиле"
+
)
+
+
return v
+
+
+
+
[docs]
+
@validator ( "action" , pre = True )
+
def validate_action ( cls , v ):
+
"""Приводит значение action к нижнему регистру перед валидацией"""
+
if isinstance ( v , str ):
+
return v . lower ()
+
return v
+
+
+
+
[docs]
+
@validator ( "protocol" , pre = True )
+
def validate_protocol ( cls , v ):
+
"""Приводит значение protocol к нижнему регистру перед валидацией"""
+
if isinstance ( v , str ):
+
return v . lower ()
+
return v
+
+
+
+
[docs]
+
def to_ufw_command ( self ) -> str :
+
"""Преобразует правило в команду UFW."""
+
cmd_parts = [ "ufw" ]
+
+
# Добавляем действие
+
cmd_parts . append ( self . action . value )
+
+
# Добавляем направление
+
cmd_parts . append ( self . direction . value )
+
+
# Добавляем from с IP
+
cmd_parts . extend ([ "from" , self . from_ip ])
+
+
# Добавляем to с IP
+
cmd_parts . extend ([ "to" , self . to_ip ])
+
+
# Добавляем порт и протокол
+
port_str = str (
+
self . port ) # Работает как с числами, так и с диапазонами
+
cmd_parts . extend ([ "port" , port_str ])
+
+
# Добавляем протокол
+
proto = self . protocol . value
+
if proto == "both" :
+
cmd_parts . append ( "proto tcp/udp" )
+
else :
+
cmd_parts . extend ([ "proto" , proto ])
+
+
# Добавляем комментарий если есть
+
if self . comment :
+
cmd_parts . extend ([ "comment" , f "' { self . comment } '" ])
+
+
return " " . join ( cmd_parts )
+
+
+
+
[docs]
+
@classmethod
+
def from_ufw_output ( cls , line : str ) -> "UFWRule" :
+
"""
+
Создает экземпляр UFWRule из строки вывода команды 'ufw status numbered'.
+
+
Args:
+
line: Строка вывода UFW (например, "[1] 80/tcp ALLOW IN 192.168.1.0/24 # Web Server")
+
+
Returns:
+
UFWRule: Новый экземпляр правила
+
"""
+
import re
+
+
# Регулярное выражение для разбора строки правила UFW
+
pattern = r """^\s*
+
\[\s*(?P<number>\d+)\s*\]\s*
+
(?P<port>\d+(?::\d+)?)/(?P<protocol>tcp(?:/udp)?|udp)\s+
+
(?P<action>ALLOW|DENY|REJECT)\s+(?P<direction>IN|OUT)\s+
+
(?P<from_ip>[\w\d\./]+|\bAnywhere\b)
+
(?:\s+\(v6\))?
+
(?:\s+\((?:out|in)\))?
+
\s*
+
(?:\#\s*(?P<comment>.*?))?
+
\s*$
+
"""
+
+
match = re . match ( pattern , line , re . VERBOSE | re . IGNORECASE )
+
if not match :
+
raise ValueError (
+
f "Невозможно разобрать строку правила UFW: { line } " )
+
+
groups = match . groupdict ()
+
+
# Преобразуем протокол в enum
+
proto_str = groups [ "protocol" ] . lower ()
+
protocol = ( Protocol . BOTH if proto_str == "tcp/udp" else
+
Protocol . UDP if proto_str == "udp" else Protocol . TCP )
+
+
# Преобразуем действие в enum (игнорируем регистр)
+
action_str = groups [ "action" ] . lower ()
+
action = ( Action . ALLOW if action_str == "allow" else
+
Action . DENY if action_str == "deny" else Action . REJECT )
+
+
# Преобразуем направление в enum
+
direction_str = groups [ "direction" ] . lower ()
+
direction = Direction . IN if direction_str == "in" else Direction . OUT
+
+
# Обрабатываем IP адреса
+
from_ip = groups [ "from_ip" ] . strip ()
+
if from_ip . lower () == "anywhere" :
+
from_ip = "0.0.0.0/0"
+
+
# Для исходящих правил меняем местами from и to
+
to_ip = "0.0.0.0/0" # По умолчанию anywhere
+
+
# Обрабатываем порт
+
port = groups [ "port" ]
+
if ":" in port :
+
# Оставляем как строку для диапазона
+
port = port . strip ()
+
else :
+
# Преобразуем в число для одиночного порта
+
port = int ( port )
+
+
# Обрабатываем комментарий
+
comment = groups . get ( "comment" ,
+
"" ) . strip () if groups . get ( "comment" ) else None
+
+
return cls (
+
action = action ,
+
direction = direction ,
+
from_ip = from_ip ,
+
to_ip = to_ip ,
+
port = port ,
+
protocol = protocol ,
+
comment = comment ,
+
)
+
+
+
+
+
+
[docs]
+
class UFWRulesResponse ( BaseModel ):
+
"""
+
Модель для представления списка правил UFW.
+
+
Attributes:
+
enabled: Включен ли UFW
+
rules: Список правил UFW
+
total_rules: Общее количество правил
+
"""
+
+
enabled : bool = Field ( ... , description = "Включен ли UFW" )
+
rules : List [ UFWRule ] = Field ( default_factory = list ,
+
description = "Список правил UFW" )
+
+
@property
+
def total_rules ( self ) -> int :
+
"""Возвращает общее количество правил."""
+
return len ( self . rules )
+
+
+
+
+
[docs]
+
class UFWStatus ( BaseModel ):
+
"""
+
Модель для представления статуса UFW.
+
+
Attributes:
+
enabled: Включен ли UFW
+
message: Сообщение о статусе
+
last_checked: Время последней проверки статуса
+
"""
+
+
enabled : bool = Field ( ... , description = "Включен ли UFW" )
+
message : str = Field ( ... , description = "Сообщение о статусе" )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/models/wifi.html b/SRV/docs/_build/html/_modules/models/wifi.html
new file mode 100644
index 0000000..c68745a
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/models/wifi.html
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+ models.wifi - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for models.wifi
+from pydantic import BaseModel
+from typing import Optional , List
+
+
+
+
[docs]
+
class WifiAccessPoint ( BaseModel ):
+
"""Модель Wi-Fi точки доступа.
+
+
Атрибуты:
+
+
- ``ssid`` (Optional[str]): Название сети (может быть скрытым).
+
- ``bssid`` (str): Уникальный идентификатор точки доступа.
+
- ``channel`` (Optional[int]): Номер канала.
+
- ``signal`` (Optional[int]): Уровень сигнала в dBm.
+
- ``encryption`` (Optional[str]): Тип шифрования (WPA/WPA2/WPA3/открытая).
+
- ``frequency`` (Optional[str]): Диапазон частот (2.4/5 ГГц).
+
- ``vendor`` (Optional[str]): Производитель оборудования.
+
- ``hidden`` (bool): Флаг скрытого SSID.
+
- ``note`` (Optional[str]): Пользовательская заметка.
+
"""
+
+
ssid : Optional [ str ] = None
+
bssid : str
+
channel : Optional [ int ] = None
+
signal : Optional [ int ] = None
+
encryption : Optional [ str ] = None
+
frequency : Optional [ str ] = None
+
vendor : Optional [ str ] = None
+
hidden : bool = False
+
note : Optional [ str ] = None
+
+
+
+
+
[docs]
+
class WifiScanResult ( BaseModel ):
+
"""Результат сканирования Wi-Fi.
+
+
Атрибуты:
+
+
- ``access_points`` (List[WifiAccessPoint]): Найденные точки доступа.
+
"""
+
+
access_points : List [ WifiAccessPoint ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/arp_service.html b/SRV/docs/_build/html/_modules/services/arp_service.html
new file mode 100644
index 0000000..d311770
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/arp_service.html
@@ -0,0 +1,523 @@
+
+
+
+
+
+
+
+ services.arp_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.arp_service
+"""
+Сервис низкоуровневого ARP-сканирования локальной сети.
+
+Назначение:
+
+- Обнаружение активных узлов в пределах сетей хоста путём широковещательных ARP-запросов от имени выбранного интерфейса.
+- Помощь сервисам более высокого уровня (например, IPService) при проверке занятости адресов перед применением конфигураций.
+
+Особенности реализации:
+
+- Определяет подключённые сети интерфейса через `netifaces` и строит CIDR для сканирования.
+- Формирует ARP-пакеты с помощью `scapy` (Ether/ARP) и отправляет их с таймаутом.
+- Добавляет локальный адрес интерфейса в результирующий список клиентов.
+
+Требования/ограничения:
+
+- Для отправки ARP-кадров могут потребоваться повышенные привилегии или capability на сырой сокет.
+"""
+import os
+import logging
+from fastapi import Depends
+import netifaces
+from netaddr import IPNetwork
+from utils.i18n import get_translations , _
+from babel.support import Translations
+
+logger = logging . getLogger ( __name__ )
+
+try :
+ from scapy.layers.l2 import ARP , Ether
+ from scapy.sendrecv import srp
+except Exception as exc : # pragma: no cover - fallback for doc builds
+ if os . getenv ( "SPHINX_BUILD" ):
+ ARP = Ether = None # type: ignore
+
+ def srp ( * args , ** kwargs ):
+ raise RuntimeError ( "scapy is unavailable during documentation build" ) from exc
+ else :
+ raise
+
+
+
[docs]
+
class NetworkScanner :
+
"""Инициализация сетевого сканера.
+
+
Предоставляет методы для сканирования сети с использованием ARP-запросов и
+
вывода информации о клиентах.
+
"""
+
+
def __init__ ( self , interface = None , translations : Translations = Depends ( get_translations ())):
+
"""Инициализация параметров сетевого сканера.
+
+
Args:
+
interface (str, optional): Название сетевого интерфейса. По умолчанию
+
используется интерфейс по умолчанию из netifaces.gateways().
+
translations: Объект переводов для текущего языка.
+
+
Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес.
+
"""
+
self . translations = translations
+
self . interface = None
+
self . ip_info = []
+
self . networks = []
+
self . local_mac = None
+
+
try :
+
candidate_interface = interface
+
if not candidate_interface :
+
gateways = netifaces . gateways ()
+
default_gateway = gateways . get ( "default" , {}) . get ( netifaces . AF_INET )
+
if default_gateway :
+
candidate_interface = default_gateway [ 1 ]
+
else :
+
for iface in netifaces . interfaces ():
+
if iface == "lo" :
+
continue
+
addrs = netifaces . ifaddresses ( iface )
+
if netifaces . AF_INET in addrs and addrs [ netifaces . AF_INET ]:
+
candidate_interface = iface
+
break
+
+
if candidate_interface :
+
self . interface = candidate_interface
+
addresses = netifaces . ifaddresses ( self . interface )
+
self . ip_info = addresses . get ( netifaces . AF_INET , [])
+
self . networks = [
+
IPNetwork (
+
f " { ip [ 'addr' ] } / { ip [ 'netmask' ] } "
+
) for ip in self . ip_info if ip . get ( "addr" ) and ip . get ( "netmask" )
+
]
+
link_info = addresses . get ( netifaces . AF_LINK , [])
+
if link_info :
+
self . local_mac = link_info [ 0 ] . get ( "addr" )
+
else :
+
logger . warning (
+
"NetworkScanner: IPv4 интерфейс не найден, сканирование будет недоступно"
+
)
+
except Exception as exc :
+
if os . getenv ( "SPHINX_BUILD" ):
+
self . interface = interface or "eth0"
+
self . ip_info = []
+
self . networks = []
+
self . local_mac = "00:00:00:00:00:00"
+
logger . warning (
+
"NetworkScanner falling back to mocked interface during documentation build: %s " ,
+
exc ,
+
)
+
else :
+
logger . error ( "NetworkScanner инициализация завершилась ошибкой: %s " , exc )
+
raise
+
+
def _create_arp_packet ( self , target_ip ):
+
"""Создаёт ARP-пакет для отправки на указанный IP-адрес.
+
+
Args:
+
target_ip (str): Целевой IP-адрес или подсеть для сканирования.
+
+
Returns:
+
scapy.packet.Packet: Сформированный ARP-пакет с Ethernet-заголовком.
+
"""
+
arp = ARP ( pdst = target_ip )
+
ether = Ether ( dst = "ff:ff:ff:ff:ff:ff" )
+
return ether / arp
+
+
+
[docs]
+
def scan_network ( self , attempts = 3 , timeout = 2 ):
+
"""Сканирует сеть для обнаружения активных клиентов.
+
+
Args:
+
attempts (int, optional): Количество попыток отправки ARP-запросов.
+
По умолчанию 3.
+
timeout (int, optional): Таймаут ожидания ответа в секундах.
+
По умолчанию 2.
+
+
Returns:
+
List[dict]: Список словарей с ключами "ip" и "mac", представляющих
+
обнаруженных клиентов, включая локальные IP-адреса.
+
+
Note:
+
Учитывает локальные IP-адреса интерфейса и удаляет дубликаты.
+
"""
+
if not self . interface or not self . networks or ARP is None or Ether is None :
+
fallback_clients = []
+
if self . ip_info :
+
for ip_info in self . ip_info :
+
fallback_clients . append ({
+
"ip" : ip_info . get ( "addr" ),
+
"mac" : self . local_mac ,
+
})
+
return [ client for client in fallback_clients if client [ "ip" ]]
+
+
all_clients = []
+
+
for network in self . networks :
+
target_ip = f " { network . network } / { network . prefixlen } "
+
packet = self . _create_arp_packet ( target_ip )
+
clients = []
+
+
for _ in range ( attempts ):
+
result = srp ( packet ,
+
timeout = timeout ,
+
iface = self . interface ,
+
verbose = 0 )[ 0 ]
+
for _ , received in result :
+
client = { "ip" : received . psrc , "mac" : received . hwsrc }
+
if client not in clients :
+
clients . append ( client )
+
+
all_clients . extend ( clients )
+
+
for ip_info in self . ip_info :
+
if not ip_info . get ( "addr" ):
+
continue
+
local_client = {
+
"ip" : ip_info [ "addr" ],
+
"mac" : self . local_mac ,
+
}
+
if local_client not in all_clients :
+
all_clients . append ( local_client )
+
+
unique_clients = { tuple ( client . items ()) for client in all_clients }
+
return [ dict ( client ) for client in unique_clients ]
+
+
+
+
[docs]
+
def print_clients ( self , clients ):
+
"""Выводит список адресов клиентов в формате таблицы.
+
+
Args:
+
clients (List[dict]): Список словарей с ключами "ip" и "mac",
+
представляющих клиентов.
+
+
Выводит IP-адреса и MAC-адреса в отсортированном по IP порядке.
+
"""
+
print ( self . translations . gettext ( "Доступные устройства в сети:" ))
+
print ( self . translations . gettext ( "IP" ) + " " * 18 + self . translations . gettext ( "MAC" ))
+
for client in sorted ( clients , key = lambda x : x [ "ip" ]):
+
print ( f " { client [ 'ip' ] : <16 } { client [ 'mac' ] } " )
+
+
+
+# Оставил нативный запуск сервиса для тестов
+if __name__ == "__main__" :
+ scanner = NetworkScanner ()
+ clients = scanner . scan_network ()
+ scanner . print_clients ( clients )
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/auth_service.html b/SRV/docs/_build/html/_modules/services/auth_service.html
new file mode 100644
index 0000000..34e4d34
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/auth_service.html
@@ -0,0 +1,492 @@
+
+
+
+
+
+
+
+ services.auth_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.auth_service
+from jose import JWTError , jwt
+from datetime import datetime , timedelta
+from fastapi import HTTPException , Depends , Request
+from fastapi.security import OAuth2PasswordBearer
+from models.auth import UserCreate , UserLogin , UserResponse , Token
+from storage.json_storage import JsonStorage
+from storage.session_storage import SessionStorage
+from typing import Optional
+import configparser
+import os
+import logging
+from utils.i18n import get_translations , _
+from babel.support import Translations
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+CONFIG_PATH = os . getenv ( "GONETS_CONFIG_PATH" , "/etc/gonets/config.ini" )
+config = configparser . ConfigParser ()
+read_files = config . read ( CONFIG_PATH )
+
+if "JWT" in config :
+ SECRET_KEY = config [ "JWT" ][ "secret_key" ]
+elif os . getenv ( "SPHINX_BUILD" ):
+ logger . warning ( "JWT secret not found; using dummy value for documentation build" )
+ SECRET_KEY = "sphinx-placeholder-secret"
+else :
+ raise RuntimeError ( f "Configuration file { CONFIG_PATH } not found or missing [JWT] section" )
+
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = 30
+
+oauth2_scheme = OAuth2PasswordBearer ( tokenUrl = "/auth/login" )
+
+
+
[docs]
+
class AuthService :
+
"""Сервис для регистрации, авторизации и управления сессиями"""
+
+
def __init__ ( self , translations : Translations = Depends ( get_translations ())):
+
self . storage = JsonStorage ()
+
self . session_storage = SessionStorage ()
+
self . translations = translations
+
+
+
[docs]
+
def create_access_token ( self , data : dict ) -> str :
+
"""Создаёт токен"""
+
to_encode = data . copy ()
+
expire = datetime . utcnow () + timedelta (
+
minutes = ACCESS_TOKEN_EXPIRE_MINUTES )
+
to_encode . update ({ "exp" : expire })
+
encoded_jwt = jwt . encode ( to_encode , SECRET_KEY , algorithm = ALGORITHM )
+
return encoded_jwt
+
+
+
+
[docs]
+
def verify_token ( self , token : str ) -> Optional [ dict ]:
+
"""Проверяет токен"""
+
try :
+
payload = jwt . decode ( token , SECRET_KEY , algorithms = [ ALGORITHM ])
+
username : str = payload . get ( "sub" )
+
if username is None :
+
return None
+
return { "username" : username }
+
except JWTError :
+
return None
+
+
+
+
[docs]
+
def register ( self , user : UserCreate ) -> UserResponse :
+
"""Регистрирует нового пользователя"""
+
try :
+
db_user = self . storage . create_user ( user )
+
return db_user
+
except ValueError as e :
+
logger . error (
+
f "Error registering user { user . username } : { str ( e ) } "
+
)
+
raise HTTPException ( status_code = 400 , detail = self . translations . gettext ( str ( e )))
+
except Exception as e :
+
logger . error (
+
f "Error registering user { user . username } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise HTTPException ( status_code = 500 , detail = self . translations . gettext ( "Ошибка регистрации" ))
+
+
+
+
[docs]
+
def login ( self , user : UserLogin , client_id : str ) -> dict :
+
"""Авторизует пользователя, создаёт сессию и возвращает токен"""
+
try :
+
db_user = self . storage . get_user_by_username ( user . username )
+
if not db_user :
+
logger . error ( f "User { user . username } not found" )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Неверный логин или пароль" ))
+
if not self . storage . verify_password ( user . password , db_user [ "password_hash" ]):
+
logger . error (
+
f "Invalid password for user { user . username } " )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Неверный логин или пароль" ))
+
access_token = self . create_access_token (
+
data = { "sub" : user . username })
+
session_id = self . session_storage . create_session (
+
user . username , access_token , client_id )
+
logger . info ( f "User { user . username } successfully logged in" )
+
return {
+
"access_token" : access_token ,
+
"token_type" : "bearer" ,
+
"session_id" : session_id ,
+
"user" : UserResponse ( id = db_user [ "id" ], username = db_user [ "username" ], is_admin = db_user [ "is_admin" ]),
+
}
+
except Exception as e :
+
logger . error (
+
f "Error logging in user { user . username } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise HTTPException ( status_code = 500 , detail = self . translations . gettext ( "Ошибка авторизации" ))
+
+
+
+
[docs]
+
def get_current_user (
+
self , token : str = Depends ( oauth2_scheme )) -> UserResponse :
+
"""Получает текущего пользователя по токену"""
+
credentials = self . verify_token ( token )
+
if not credentials :
+
logger . error ( "Invalid or expired token" )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Неверный токен" ))
+
username = credentials [ "username" ]
+
db_user = self . storage . get_user_by_username ( username )
+
if not db_user :
+
logger . error ( f "User { username } not found" )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Пользователь не найден" ))
+
return UserResponse ( id = db_user [ "id" ], username = db_user [ "username" ], is_admin = db_user [ "is_admin" ])
+
+
+
+
[docs]
+
def get_user_by_session ( self , client_id : str ) -> dict :
+
"""Получает пользователя и токен по client_id"""
+
session = self . session_storage . get_latest_session ( client_id )
+
if not session :
+
logger . error (
+
f "Session for client_id { client_id } not found or expired" )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Сессия недействительна" ))
+
username = session [ "username" ]
+
token = session [ "token" ]
+
db_user = self . storage . get_user_by_username ( username )
+
if not db_user :
+
logger . error ( f "User { username } not found" )
+
raise HTTPException ( status_code = 401 , detail = self . translations . gettext ( "Пользователь не найден" ))
+
return {
+
"user" :
+
UserResponse ( id = db_user [ "id" ], username = db_user [ "username" ], is_admin = db_user [ "is_admin" ]),
+
"access_token" : token
+
}
+
+
+
+
[docs]
+
def logout ( self , client_id : str ):
+
"""Выполняет выход из системы, удаляя последнюю сессию для client_id"""
+
try :
+
session = self . session_storage . get_latest_session ( client_id )
+
if session :
+
self . session_storage . delete_session ( session [ "session_id" ])
+
logger . info (
+
f "User successfully logged out for client_id { client_id } "
+
)
+
return { "message" : self . translations . gettext ( "Успешный выход из системы" )}
+
except Exception as e :
+
logger . error (
+
f "Error logging out: { type ( e ) . __name__ } - { str ( e ) } " )
+
raise HTTPException ( status_code = 500 , detail = self . translations . gettext ( "Ошибка при выходе из системы" ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/connect_service.html b/SRV/docs/_build/html/_modules/services/connect_service.html
new file mode 100644
index 0000000..df624b4
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/connect_service.html
@@ -0,0 +1,567 @@
+
+
+
+
+
+
+
+ services.connect_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.connect_service
+"""
+Сервис управления подключениями Wi-Fi через `nmcli`.
+
+Назначение:
+
+- Предоставляет операции подключения/отключения к точкам доступа и проверки текущего статуса, ориентируясь на системный NetworkManager.
+
+Поведение:
+
+- Автоматически определяет активный Wi-Fi-интерфейс.
+- Валидирует наличие SSID в листинге перед подключением и интерпретирует частые ошибки `nmcli` (неверный пароль, отсутствующая сеть и т.п.).
+
+Ограничения:
+
+- Требуются права для управления сетевыми соединениями в системе.
+- В окружениях без NetworkManager команды `nmcli` будут недоступны.
+"""
+import subprocess
+import logging
+from typing import Tuple , Optional
+from utils.i18n import get_translations , _
+from babel.support import Translations
+from fastapi import Depends
+
+logging . basicConfig ( level = logging . INFO , format = " %(asctime)s - %(name)s - %(levelname)s - %(message)s " )
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
class WifiConnectionService :
+
"""Сервис для управления подключением к Wi-Fi сетям с использованием nmcli.
+
+
Предоставляет методы для подключения, отключения и проверки статуса Wi-Fi соединений.
+
"""
+
+
def __init__ ( self , translations : Optional [ Translations ] = None ):
+
"""Инициализирует сервис Wi-Fi.
+
+
Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя.
+
"""
+
self . interface = self . _get_wifi_interface ()
+
if translations is None :
+
try :
+
self . translations = Translations . load ( "locales" , [ "ru" ], "messages" )
+
except Exception :
+
self . translations = Translations ()
+
else :
+
self . translations = translations
+
+
def _get_wifi_interface ( self ) -> str :
+
"""Автоматически определяет Wi-Fi-интерфейс.
+
+
Returns:
+
str: Название Wi-Fi-интерфейса (например, "wlan0") или пустая строка,
+
если интерфейс не найден или Wi-Fi отключён.
+
+
Raises:
+
Exception: Если произошла ошибка при выполнении команд nmcli.
+
"""
+
try :
+
result = subprocess . run (
+
[ "nmcli" , "radio" , "wifi" ], capture_output = True , text = True
+
)
+
if "enabled" not in result . stdout . lower ():
+
logger . error ( "Wi-Fi radio is disabled" )
+
return ""
+
+
interfaces_output = subprocess . run (
+
[ "nmcli" , "device" , "status" ], capture_output = True , text = True
+
) . stdout
+
+
for line in interfaces_output . splitlines ():
+
parts = line . split ()
+
if len ( parts ) < 2 :
+
continue
+
device_name , device_type = parts [ 0 ], parts [ 1 ]
+
if device_type . lower () == "wifi" :
+
logger . info ( f "Using Wi-Fi interface: { device_name } " )
+
return device_name
+
+
logger . warning ( "Wi-Fi interface not found in `nmcli device status` output" )
+
return ""
+
except Exception as e :
+
logger . error ( f "Error detecting Wi-Fi interface: { str ( e ) } " )
+
return ""
+
+
+
[docs]
+
def connect ( self , ssid : str , password : str ) -> Tuple [ bool , Optional [ str ]]:
+
"""Подключается к Wi-Fi сети по SSID и паролю.
+
+
Args:
+
ssid (str): Идентификатор сети Wi-Fi.
+
password (str): Пароль для подключения.
+
+
Returns:
+
Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
+
- Успех: (True, None).
+
- Ошибка: (False, сообщение об ошибке).
+
+
Raises:
+
subprocess.CalledProcessError: Если команда nmcli завершилась с ошибкой.
+
Exception: Если произошла неизвестная ошибка при выполнении.
+
"""
+
try :
+
if not self . interface :
+
return False , self . translations . gettext ( "Wi-Fi-интерфейс не доступен" )
+
+
result = subprocess . run (
+
[ "nmcli" , "-f" , "SSID" , "device" , "wifi" , "list" ],
+
capture_output = True ,
+
text = True ,
+
)
+
if ssid not in result . stdout :
+
return False , self . translations . gettext ( "Сеть с SSID {ssid} не найдена в списке доступных сетей" ) . format ( ssid = ssid )
+
+
def run_connect () -> subprocess . CompletedProcess [ str ]:
+
cmd = [
+
"nmcli" ,
+
"device" ,
+
"wifi" ,
+
"connect" ,
+
ssid ,
+
"ifname" ,
+
self . interface ,
+
]
+
if password :
+
cmd . extend ([ "password" , password ])
+
return subprocess . run ( cmd , capture_output = True , text = True , check = False )
+
+
process = run_connect ()
+
if process . returncode != 0 :
+
error_msg = process . stderr . lower ()
+
key_mgmt_error = (
+
"secrets were required" in error_msg
+
or "authentication failed" in error_msg
+
or "802-11-wireless-security.psk" in error_msg
+
or "key-mgmt" in error_msg
+
)
+
if key_mgmt_error :
+
subprocess . run (
+
[ "nmcli" , "connection" , "delete" , "id" , ssid ],
+
capture_output = True ,
+
text = True ,
+
)
+
retry_process = run_connect ()
+
if retry_process . returncode == 0 :
+
logger . info ( f "Successfully connected to network { ssid } after profile reset" )
+
return True , None
+
error_msg = retry_process . stderr . lower ()
+
if (
+
"secrets were required" in error_msg
+
or "authentication failed" in error_msg
+
or "802-11-wireless-security.psk" in error_msg
+
or "key-mgmt" in error_msg
+
):
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Неверный пароль или тип безопасности" ) . format ( ssid = ssid )
+
elif "no network with ssid" in error_msg :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Сеть с SSID {ssid} недоступна или неверно указана" ) . format ( ssid = ssid )
+
else :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : {error} " ) . format ( ssid = ssid , error = retry_process . stderr )
+
elif "no network with ssid" in error_msg :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Сеть с SSID {ssid} недоступна или неверно указана" ) . format ( ssid = ssid )
+
else :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : {error} " ) . format ( ssid = ssid , error = process . stderr )
+
+
logger . info ( f "Successfully connected to network { ssid } " )
+
return True , None
+
except subprocess . CalledProcessError as e :
+
logger . error ( f "Error connecting to { ssid } : { e . stderr } " )
+
error_msg = e . stderr . lower ()
+
if "secrets were required" in error_msg or "authentication failed" in error_msg or "802-11-wireless-security.psk: недействительное свойство" in e . stderr :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Неверный пароль" ) . format ( ssid = ssid )
+
elif "no network with ssid" in error_msg :
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Сеть с SSID {ssid} недоступна или неверно указана" ) . format ( ssid = ssid )
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : {error} " ) . format ( ssid = ssid , error = e . stderr )
+
except Exception as e :
+
logger . error ( f "Unknown error connecting to { ssid } : { str ( e ) } " )
+
return False , self . translations . gettext ( "Ошибка подключения к {ssid} : Неизвестная ошибка - {error} " ) . format ( ssid = ssid , error = str ( e ))
+
+
+
+
[docs]
+
def disconnect ( self , ssid : str ) -> Tuple [ bool , Optional [ str ]]:
+
"""Отключается от Wi-Fi сети по указанному SSID.
+
+
Args:
+
ssid (str): Идентификатор сети Wi-Fi для отключения.
+
+
Returns:
+
Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
+
- Успех: (True, None или сообщение, если подключение не найдено).
+
- Ошибка: (False, сообщение об ошибке).
+
+
Raises:
+
subprocess.CalledProcessError: Если команда nmcli завершилась с ошибкой.
+
Exception: Если произошла неизвестная ошибка при выполнении.
+
"""
+
try :
+
if not self . interface :
+
return False , self . translations . gettext ( "Wi-Fi-интерфейс не доступен" )
+
result = subprocess . run ([ "nmcli" , "connection" , "show" , "--active" ], capture_output = True , text = True )
+
active_connections = result . stdout . strip () . splitlines ()
+
if not active_connections :
+
logger . info ( f "No active connections for SSID { ssid } " )
+
return True , self . translations . gettext ( "Нет активного подключения для SSID {ssid} " ) . format ( ssid = ssid )
+
for conn in active_connections :
+
conn_details = conn . split ()
+
if len ( conn_details ) > 1 :
+
conn_name = conn_details [ 0 ]
+
conn_info = subprocess . run ([ "nmcli" , "connection" , "show" , conn_name ], capture_output = True , text = True ) . stdout
+
if "802-11-wireless.ssid:" in conn_info and ssid in conn_info :
+
subprocess . run ([ "nmcli" , "connection" , "down" , conn_name ], capture_output = True , text = True , check = True )
+
logger . info ( f "Disconnected from SSID { ssid } " )
+
return True , None
+
logger . warning ( f "Active connection with SSID { ssid } not found" )
+
return True , self . translations . gettext ( "Активное подключение с SSID {ssid} не найдено" ) . format ( ssid = ssid )
+
except subprocess . CalledProcessError as e :
+
logger . error ( f "Error disconnecting from { ssid } : { e . stderr } " )
+
return False , self . translations . gettext ( "Ошибка отключения от {ssid} : {error} " ) . format ( ssid = ssid , error = e . stderr )
+
except Exception as e :
+
logger . error ( f "Unknown error disconnecting from { ssid } : { str ( e ) } " )
+
return False , self . translations . gettext ( "Ошибка отключения от {ssid} : {error} " ) . format ( ssid = ssid , error = str ( e ))
+
+
+
+
[docs]
+
def get_status ( self ) -> Tuple [ bool , Optional [ str ]]:
+
"""Проверяет статус текущего подключения к Wi-Fi.
+
+
Returns:
+
Tuple[bool, Optional[str]]: Кортеж (успех, SSID или сообщение об ошибке).
+
- Успех: (True, SSID текущей сети).
+
- Ошибка или отсутствие подключения: (False, сообщение об ошибке).
+
+
Raises:
+
Exception: Если произошла ошибка при выполнении команды nmcli.
+
"""
+
try :
+
if not self . interface :
+
return False , self . translations . gettext ( "Wi-Fi-интерфейс не доступен" )
+
result = subprocess . run ([ "nmcli" , "connection" , "show" , "--active" ], capture_output = True , text = True )
+
if result . stdout . strip ():
+
ssid = result . stdout . splitlines ()[ 1 ] . split ()[ 0 ]
+
logger . info ( f "Current network: { ssid } " )
+
return True , ssid
+
return False , self . translations . gettext ( "Не подключено к Wi-Fi" )
+
except Exception as e :
+
logger . error ( f "Error checking status: { str ( e ) } " )
+
return False , self . translations . gettext ( "Ошибка проверки статуса: {error} " ) . format ( error = str ( e ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/firewalld_service.html b/SRV/docs/_build/html/_modules/services/firewalld_service.html
new file mode 100644
index 0000000..498bfa9
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/firewalld_service.html
@@ -0,0 +1,522 @@
+
+
+
+
+
+
+
+ services.firewalld_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.firewalld_service
+"""
+Сервис для управления firewalld через D-Bus.
+
+Назначение:
+- Обёртка над интерфейсами `org.fedoraproject.FirewallD1` для выполнения
+ типовых операций по зонам: просмотр/изменение дефолтной зоны, добавление и
+ удаление сервисов, портов, источников, перезагрузка конфигурации.
+
+Примечания:
+- Используется `pydbus.SystemBus` для доступа к системной шине.
+- Методы генерируют дружелюбные исключения при ошибках, содержащие контекст
+ операции.
+"""
+from re import DEBUG
+from typing import Dict , List , Optional
+
+from pydbus import SystemBus
+
+DEBUG = True
+
+
+
+
[docs]
+
class FirewalldService :
+
+
def __init__ ( self ):
+
self . bus = SystemBus ()
+
self . proxy = self . bus . get ( "org.fedoraproject.FirewallD1" ,
+
"/org/fedoraproject/FirewallD1" )
+
+
+
[docs]
+
def get_zones ( self ) -> List [ str ]:
+
"""Get list of all zones."""
+
try :
+
return self . proxy . getZones ()
+
except Exception as e :
+
raise Exception ( f "Failed to get zones: { str ( e ) } " )
+
+
+
+
[docs]
+
def get_default_zone ( self ) -> str :
+
"""Get the default zone."""
+
try :
+
return self . proxy . getDefaultZone ()
+
except Exception as e :
+
raise Exception ( f "Failed to get default zone: { str ( e ) } " )
+
+
+
+
[docs]
+
def set_default_zone ( self , zone : str ) -> None :
+
"""Set the default zone."""
+
try :
+
self . proxy . setDefaultZone ( zone )
+
except Exception as e :
+
raise Exception ( f "Failed to set default zone to { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def add_service ( self ,
+
zone : str ,
+
service : str ,
+
timeout : Optional [ int ] = 0 ) -> None :
+
"""Add a service to a zone."""
+
try :
+
self . proxy . addService ( zone , service , timeout )
+
except Exception as e :
+
raise Exception (
+
f "Failed to add service { service } to zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def remove_service ( self , zone : str , service : str ) -> None :
+
"""Remove a service from a zone."""
+
try :
+
self . proxy . removeService ( zone , service )
+
except Exception as e :
+
raise Exception (
+
f "Failed to remove service { service } from zone { zone } : { str ( e ) } "
+
)
+
+
+
+
[docs]
+
def get_services ( self , zone : str ) -> List [ str ]:
+
"""Get list of services in a zone."""
+
try :
+
return self . proxy . getServices ( zone )
+
except Exception as e :
+
raise Exception (
+
f "Failed to get services for zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def add_port ( self ,
+
zone : str ,
+
port : int ,
+
protocol : str ,
+
timeout : Optional [ int ] = 0 ) -> None :
+
"""Add a port to a zone."""
+
try :
+
if DEBUG :
+
print ( f "Adding port { port } / { protocol } to zone { zone } " )
+
print ( f "Timeout: { timeout } " )
+
# Get the zone interface
+
zone_proxy = self . bus . get ( "org.fedoraproject.FirewallD1" ,
+
"/org/fedoraproject/FirewallD1" )
+
zone_iface = zone_proxy [ "org.fedoraproject.FirewallD1.zone" ]
+
# Convert port to string
+
port_str = str ( port )
+
# Convert timeout to integer
+
timeout_int = int ( timeout ) if timeout is not None else 0
+
# Call addPort with the correct signature (sssi)
+
zone_iface . addPort ( zone , port_str , protocol , timeout_int )
+
except Exception as e :
+
raise Exception (
+
f "Failed to add port { port } / { protocol } to zone { zone } : { str ( e ) } "
+
)
+
+
+
+
[docs]
+
def remove_port ( self , zone : str , port : int , protocol : str ) -> None :
+
"""Remove a port from a zone."""
+
try :
+
port_str = f " { port } / { protocol } "
+
self . proxy . removePort ( zone , port_str )
+
except Exception as e :
+
raise Exception (
+
f "Failed to remove port { port } / { protocol } from zone { zone } : { str ( e ) } "
+
)
+
+
+
+
[docs]
+
def get_ports ( self , zone : str ) -> List [ str ]:
+
"""Get list of ports in a zone."""
+
try :
+
ports = self . proxy . getPorts ( zone )
+
# Convert port list items to strings in format "port/protocol"
+
return [ f " { port [ 0 ] } / { port [ 1 ] } " for port in ports ]
+
except Exception as e :
+
raise Exception ( f "Failed to get ports for zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def add_source ( self , zone : str , source : str ) -> None :
+
"""Add a source to a zone."""
+
try :
+
self . proxy . addSource ( zone , source )
+
except Exception as e :
+
raise Exception (
+
f "Failed to add source { source } to zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def remove_source ( self , zone : str , source : str ) -> None :
+
"""Remove a source from a zone."""
+
try :
+
self . proxy . removeSource ( zone , source )
+
except Exception as e :
+
raise Exception (
+
f "Failed to remove source { source } from zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def get_sources ( self , zone : str ) -> List [ str ]:
+
"""Get list of sources in a zone."""
+
try :
+
return self . proxy . getSources ( zone )
+
except Exception as e :
+
raise Exception ( f "Failed to get sources for zone { zone } : { str ( e ) } " )
+
+
+
+
[docs]
+
def reload ( self ) -> None :
+
"""Reload firewall rules."""
+
try :
+
self . proxy . reload ()
+
except Exception as e :
+
raise Exception ( f "Failed to reload firewall rules: { str ( e ) } " )
+
+
+
+
[docs]
+
def get_zone_settings ( self , zone : str ) -> Dict :
+
"""Get all settings for a zone."""
+
try :
+
return {
+
"services" : self . get_services ( zone ),
+
"ports" : self . get_ports ( zone ),
+
"sources" : self . get_sources ( zone ),
+
}
+
except Exception as e :
+
raise Exception (
+
f "Failed to get zone settings for { zone } : { str ( e ) } " )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/ip_service.html b/SRV/docs/_build/html/_modules/services/ip_service.html
new file mode 100644
index 0000000..991df10
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/ip_service.html
@@ -0,0 +1,468 @@
+
+
+
+
+
+
+
+ services.ip_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.ip_service
+"""
+Сервис проверки и применения IP-настроек через NetworkManager (D-Bus).
+
+Назначение:
+- Перед применением конфигурации проверяет занятость IP адреса, сканируя сегмент
+ сети средствами ARP (через `NetworkScanner`).
+- Применяет изменения профиля подключения через `sdbus_async.networkmanager` и
+ выполняет переподключение интерфейса.
+
+Требования/ограничения:
+- Доступ к системной шине D-Bus и права на изменения сетевых профилей.
+- Структура входных данных должна соответствовать схеме `ipv4_updates` для
+ корректного обновления вложенных полей у профиля.
+"""
+from typing import Dict , Any
+from sdbus_async.networkmanager import (
+ NetworkManager ,
+ NetworkManagerSettings ,
+ NetworkConnectionSettings ,
+ NetworkDeviceGeneric ,
+)
+import sdbus
+from sdbus_async.networkmanager.settings import ConnectionProfile
+from services.arp_service import NetworkScanner
+from utils.i18n import get_translations
+from babel.support import Translations
+from fastapi import Depends
+
+
+
[docs]
+
class IPService :
+
"""Сервис проверки доступности IP-адреса и применения настроек сети.
+
+
Предоставляет асинхронные методы для проверки занятости IP-адреса с использованием
+
ARP-сканирования и применения настроек через NetworkManager.
+
"""
+
+
def __init__ ( self ):
+
"""Инициализирует сервис IP.
+
+
Создаёт объект ARP-сканера и устанавливает системную шину D-Bus как
+
по умолчанию.
+
"""
+
self . arp_scanner = NetworkScanner ()
+
self . bus = sdbus . sd_bus_open_system ()
+
sdbus . set_default_bus ( self . bus )
+
+
def _reset_bus ( self ):
+
"""Перезапускает соединение с системной шиной D-Bus.
+
+
Закрывает текущее соединение и создаёт новое, устанавливая его как
+
по умолчанию.
+
"""
+
new_bus = sdbus . sd_bus_open_system ()
+
sdbus . set_default_bus ( new_bus )
+
if hasattr ( self , "bus" ) and self . bus is not None :
+
self . bus = None
+
self . bus = new_bus
+
+
+
[docs]
+
async def check_ip_config ( self , args : Dict [ str , Any ], translations : Translations = Depends ( get_translations ())) -> Dict [ str , Any ]:
+
"""Проверяет доступность IP-адреса перед его применением.
+
+
Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
+
Перезапускает шину D-Bus перед выполнением.
+
+
Args:
+
args (Dict[str, Any]): Словарь с данными, включая "ipv4_updates"
+
с полем "ipv4.address-data" для нового IP-адреса.
+
translations (Translations): Объект переводов.
+
+
Returns:
+
Dict[str, Any]: Словарь с ключами:
+
- "available" (bool): Доступен ли IP-адрес.
+
- "message" (str): Сообщение о результате проверки.
+
+
Raises:
+
Exception: Если произошла ошибка при сканировании ARP.
+
"""
+
self . _reset_bus ()
+
clients = self . arp_scanner . scan_network ()
+
new_ip = args [ "ipv4_updates" ][ "ipv4.address-data" ][ 0 ][ "address" ]
+
if any ( client [ "ip" ] == new_ip for client in clients ):
+
return {
+
"available" : False ,
+
"message" : translations . gettext ( "IP-адрес {ip} уже занят." ) . format ( ip = new_ip )
+
}
+
return {
+
"available" : True ,
+
"message" : translations . gettext ( "Настройки сетевого профиля доступны для использования." )
+
}
+
+
+
+
[docs]
+
async def apply_ip_config ( self , args : Dict [ str , Any ], translations : Translations = Depends ( get_translations ())) -> None :
+
"""Применяет проверенные настройки IP к указанному соединению.
+
+
Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
+
отключает и переподключает устройство к сети.
+
+
Args:
+
args (Dict[str, Any]): Словарь с данными, включая:
+
- "connection_id" (str): Идентификатор соединения.
+
- "ipv4_updates" (Dict): Обновления настроек IPv4.
+
translations (Translations): Объект переводов.
+
+
Raises:
+
ValueError: Если соединение с указанным ID не найдено.
+
RuntimeError: Если устройство для интерфейса не найдено.
+
+
Note:
+
Требуются права для изменения настроек через NetworkManager.
+
"""
+
self . _reset_bus ()
+
nm = NetworkManager ()
+
settings = NetworkManagerSettings ()
+
connection_paths = await settings . get_connections_by_id (
+
args [ "connection_id" ])
+
+
if not connection_paths :
+
raise ValueError ( translations . gettext ( "Подключение {connection_id} не найдено" ) . format ( connection_id = args [ "connection_id" ]))
+
+
connection = NetworkConnectionSettings ( connection_paths [ 0 ])
+
profile = await connection . get_profile ()
+
settings_dict = profile . to_settings_dict ()
+
+
for key_path , value in args [ "ipv4_updates" ] . items ():
+
section , * props = key_path . split ( "." )
+
current = settings_dict [ section ]
+
for prop in props [: - 1 ]:
+
current = current . setdefault ( prop , {})
+
current [ props [ - 1 ]] = value
+
+
new_profile = ConnectionProfile . from_settings_dict ( settings_dict )
+
await connection . update ( new_profile . to_dbus ())
+
print ( f "Updated settings for { args [ 'connection_id' ] } " )
+
+
interface_name = settings_dict [ "connection" ][ "interface-name" ]
+
devices = await nm . get_devices ()
+
for device_path in devices :
+
device = NetworkDeviceGeneric ( device_path )
+
if await device . interface == interface_name :
+
await device . disconnect ()
+
print ( f "Disconnected from { interface_name } " )
+
await nm . activate_connection ( connection = connection_paths [ 0 ], device = device_path )
+
print ( f "Reconnected to { args [ 'connection_id' ] } " )
+
return
+
+
raise RuntimeError ( translations . gettext ( "Устройство для интерфейса {interface} не найдено" ) . format ( interface = interface_name ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/network_service.html b/SRV/docs/_build/html/_modules/services/network_service.html
new file mode 100644
index 0000000..0a79a74
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/network_service.html
@@ -0,0 +1,744 @@
+
+
+
+
+
+
+
+ services.network_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.network_service
+"""
+Сервис чтения сетевого состояния через NetworkManager (D-Bus).
+
+Назначение:
+- Предоставляет асинхронные методы для сборки унифицированных моделей
+ сетевых интерфейсов (Ethernet/Wi-Fi), их IP-конфигураций, а также получения
+ имени хоста. Используется API-слоем для визуализации и управления.
+
+Особенности:
+- Фильтрация loopback и явно неактивных интерфейсов.
+- Адаптация данных D-Bus в pydantic-модели (`NetworkInterface`, `WiFiDevice`,
+ `NetworkInterfacesContainer`).
+"""
+from typing import List , Dict , Tuple , Union , Optional
+from sdbus_async.networkmanager import (
+ NetworkManager ,
+ NetworkDeviceGeneric ,
+ IPv4Config ,
+ IPv6Config ,
+ NetworkManagerSettings ,
+ NetworkDeviceWireless ,
+ AccessPoint ,
+ DeviceType ,
+)
+from sdbus import sd_bus_open_system
+from models.network import (
+ NetworkInterface ,
+ IPv4Route ,
+ IPv6Route ,
+ IPConfig ,
+ IndividualIPConfig ,
+ NetworkInterfacesContainer ,
+ WiFiDevice ,
+)
+import subprocess
+from utils.i18n import get_translations , _
+from babel.support import Translations
+from fastapi import Depends
+
+
+
[docs]
+
class NetworkService :
+
"""Сервис для взаимодействия с NetworkManager через D-Bus.
+
+
Предоставляет асинхронные методы для получения информации о сетевых интерфейсах,
+
их конфигурациях IP, Wi-Fi и состояниях.
+
"""
+
+
def __init__ ( self , translations : Translations = Depends ( get_translations ())):
+
"""Инициализирует сервис NetworkManager.
+
+
Устанавливает системный D-Bus и создаёт объекты для взаимодействия с
+
NetworkManager и его настройками.
+
"""
+
self . system_bus = sd_bus_open_system ()
+
self . nm = NetworkManager ( self . system_bus )
+
self . nm_settings = NetworkManagerSettings ( self . system_bus )
+
self . translations = translations
+
+
+
[docs]
+
async def get_ip_method ( self , device : NetworkDeviceGeneric , ip_type : str ) -> str :
+
"""Получает метод IP (например, "auto", "manual") для указанного устройства.
+
+
Args:
+
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
+
ip_type (str): Тип IP (например, "ipv4" или "ipv6").
+
+
Returns:
+
str: Метод IP или "unknown" в случае ошибки.
+
+
Raises:
+
Exception: Если произошла ошибка при получении настроек соединения.
+
"""
+
try :
+
config = await device . get_applied_connection ()
+
settings = config [ 0 ] . get ( ip_type , {})
+
return str ( settings . get ( "method" , ( "s" , "unknown" ))[ 1 ])
+
except Exception :
+
return "unknown"
+
+
+
+
[docs]
+
async def get_connection_name ( self , device : NetworkDeviceGeneric ) -> str :
+
"""Извлекает имя соединения (GENERAL.CONNECTION) для устройства.
+
+
Args:
+
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
+
+
Returns:
+
str: Имя соединения или "unknown" в случае ошибки.
+
+
Raises:
+
Exception: Если произошла ошибка при получении настроек соединения.
+
"""
+
try :
+
applied_connection = await device . get_applied_connection ()
+
connection_settings = applied_connection [ 0 ]
+
connection_info = connection_settings . get ( "connection" , {})
+
return str ( connection_info . get ( "id" , ( "s" , "unknown" ))[ 1 ])
+
except Exception as e :
+
return "unknown"
+
+
+
+
[docs]
+
async def get_ip_config ( self , device : NetworkDeviceGeneric , ip_version : str ) -> List [ IndividualIPConfig ]:
+
"""Получает конфигурацию IP для устройства.
+
+
Args:
+
device (NetworkDeviceGeneric): Объект устройства NetworkManager.
+
ip_version (str): Версия IP ("4" или "6").
+
+
Returns:
+
List[IndividualIPConfig]: Список конфигураций IP, включая адреса и маршруты.
+
+
Raises:
+
Exception: Если произошла ошибка при получении данных IP.
+
"""
+
configs = []
+
device_ip = await getattr ( device , f "ip { ip_version } _config" )
+
if device_ip != "/" :
+
ip_config = globals ()[ f "IPv { ip_version } Config" ]( device_ip , self . system_bus )
+
address_data = await ip_config . address_data
+
for address in address_data :
+
config = IndividualIPConfig (
+
address = str ( address [ "address" ][ 1 ]),
+
routes = [
+
globals ()[ f "IPv { ip_version } Route" ](
+
destination = route . get ( "dest" , ( "s" , "0.0.0.0" if ip_version == "4" else "::" ))[ 1 ],
+
prefix = route . get ( "prefix" , ( "u" , 0 ))[ 1 ],
+
next_hop = route . get ( "next-hop" , ( "s" , "0.0.0.0" if ip_version == "4" else "::" ))[ 1 ],
+
metric = route . get ( "metric" , ( "u" , 0 ))[ 1 ],
+
) for route in await ip_config . route_data
+
],
+
method = await self . get_ip_method ( device , f "ipv { ip_version } " ),
+
gateway = str ( await ip_config . gateway ),
+
dns = [
+
str ( ns [ "address" ][ 1 ]) if ip_version == "4" else str ( ns )
+
for ns in await getattr (
+
ip_config ,
+
"nameserver_data" if ip_version == "4" else "nameservers" ,
+
)
+
],
+
)
+
configs . append ( config )
+
return configs
+
+
+
+
[docs]
+
async def get_wifi_info ( self , wifi_device : NetworkDeviceWireless ) -> Dict [ str , Optional [ Union [ str , int ]]]:
+
"""Получает информацию о Wi-Fi устройстве.
+
+
Args:
+
wifi_device (NetworkDeviceWireless): Объект Wi-Fi устройства NetworkManager.
+
+
Returns:
+
Dict[str, Optional[Union[str, int]]]: Словарь с данными Wi-Fi (SSID, BSSID,
+
bitrate, strength, frequency, mode), где значения могут быть None.
+
+
Raises:
+
Exception: Если произошла ошибка при получении данных точки доступа.
+
"""
+
active_ap = await wifi_device . active_access_point
+
wifi_info = {
+
"ssid" : None ,
+
"bssid" : None ,
+
"bitrate" : await wifi_device . bitrate ,
+
"strength" : None ,
+
"frequency" : None ,
+
"mode" : None ,
+
}
+
if active_ap != "/" :
+
ap = AccessPoint ( active_ap , self . system_bus )
+
wifi_info . update ({
+
"ssid" : await ap . ssid ,
+
"bssid" : await ap . hw_address ,
+
"strength" : await ap . strength ,
+
"frequency" : await ap . frequency ,
+
"mode" : await ap . mode ,
+
})
+
return wifi_info
+
+
+
+
[docs]
+
async def get_network_interfaces (
+
self ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None
+
) -> List [ Union [ NetworkInterface , WiFiDevice ]]:
+
"""Получает список сетевых интерфейсов.
+
+
Args:
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None (используются (100, 100)).
+
+
Returns:
+
List[Union[NetworkInterface, WiFiDevice]]: Список объектов интерфейсов,
+
включая Wi-Fi устройства.
+
+
Исключает интерфейсы типа LOOPBACK и неактивные состояния (state == "10").
+
"""
+
devices_paths = await self . nm . get_devices ()
+
network_interfaces = []
+
initial_positions = initial_positions or {}
+
+
for device_path in devices_paths :
+
generic_device = NetworkDeviceGeneric ( device_path , self . system_bus )
+
interface_name = str ( await generic_device . interface )
+
device_type = DeviceType ( await generic_device . device_type ) . name
+
+
if device_type == "LOOPBACK" :
+
continue
+
+
device_state = str ( await generic_device . state )
+
if device_state == "10" :
+
continue
+
+
common_data = {
+
"name" : interface_name ,
+
"type" : device_type ,
+
"device_state" : device_state ,
+
"active_connection" : await self . get_connection_name ( generic_device ),
+
"mac_address" : str ( await generic_device . hw_address ),
+
"device_mtu" : str ( await generic_device . mtu ),
+
"ipv4" : IPConfig ( configurations = await self . get_ip_config ( generic_device , "4" )),
+
"ipv6" : IPConfig ( configurations = await self . get_ip_config ( generic_device , "6" )),
+
"x" : initial_positions . get ( interface_name , ( 100 , 100 ))[ 0 ],
+
"y" : initial_positions . get ( interface_name , ( 100 , 100 ))[ 1 ],
+
}
+
+
if device_type == "WIFI" :
+
wifi_device = NetworkDeviceWireless ( device_path , self . system_bus )
+
wifi_info = await self . get_wifi_info ( wifi_device )
+
network_interfaces . append ( WiFiDevice ( ** common_data , ** wifi_info ))
+
else :
+
network_interfaces . append ( NetworkInterface ( ** common_data ))
+
+
return network_interfaces
+
+
+
+
[docs]
+
async def get_hostname ( self ) -> Dict [ str , str ]:
+
"""Возвращает имя хоста из команды hostname в формате JSON.
+
+
Returns:
+
Dict[str, str]: Словарь с ключом "hostname" и значением имени хоста.
+
+
Raises:
+
RuntimeError: Если произошла ошибка при выполнении команды hostname
+
или обработке вывода.
+
"""
+
try :
+
result = subprocess . run ([ "hostname" ],
+
capture_output = True ,
+
text = True ,
+
check = True )
+
hostname = result . stdout . strip ()
+
return { "hostname" : hostname }
+
except subprocess . CalledProcessError as e :
+
raise RuntimeError ( self . translations . gettext ( "Не удалось выполнить команду hostname: {error} " ) . format ( error = str ( e )))
+
except Exception as e :
+
raise RuntimeError ( self . translations . gettext ( "Ошибка обработки вывода hostname: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
async def get_interfaces_by_state (
+
self ,
+
state : str ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None
+
) -> List [ NetworkInterface ]:
+
"""Получает список интерфейсов по указанному состоянию.
+
+
Args:
+
state (str): Состояние интерфейса (например, "100" для активных).
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
+
Returns:
+
List[NetworkInterface]: Список интерфейсов с указанным состоянием.
+
"""
+
all_interfaces = await self . get_network_interfaces ( initial_positions )
+
return [
+
interface for interface in all_interfaces
+
if interface . device_state == state
+
]
+
+
+
+
[docs]
+
async def get_interfaces_container (
+
self ,
+
state : Optional [ str ] = None ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None ,
+
container_x : int = 0 ,
+
container_y : int = 0 ,
+
) -> NetworkInterfacesContainer :
+
"""Получает объект, содержащий список интерфейсов и координаты контейнера.
+
+
Args:
+
state (Optional[str], optional): Состояние интерфейсов (например, "100").
+
По умолчанию None (все интерфейсы).
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
container_x (int, optional): Координата X контейнера. По умолчанию 0.
+
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
+
+
Returns:
+
NetworkInterfacesContainer: Объект с интерфейсами и координатами контейнера.
+
"""
+
if state :
+
interfaces = await self . get_interfaces_by_state ( state , initial_positions )
+
else :
+
interfaces = await self . get_network_interfaces ( initial_positions )
+
return NetworkInterfacesContainer ( interfaces = interfaces ,
+
container_x = container_x ,
+
container_y = container_y )
+
+
+
+
[docs]
+
async def get_network_interfaces_container (
+
self ,
+
initial_positions : Optional [ Dict [ str , Tuple [ int , int ]]] = None ,
+
) -> NetworkInterfacesContainer :
+
"""Получает контейнер с сетевыми интерфейсами и координатами контейнера.
+
+
Args:
+
initial_positions (Optional[Dict[str, Tuple[int, int]]], optional):
+
Словарь начальных координат (x, y) для интерфейсов. По умолчанию None.
+
+
Returns:
+
NetworkInterfacesContainer: Объект с интерфейсами и координатами (0, 0).
+
"""
+
interfaces = await self . get_network_interfaces ( initial_positions )
+
return NetworkInterfacesContainer ( interfaces = interfaces ,
+
container_x = 0 ,
+
container_y = 0 )
+
+
+
+
[docs]
+
async def get_activated_interfaces (
+
self ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None
+
) -> List [ NetworkInterface ]:
+
"""Получает список активных сетевых интерфейсов.
+
+
Args:
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
+
Returns:
+
List[NetworkInterface]: Список активных интерфейсов (state == "100").
+
"""
+
return await self . get_interfaces_by_state ( "100" , initial_positions )
+
+
+
+
[docs]
+
async def get_deactivated_interfaces (
+
self ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None
+
) -> List [ NetworkInterface ]:
+
"""Получает список неактивных сетевых интерфейсов.
+
+
Args:
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
+
Returns:
+
List[NetworkInterface]: Список интерфейсов с state != "100".
+
"""
+
all_interfaces = await self . get_network_interfaces ( initial_positions )
+
return [
+
interface for interface in all_interfaces
+
if interface . device_state != "100"
+
]
+
+
+
+
[docs]
+
async def get_activated_interfaces_container (
+
self ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None ,
+
container_x : int = 0 ,
+
container_y : int = 0 ,
+
) -> NetworkInterfacesContainer :
+
"""Получает объект, содержащий список активных интерфейсов и координаты контейнера.
+
+
Args:
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
container_x (int, optional): Координата X контейнера. По умолчанию 0.
+
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
+
+
Returns:
+
NetworkInterfacesContainer: Объект с активными интерфейсами и координатами.
+
"""
+
return await self . get_interfaces_container ( "100" , initial_positions , container_x , container_y )
+
+
+
+
[docs]
+
async def get_deactivated_interfaces_container (
+
self ,
+
initial_positions : Dict [ str , Tuple [ int , int ]] = None ,
+
container_x : int = 0 ,
+
container_y : int = 0 ,
+
) -> NetworkInterfacesContainer :
+
"""Получает объект, содержащий список неактивных интерфейсов и координаты контейнера.
+
+
Args:
+
initial_positions (Dict[str, Tuple[int, int]], optional): Словарь начальных
+
координат (x, y) для интерфейсов. По умолчанию None.
+
container_x (int, optional): Координата X контейнера. По умолчанию 0.
+
container_y (int, optional): Координата Y контейнера. По умолчанию 0.
+
+
Returns:
+
NetworkInterfacesContainer: Объект с неактивными интерфейсами и координатами.
+
"""
+
interfaces = await self . get_deactivated_interfaces ( initial_positions )
+
return NetworkInterfacesContainer ( interfaces = interfaces , container_x = container_x , container_y = container_y )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/settings_service.html b/SRV/docs/_build/html/_modules/services/settings_service.html
new file mode 100644
index 0000000..5caff6b
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/settings_service.html
@@ -0,0 +1,431 @@
+
+
+
+
+
+
+
+ services.settings_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.settings_service
+import json
+import logging
+from typing import Optional
+
+from utils.i18n import get_translations
+from babel.support import Translations
+from fastapi import Depends
+from pathlib import Path
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
class SettingsService :
+
"""Сервис для управления настройками и очистки данных сети."""
+
+
def __init__ ( self , translations : Translations = Depends ( get_translations ())):
+
self . subnet_base_path = "storage/subnets"
+
self . wifi_file = "storage/wifi/wifi.json"
+
self . translations = translations
+
+
+
[docs]
+
def clear_subnet ( self , subnet : Optional [ str ] = None ) -> bool :
+
"""Очищает данные узлов подсети в указанном файле или во всех файлах.
+
+
Args:
+
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+
Если None, очищает все подсети.
+
+
Returns:
+
bool: True, если очистка успешна.
+
+
Raises:
+
Exception: Если произошла ошибка при записи в файл.
+
"""
+
try :
+
if subnet :
+
file_path = Path ( self . subnet_base_path ) / f " { subnet . replace ( '/' , '_' ) } .json"
+
if file_path . exists ():
+
with open ( file_path , "w" ) as f :
+
json . dump ([], f )
+
logger . info ( f "Subnet nodes data cleared for { subnet } " )
+
else :
+
logger . info ( f "Subnet file not found for { subnet } , clearing considered successful" )
+
return True
+
else :
+
for file_path in Path ( self . subnet_base_path ) . glob ( "*.json" ):
+
with open ( file_path , "w" ) as f :
+
json . dump ([], f )
+
logger . info ( f "Cleared subnet data in { file_path } " )
+
return True
+
except FileNotFoundError :
+
logger . info ( "No subnet files found, clearing considered successful" )
+
return True
+
except Exception as e :
+
logger . error ( f "Error clearing subnet nodes: { str ( e ) } " )
+
raise Exception ( self . translations . gettext ( "Ошибка очистки узлов подсети: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
def clear_wifi ( self ) -> bool :
+
"""Очищает данные точек доступа Wi-Fi в файле wifi.json.
+
+
Returns:
+
bool: True, если очистка успешна.
+
+
Raises:
+
Exception: Если произошла ошибка при записи в файл.
+
"""
+
try :
+
with open ( self . wifi_file , "w" ) as f :
+
json . dump ([], f )
+
logger . info ( "Wi-Fi access points data successfully cleared" )
+
return True
+
except FileNotFoundError :
+
logger . info ( "Wi-Fi file not found, clearing considered successful" )
+
return True
+
except Exception as e :
+
logger . error ( f "Error clearing Wi-Fi access points: { str ( e ) } " )
+
raise Exception ( self . translations . gettext ( "Ошибка очистки точек доступа Wi-Fi: {error} " ) . format ( error = str ( e )))
+
+
+
+
[docs]
+
def clear_all_data ( self ) -> bool :
+
"""Очищает данные узлов всех подсетей и точек доступа Wi-Fi.
+
+
Returns:
+
bool: True, если очистка успешна.
+
+
Raises:
+
Exception: Если произошла ошибка при записи в один из файлов.
+
"""
+
try :
+
try :
+
for file_path in Path ( self . subnet_base_path ) . glob ( "*.json" ):
+
with open ( file_path , "w" ) as f :
+
json . dump ([], f )
+
logger . info ( f "Cleared subnet data in { file_path } " )
+
except FileNotFoundError :
+
logger . info ( "No subnet files found, clearing considered successful" )
+
try :
+
with open ( self . wifi_file , "w" ) as f :
+
json . dump ([], f )
+
except FileNotFoundError :
+
logger . info ( "Wi-Fi file not found, clearing considered successful" )
+
logger . info ( "All network data successfully cleared" )
+
return True
+
except Exception as e :
+
logger . error ( f "Error clearing all network data: { str ( e ) } " )
+
raise Exception ( self . translations . gettext ( "Ошибка очистки всех сетевых данных: {error} " ) . format ( error = str ( e )))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/subnet_service.html b/SRV/docs/_build/html/_modules/services/subnet_service.html
new file mode 100644
index 0000000..03c03b3
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/subnet_service.html
@@ -0,0 +1,1095 @@
+
+
+
+
+
+
+
+ services.subnet_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.subnet_service
+from scapy.config import conf
+from mac_vendor_lookup import MacLookup
+from typing import List , Optional , Tuple , Dict
+import socket
+import logging
+import netifaces
+import ipaddress
+from concurrent.futures import ThreadPoolExecutor
+import time
+from functools import lru_cache
+import json
+from pathlib import Path
+from datetime import datetime , timedelta
+import subprocess
+
+from scapy.layers.l2 import ARP , Ether
+from scapy.sendrecv import srp
+from utils.i18n import get_translations
+from babel.support import Translations
+from fastapi import Depends
+
+from models.subnet import SubnetScanResult , Device
+
+logging . basicConfig (
+ level = logging . INFO ,
+ format = " %(asctime)s - %(name)s - %(levelname)s - %(message)s "
+)
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
def get_subnet_service ( translations : Translations = Depends ( get_translations ())):
+
"""Фабричная функция для создания экземпляра SubnetService.
+
+
Использует зависимости FastAPI для получения объекта переводов и возвращает
+
экземпляр SubnetService с текущей локализацией.
+
+
Args:
+
translations (Translations): Объект переводов для текущего языка.
+
+
Returns:
+
SubnetService: Экземпляр сервиса для работы с подсетями.
+
"""
+
return SubnetService ( translations )
+
+
+
+
[docs]
+
class NetworkScanner :
+
"""Класс для сканирования подсети с использованием ARP-запросов.
+
+
Назначение:
+
Обнаруживает активные устройства в локальной сети, отправляя широковещательные
+
ARP-запросы по указанной подсети и собирая ответы (IP, MAC).
+
+
Детали реализации:
+
- Использует `scapy` (`Ether` + `ARP`) для генерации пакетов и `srp` для
+
отправки/получения на канальном уровне от имени заданного интерфейса.
+
- Таймаут ожидания ответа короткий (2 секунды), ориентирован на интерактивные
+
сценарии UI.
+
+
Ограничения и замечания:
+
- Требуются права для отправки низкоуровневых пакетов (capabilities/root).
+
- В сетях с фильтрацией широковещания количество обнаруженных устройств
+
может быть ограничено.
+
"""
+
def __init__ ( self , interface : str , translations : Translations ):
+
"""Инициализирует сканер сети.
+
+
Устанавливает интерфейс для сканирования и объект переводов.
+
+
Args:
+
interface (str): Имя сетевого интерфейса (например, "wlp0s20f3").
+
translations (Translations): Объект переводов для текущего языка.
+
"""
+
self . interface = interface
+
conf . iface = interface
+
self . translations = translations
+
+
+
[docs]
+
def scan ( self , subnet : str ) -> List [ Tuple [ str , str ]]:
+
"""Сканирует подсеть с использованием ARP-запросов.
+
+
Отправляет ARP-пакеты для обнаружения активных устройств и возвращает
+
список кортежей (IP, MAC).
+
+
Args:
+
subnet (str): Подсеть в формате CIDR (например, "10.1.1.0/24").
+
+
Returns:
+
List[Tuple[str, str]]: Список кортежей (IP-адрес, MAC-адрес) обнаруженных устройств.
+
+
Raises:
+
Exception: Если произошла ошибка сканирования.
+
"""
+
try :
+
arp = ARP ( pdst = subnet )
+
except Exception as e :
+
logger . error ( f "Error checking status: { str ( e ) } " )
+
try :
+
arp = ARP ( pdst = subnet )
+
ether = Ether ( dst = "ff:ff:ff:ff:ff:ff" )
+
packet = ether / arp
+
result = srp ( packet , timeout = 2 , verbose = False , iface = self . interface )[ 0 ]
+
return [( received . psrc , received . hwsrc ) for sent , received in result ]
+
except Exception as e :
+
logger . error ( f "Error scanning subnet { subnet } : { str ( e ) } " )
+
return []
+
+
+
+
+
[docs]
+
class PortScanner :
+
"""Класс для сканирования открытых портов на устройствах.
+
+
Назначение:
+
Плейсхолдер для будущего функционала активного сканирования TCP/UDP-портов
+
у обнаруженных узлов. В текущей версии возвращает пустой список.
+
+
План развития:
+
- Добавить неблокирующее сканирование наиболее распространённых портов
+
(22, 80, 443 и т.д.) с ограничением по времени.
+
- Настраиваемые профили глубины и скорости сканирования.
+
"""
+
def __init__ ( self , interface : str , translations : Translations ):
+
"""Инициализирует сканер портов.
+
+
Устанавливает интерфейс для сканирования и объект переводов.
+
+
Args:
+
interface (str): Имя сетевого интерфейса.
+
translations (Translations): Объект переводов для текущего языка.
+
"""
+
self . interface = interface
+
self . translations = translations
+
+
+
[docs]
+
def scan ( self , ip : str ) -> List [ str ]:
+
"""Сканирует открытые порты на указанном IP-адресе.
+
+
Пока возвращает пустой список, так как функциональность не реализована.
+
+
Args:
+
ip (str): IP-адрес устройства для сканирования.
+
+
Returns:
+
List[str]: Список открытых портов (пока пустой).
+
"""
+
return []
+
+
+
+
+
[docs]
+
class DeviceInfoCollector :
+
"""Класс для сбора информации об устройствах, включая вендоров и хостнеймы.
+
+
Источники данных:
+
- MacLookup: сопоставление префикса MAC (OUI) с названием производителя.
+
- DNS: `gethostbyaddr` с малым таймаутом для обратного разрешения имени.
+
- NetBIOS (nbtscan): опрос сегмента сети для извлечения NetBIOS-имён.
+
+
Политика кэширования:
+
- Результаты `get_vendor` кэшируются с помощью `functools.lru_cache`.
+
- Результаты `nbtscan` аккумулируются в словарях `nbtscan_cache`/`nbtscan_results`
+
на время жизни экземпляра.
+
"""
+
def __init__ ( self , translations : Translations ):
+
"""Инициализирует коллектор информации об устройствах.
+
+
Настраивает кэширование и объект переводов.
+
+
Args:
+
translations (Translations): Объект переводов для текущего языка.
+
"""
+
self . mac_lookup = MacLookup ()
+
self . nbtscan_cache = {}
+
self . nbtscan_results = {}
+
self . translations = translations
+
+
+
[docs]
+
@lru_cache ( maxsize = 1000 )
+
def get_vendor ( self , mac : str ) -> str :
+
"""Получает вендора устройства по MAC-адресу.
+
+
Использует кэширование для повышения производительности.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
+
Returns:
+
str: Название вендора или "Неизвестный" в случае ошибки.
+
"""
+
try :
+
return self . mac_lookup . lookup ( mac )
+
except Exception as e :
+
logger . warning ( f "Failed to determine vendor for MAC { mac } : { str ( e ) } " )
+
return self . translations . gettext ( "Неизвестный" )
+
+
+
+
[docs]
+
def scan_nbtscan ( self , subnet : str ) -> None :
+
"""Сканирует подсеть с помощью nbtscan для получения хостнеймов.
+
+
Сохраняет результаты в кэш и логирует процесс.
+
+
Args:
+
subnet (str): Подсеть в формате CIDR.
+
"""
+
if subnet not in self . nbtscan_results :
+
try :
+
logger . debug ( f "Starting nbtscan for subnet { subnet } " )
+
result = subprocess . run ([ "sudo" , "nbtscan" , "-r" , str ( subnet )],
+
capture_output = True ,
+
text = True ,
+
check = False ,
+
timeout = 10 )
+
logger . debug ( f "Full nbtscan output: { result . stdout } " )
+
if result . returncode == 0 :
+
for line in result . stdout . splitlines ():
+
logger . debug ( f "Processing line: { line } " )
+
if not line . startswith (( "IP address" , "-" , "Doing NBT" )):
+
parts = [ p for p in line . split () if p ]
+
if len ( parts ) >= 2 :
+
ip_addr , hostname = parts [ 0 ], parts [ 1 ]
+
if hostname != "<unknown>" :
+
self . nbtscan_cache [ ip_addr ] = hostname
+
logger . info ( f "NetBIOS: Found hostname { hostname } for IP { ip_addr } " )
+
self . nbtscan_results [ subnet ] = True
+
except subprocess . TimeoutExpired :
+
logger . error ( f "NetBIOS: Timeout for subnet { subnet } " )
+
except Exception as e :
+
logger . error ( f "NetBIOS: Error for subnet { subnet } : { str ( e ) } " )
+
+
+
+
[docs]
+
def get_hostname ( self , ip : str ) -> Optional [ str ]:
+
"""Получает хостнейм для указанного IP-адреса.
+
+
Пробует сначала DNS, затем NetBIOS.
+
+
Args:
+
ip (str): IP-адрес устройства.
+
+
Returns:
+
Optional[str]: Хостнейм или None, если не удалось определить.
+
"""
+
try :
+
socket . setdefaulttimeout ( 2 )
+
return socket . gethostbyaddr ( ip )[ 0 ]
+
except ( socket . herror , socket . gaierror , socket . timeout ):
+
logger . debug ( f "DNS: Failed to resolve name for IP { ip } " )
+
+
subnet = ipaddress . ip_network ( f " { ip } /24" , strict = False )
+
if subnet not in self . nbtscan_results :
+
self . scan_nbtscan ( str ( subnet ))
+
+
if ip in self . nbtscan_cache :
+
return self . nbtscan_cache [ ip ]
+
+
logger . debug ( f "Failed to determine hostname for IP { ip } via DNS or NetBIOS" )
+
return None
+
+
+
+
+
[docs]
+
class DeviceStorage :
+
"""Класс для хранения и управления данными об устройствах.
+
+
Формат файла подсети:
+
JSON-структура вида
+
{
+
"devices": [Device, ...]
+
}
+
+
Политика очистки:
+
- Метод `_clean_old_devices` удаляет оффлайн-устройства, не наблюдавшиеся
+
более 7 дней, чтобы поддерживать актуальность списка.
+
"""
+
def __init__ ( self , base_path : str = "storage/subnets" , translations : Translations = None ):
+
"""Инициализирует хранилище устройств.
+
+
Создаёт директорию, если её нет, и задаёт параметры очистки.
+
+
Args:
+
base_path (str): Путь к директории хранения данных.
+
translations (Translations, optional): Объект переводов.
+
"""
+
self . base_path = Path ( base_path )
+
self . base_path . mkdir ( parents = True , exist_ok = True )
+
self . offline_timeout = timedelta ( days = 7 )
+
self . translations = translations or Translations . load ( "locales" , [ "ru" ], "messages" ) # Fallback на русский
+
+
+
[docs]
+
def get_subnet_file ( self , subnet : str ) -> Path :
+
"""Возвращает путь к файлу для конкретной подсети.
+
+
Заменяет символы '/' на '_' для корректного имени файла.
+
+
Args:
+
subnet (str): Подсеть в формате CIDR.
+
+
Returns:
+
Path: Путь к файлу.
+
"""
+
sanitized_subnet = subnet . replace ( "/" , "_" )
+
return self . base_path / f " { sanitized_subnet } .json"
+
+
+
+
[docs]
+
def load_devices ( self , subnet : str ) -> Dict [ str , Device ]:
+
"""Загружает устройства для указанной подсети.
+
+
Читает данные из JSON-файла и фильтрует устаревшие устройства.
+
+
Args:
+
subnet (str): Подсеть в формате CIDR.
+
+
Returns:
+
Dict[str, Device]: Словарь устройств с MAC как ключом.
+
"""
+
file_path = self . get_subnet_file ( subnet )
+
try :
+
if file_path . exists ():
+
with open ( file_path , "r" , encoding = "utf-8" ) as f :
+
data = json . load ( f )
+
if not isinstance ( data , dict ) or "devices" not in data :
+
logger . warning ( f "Invalid JSON structure in { file_path } , expected { 'devices' : [...] } , got { data } " )
+
return {}
+
devices = {}
+
for device_data in data . get ( "devices" , []):
+
device = Device ( ** device_data )
+
if device . mac :
+
devices [ device . mac ] = device
+
return self . _clean_old_devices ( devices )
+
return {}
+
except json . JSONDecodeError as e :
+
logger . error ( f "JSON decode error in { file_path } : { str ( e ) } " )
+
return {}
+
except Exception as e :
+
logger . error ( f "Error loading devices for subnet { subnet } : { str ( e ) } " )
+
return {}
+
+
+
def _clean_old_devices ( self , devices : Dict [ str , Device ]) -> Dict [ str , Device ]:
+
"""Очищает список устройств от устаревших оффлайн-устройств.
+
+
Удаляет устройства, которые оффлайн более 7 дней.
+
+
Args:
+
devices (Dict[str, Device]): Словарь устройств.
+
+
Returns:
+
Dict[str, Device]: Отфильтрованный словарь устройств.
+
"""
+
now = datetime . now ()
+
filtered = {}
+
for mac , device in devices . items ():
+
if device . status == "offline" and device . last_seen :
+
try :
+
last_seen = datetime . fromisoformat ( device . last_seen )
+
if now - last_seen <= self . offline_timeout :
+
filtered [ mac ] = device
+
else :
+
logger . info ( f "Removed device { mac } (offline > 7 days)" )
+
except ValueError :
+
logger . warning ( f "Invalid last_seen format for device { mac } " )
+
else :
+
filtered [ mac ] = device
+
return filtered
+
+
+
[docs]
+
def save_devices ( self , subnet : str , devices : List [ Device ]) -> None :
+
"""Сохраняет устройства для указанной подсети.
+
+
Записывает данные в JSON-файл.
+
+
Args:
+
subnet (str): Подсеть в формате CIDR.
+
devices (List[Device]): Список устройств для сохранения.
+
+
Raises:
+
Exception: Если произошла ошибка при сохранении.
+
"""
+
file_path = self . get_subnet_file ( subnet )
+
try :
+
data = SubnetScanResult ( devices = devices ) . model_dump ()
+
with open ( file_path , "w" , encoding = "utf-8" ) as f :
+
json . dump ( data , f , indent = 2 , ensure_ascii = False )
+
logger . info ( f "Saved { len ( devices ) } devices to { file_path } " )
+
except Exception as e :
+
logger . error ( f "Error saving devices for subnet { subnet } : { str ( e ) } " )
+
if self . translations :
+
raise Exception ( self . translations . gettext ( "Ошибка при сохранении устройств: {error} " ) . format ( error = str ( e )))
+
else :
+
raise Exception ( f "Ошибка при сохранении устройств: { str ( e ) } " )
+
+
+
+
+
[docs]
+
class SubnetService :
+
"""Сервис для сканирования подсетей и управления устройствами.
+
+
Роль сервиса:
+
Объединяет сетевое сканирование (ARP), сбор дополнительной информации
+
(вендор, хостнейм), слияние с кэшем предыдущих результатов и персистентное
+
хранение данных по подсетям. Предоставляет вспомогательные операции для
+
локального узла и работы с заметками устройств.
+
+
Поток выполнения скана (`scan_subnet`):
+
1) Определение подсети (вручную или авто по интерфейсу).
+
2) Загрузка ранее сохранённых устройств подсети.
+
3) Добавление локального устройства (если принадлежит подсети).
+
4) ARP-сканирование и параллельная обработка узлов (обогащение данных).
+
5) Маркировка оффлайн-узлов из кэша, не встреченных в текущем скане.
+
6) Сортировка по IP и сохранение объединённого списка в JSON.
+
"""
+
def __init__ ( self , translations : Translations ):
+
"""Инициализирует сервис подсети.
+
+
Настраивает интерфейс, сканеры и хранилище с текущей локализацией.
+
+
Args:
+
translations (Translations): Объект переводов для текущего языка.
+
"""
+
self . interface = self . _get_default_interface ()
+
self . network_scanner = NetworkScanner ( self . interface , translations )
+
self . port_scanner = PortScanner ( self . interface , translations )
+
self . device_info = DeviceInfoCollector ( translations )
+
self . storage = DeviceStorage ( translations = translations )
+
self . translations = translations
+
logger . info ( f "Initialized SubnetService with interface: { self . interface } " )
+
+
def _get_default_interface ( self ) -> str :
+
"""Определяет интерфейс по умолчанию.
+
+
Использует информацию о шлюзе или первый доступный интерфейс.
+
+
Returns:
+
str: Имя интерфейса (например, "wlp0s20f3" или "eth0").
+
+
Raises:
+
Exception: Если не удалось определить интерфейс.
+
"""
+
try :
+
gateways = netifaces . gateways ()
+
default_gateway = gateways . get ( "default" , {}) . get ( netifaces . AF_INET )
+
if default_gateway :
+
iface = default_gateway [ 1 ]
+
logger . info ( f "Default interface from gateway: { iface } " )
+
return iface
+
interfaces = netifaces . interfaces ()
+
for iface in interfaces :
+
if iface != "lo" :
+
addrs = netifaces . ifaddresses ( iface )
+
if netifaces . AF_INET in addrs :
+
logger . info ( f "Selected interface: { iface } " )
+
return iface
+
logger . warning ( "No suitable interface found, defaulting to eth0" )
+
return "eth0"
+
except Exception as e :
+
logger . warning ( f "Failed to determine interface: { str ( e ) } , defaulting to eth0" )
+
return "eth0"
+
+
def _get_default_subnet ( self ) -> str :
+
"""Автоматически определяет подсеть локального устройства.
+
+
Использует информацию о текущем интерфейсе для определения подсети.
+
+
Returns:
+
str: Подсеть в формате CIDR (например, "10.1.1.0/24").
+
+
Raises:
+
Exception: Если не удалось определить подсеть.
+
"""
+
try :
+
interfaces = netifaces . interfaces ()
+
for iface in interfaces :
+
if iface == "lo" or iface != self . interface :
+
continue
+
addrs = netifaces . ifaddresses ( iface )
+
if netifaces . AF_INET in addrs :
+
ip_info = addrs [ netifaces . AF_INET ][ 0 ]
+
ip = ip_info [ "addr" ]
+
netmask = ip_info [ "netmask" ]
+
# Преобразуем маску из строки (например, "255.255.255.0") в CIDR
+
mask = ipaddress . ip_network ( f "0.0.0.0/ { netmask } " , strict = False ) . prefixlen
+
network = ipaddress . ip_network ( f " { ip } / { mask } " , strict = False )
+
logger . info ( f "Auto-detected subnet: { network } (IP: { ip } , Mask: { netmask } -> / { mask } )" )
+
return str ( network )
+
logger . warning ( "No suitable subnet found, defaulting to 0.0.0.0/0" )
+
return "0.0.0.0/0"
+
except Exception as e :
+
logger . error ( f "Error determining default subnet: { str ( e ) } " )
+
return "0.0.0.0/0"
+
+
+
[docs]
+
def scan_subnet ( self , subnet : Optional [ str ] = None ) -> SubnetScanResult :
+
"""Сканирует указанную подсеть и возвращает результаты.
+
+
Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные.
+
+
Args:
+
subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+
Если None или пустая строка, определяется автоматически.
+
+
Returns:
+
SubnetScanResult: Объект с результатами сканирования, содержащий список
+
устройств или сообщение об ошибке.
+
+
Raises:
+
Exception: Если произошла ошибка сканирования.
+
"""
+
start_time = time . time ()
+
devices = []
+
now = datetime . now () . astimezone ()
+
formatted_now = now . strftime ( " %d -%m-%Y, %H:%M" )
+
try :
+
# Определяем подсеть, если не указана или пустая
+
if not subnet or subnet . strip () == "" :
+
subnet = self . _get_default_subnet ()
+
if subnet == "0.0.0.0/0" :
+
logger . warning ( "Default subnet is invalid, scan aborted" )
+
return SubnetScanResult ( devices = devices , error = self . translations . gettext ( "Недопустимая подсеть по умолчанию" ))
+
+
saved_devices = self . storage . load_devices ( subnet )
+
+
local_device = self . _get_local_device ( subnet )
+
if local_device :
+
local_device . status = "online"
+
local_device . last_seen = formatted_now
+
devices . append ( local_device )
+
if local_device . mac :
+
saved_devices [ local_device . mac ] = local_device
+
logger . info ( f "Detected local device: { local_device . ip } , MAC: { local_device . mac } " )
+
+
logger . info ( f "Scanning subnet: { subnet } with interface: { self . interface } " )
+
discovered_devices = self . network_scanner . scan ( subnet )
+
logger . info ( f "Discovered devices: { len ( discovered_devices ) } " )
+
self . device_info . scan_nbtscan ( subnet )
+
+
if not discovered_devices and not local_device :
+
logger . warning ( f "No devices found in subnet { subnet } " )
+
return SubnetScanResult ( devices = [], error = self . translations . gettext ( "В данной подсети не обнаружено устройств" ))
+
+
def process_device ( ip_mac : Tuple [ str , str ]) -> Optional [ Device ]:
+
ip , mac = ip_mac
+
if local_device and ip == local_device . ip :
+
return None
+
+
existing_device = saved_devices . get ( mac )
+
if existing_device :
+
return Device (
+
ip = ip ,
+
mac = mac ,
+
hostname = self . device_info . get_hostname ( ip ) or existing_device . hostname ,
+
vendor = self . device_info . get_vendor ( mac ),
+
open_ports = existing_device . open_ports ,
+
status = "online" ,
+
last_seen = formatted_now ,
+
note = existing_device . note ,
+
)
+
vendor = self . device_info . get_vendor ( mac )
+
hostname = self . device_info . get_hostname ( ip )
+
return Device (
+
ip = ip ,
+
mac = mac ,
+
hostname = hostname ,
+
vendor = vendor ,
+
open_ports = [],
+
status = "online" ,
+
last_seen = formatted_now ,
+
note = None ,
+
)
+
+
with ThreadPoolExecutor ( max_workers = 10 ) as executor :
+
device_results = executor . map ( process_device , discovered_devices )
+
devices . extend ([ d for d in device_results if d is not None ])
+
+
active_macs = { d . mac for d in devices if d . mac }
+
for mac , device in saved_devices . items ():
+
if mac not in active_macs :
+
device . status = "offline"
+
devices . append ( device )
+
+
devices . sort ( key = lambda x : int ( ipaddress . IPv4Address ( x . ip )))
+
+
for device in devices :
+
if device . mac :
+
saved_devices [ device . mac ] = device
+
self . storage . save_devices ( subnet , list ( saved_devices . values ()))
+
+
logger . info (
+
f "Subnet scan { subnet } completed in { time . time () - start_time : .2f } seconds, "
+
f "found { len ( devices ) } devices (active: { len ( active_macs ) } )"
+
)
+
return SubnetScanResult ( devices = devices )
+
+
except Exception as e :
+
logger . error ( f "Error scanning subnet { subnet } : { str ( e ) } " )
+
return SubnetScanResult ( devices = devices , error = self . translations . gettext ( "Ошибка сканирования: {error} " ) . format ( error = str ( e )))
+
+
+
def _get_local_device ( self , subnet : str ) -> Optional [ Device ]:
+
"""Определяет локальное устройство в указанной подсети.
+
+
Использует информацию об интерфейсе для обнаружения.
+
+
Args:
+
subnet (str): Подсеть в формате CIDR.
+
+
Returns:
+
Optional[Device]: Объект устройства или None, если не найдено.
+
"""
+
now = datetime . now () . astimezone ()
+
formatted_now = now . strftime ( " %d -%m-%Y, %H:%M" )
+
try :
+
subnet_network = ipaddress . ip_network ( subnet , strict = False )
+
interfaces = netifaces . interfaces ()
+
for iface in interfaces :
+
if iface == "lo" :
+
continue
+
addrs = netifaces . ifaddresses ( iface )
+
if netifaces . AF_INET in addrs and netifaces . AF_LINK in addrs :
+
ip = addrs [ netifaces . AF_INET ][ 0 ][ "addr" ]
+
if ipaddress . ip_address ( ip ) in subnet_network :
+
mac = addrs [ netifaces . AF_LINK ][ 0 ][ "addr" ]
+
vendor = self . device_info . get_vendor ( mac )
+
hostname = self . device_info . get_hostname ( ip )
+
logger . info ( f "Local device detected: IP= { ip } , MAC= { mac } , Interface= { iface } " )
+
return Device (
+
ip = ip ,
+
mac = mac ,
+
hostname = hostname ,
+
vendor = vendor ,
+
open_ports = [],
+
status = "online" ,
+
last_seen = formatted_now ,
+
note = None ,
+
)
+
logger . warning ( f "Local device not found in subnet { subnet } " )
+
return None
+
except Exception as e :
+
logger . warning ( f "Failed to get local device: { str ( e ) } " )
+
return None
+
+
+
[docs]
+
def add_note ( self , mac : str , note : str , translations : Translations ) -> Tuple [ bool , Optional [ Tuple [ str , Dict ]]]:
+
"""Добавляет заметку для устройства по MAC-адресу.
+
+
Привязывает заметку к устройству, если оно существует.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
note (str): Текст заметки.
+
translations (Translations): Объект переводов для текущего языка.
+
+
Returns:
+
Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+
где результат содержит код ошибки/успеха и данные.
+
"""
+
try :
+
saved_devices = {}
+
for subnet in self . storage . base_path . glob ( "*.json" ):
+
devices = self . storage . load_devices ( subnet . stem . replace ( "_" , "/" ))
+
saved_devices . update ( devices )
+
if mac not in saved_devices :
+
logger . warning ( f "Device with MAC { mac } not found" )
+
return False , ( "not_found" , { "mac" : mac })
+
if saved_devices [ mac ] . note is not None :
+
logger . error ( f "Note for device with MAC { mac } already exists" )
+
return False , ( "note_exists" , { "mac" : mac })
+
for subnet_file in self . storage . base_path . glob ( "*.json" ):
+
subnet = subnet_file . stem . replace ( "_" , "/" )
+
devices = self . storage . load_devices ( subnet )
+
if mac in devices :
+
devices [ mac ] . note = note
+
self . storage . save_devices ( subnet , list ( devices . values ()))
+
break
+
logger . info ( f "Added note ' { note } ' for device with MAC { mac } " )
+
return True , ( "success" , { "mac" : mac , "note" : note })
+
except Exception as e :
+
logger . error ( f "Error adding note for MAC { mac } : { str ( e ) } " )
+
return False , ( "internal_error" , { "error" : str ( e )})
+
+
+
+
[docs]
+
def update_note ( self , mac : str , note : str , translations : Translations ) -> Tuple [ bool , Optional [ Tuple [ str , Dict ]]]:
+
"""Обновляет заметку для устройства по MAC-адресу.
+
+
Изменяет существующую заметку, если она есть.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
note (str): Новый текст заметки.
+
translations (Translations): Объект переводов для текущего языка.
+
+
Returns:
+
Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+
где результат содержит код ошибки/успеха и данные.
+
"""
+
try :
+
saved_devices = {}
+
for subnet in self . storage . base_path . glob ( "*.json" ):
+
devices = self . storage . load_devices ( subnet . stem . replace ( "_" , "/" ))
+
saved_devices . update ( devices )
+
if mac not in saved_devices :
+
logger . warning ( f "Device with MAC { mac } not found" )
+
return False , ( "not_found" , { "mac" : mac })
+
if saved_devices [ mac ] . note is None :
+
logger . error ( f "Note for device with MAC { mac } does not exist" )
+
return False , ( "note_not_exists" , { "mac" : mac })
+
for subnet_file in self . storage . base_path . glob ( "*.json" ):
+
subnet = subnet_file . stem . replace ( "_" , "/" )
+
devices = self . storage . load_devices ( subnet )
+
if mac in devices :
+
devices [ mac ] . note = note
+
self . storage . save_devices ( subnet , list ( devices . values ()))
+
break
+
logger . info ( f "Updated note ' { note } ' for device with MAC { mac } " )
+
return True , ( "success" , { "mac" : mac , "note" : note })
+
except Exception as e :
+
logger . error ( f "Error updating note for MAC { mac } : { str ( e ) } " )
+
return False , ( "internal_error" , { "error" : str ( e )})
+
+
+
+
[docs]
+
def delete_note ( self , mac : str , translations : Translations ) -> Tuple [ bool , Optional [ Tuple [ str , Dict ]]]:
+
"""Удаляет заметку для устройства по MAC-адресу.
+
+
Удаляет привязанную заметку, если она существует.
+
+
Args:
+
mac (str): MAC-адрес устройства.
+
translations (Translations): Объект переводов для текущего языка.
+
+
Returns:
+
Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+
где результат содержит код ошибки/успеха и данные.
+
"""
+
try :
+
saved_devices = {}
+
for subnet in self . storage . base_path . glob ( "*.json" ):
+
devices = self . storage . load_devices ( subnet . stem . replace ( "_" , "/" ))
+
saved_devices . update ( devices )
+
if mac not in saved_devices :
+
logger . warning ( f "Device with MAC { mac } not found" )
+
return False , ( "not_found" , { "mac" : mac })
+
if saved_devices [ mac ] . note is None :
+
logger . error ( f "Note for device with MAC { mac } does not exist" )
+
return False , ( "note_not_exists" , { "mac" : mac })
+
for subnet_file in self . storage . base_path . glob ( "*.json" ):
+
subnet = subnet_file . stem . replace ( "_" , "/" )
+
devices = self . storage . load_devices ( subnet )
+
if mac in devices :
+
devices [ mac ] . note = None
+
self . storage . save_devices ( subnet , list ( devices . values ()))
+
break
+
logger . info ( f "Deleted note for device with MAC { mac } " )
+
return True , ( "success" , { "mac" : mac })
+
except Exception as e :
+
logger . error ( f "Error deleting note for MAC { mac } : { str ( e ) } " )
+
return False , ( "internal_error" , { "error" : str ( e )})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/ufw_service.html b/SRV/docs/_build/html/_modules/services/ufw_service.html
new file mode 100644
index 0000000..3afe7f0
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/ufw_service.html
@@ -0,0 +1,754 @@
+
+
+
+
+
+
+
+ services.ufw_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.ufw_service
+import ipaddress
+import subprocess
+from typing import List , Union
+
+from models.ufw import Direction , Protocol , UFWRule , UFWRulesResponse , UFWStatus
+
+DEBUG = True
+
+
+
+
[docs]
+
class UFWService :
+
"""Сервис взаимодействия с UFW (Uncomplicated Firewall).
+
+
Назначение:
+
- Чтение статуса и списка правил брандмауэра.
+
- Добавление, удаление и массовая обработка правил с поддержкой диапазонов портов,
+
комментариев и выбором протоколов.
+
+
Детали реализации:
+
- Команды выполняются через `sudo ufw ...`; вывод парсится в модели pydantic.
+
- Для операций удаления соблюдается порядок с конца, чтобы не нарушать нумерацию.
+
"""
+
+
def _run_command ( self , command : str ) -> str :
+
"""
+
Выполняет команду в системе и возвращает ее вывод.
+
+
Args:
+
command (str): Команда для выполнения в системе.
+
+
Returns:
+
str: Вывод команды.
+
"""
+
try :
+
if DEBUG :
+
print ( f "Running command: { command } " )
+
+
# Используем shell=True для корректной обработки аргументов с пробелами
+
result = subprocess . run (
+
f "sudo { command } " ,
+
shell = True ,
+
stdout = subprocess . PIPE ,
+
stderr = subprocess . PIPE ,
+
text = True ,
+
)
+
+
if DEBUG :
+
print ( f "Command output: { result . stdout } " )
+
print ( f "Command error: { result . stderr } " )
+
+
if result . returncode != 0 :
+
raise subprocess . CalledProcessError ( result . returncode , command ,
+
result . stdout ,
+
result . stderr )
+
+
return result . stdout
+
+
except subprocess . CalledProcessError as e :
+
if DEBUG :
+
print ( f "Command failed with return code { e . returncode } " )
+
print ( f "Error output: { e . stderr } " )
+
raise Exception ( f "Command failed: { e . stderr } " )
+
+
def _get_port_range_args ( self , port : Union [ int , str ]) -> List [ str ]:
+
"""
+
Преобразует порт или диапазон портов в аргументы команды UFW.
+
+
Args:
+
port: Порт (число) или диапазон портов (строка вида "3000:3999")
+
+
Returns:
+
List[str]: Список аргументов для UFW
+
"""
+
if isinstance ( port , int ):
+
return [ str ( port )]
+
elif isinstance ( port , str ) and ":" in port :
+
# Диапазон портов уже в нужном формате
+
return [ port ]
+
else :
+
raise ValueError ( f "Некорректный формат порта: { port } " )
+
+
+
[docs]
+
def get_ufw_rules ( self ) -> UFWRulesResponse :
+
"""
+
Получает список правил UFW.
+
+
Returns:
+
UFWRulesResponse: Объект, содержащий статус UFW и список правил
+
"""
+
# Check UFW status
+
status_result = self . _run_command ( "ufw status numbered" )
+
lines = status_result . splitlines ()
+
+
if not lines :
+
return UFWRulesResponse ( enabled = False , rules = [])
+
+
first_line = lines [ 0 ]
+
is_enabled = "Status: active" in first_line
+
+
if not is_enabled :
+
return UFWRulesResponse ( enabled = False , rules = [])
+
+
rules = []
+
# Skip header lines
+
for line in lines [ 4 :]: # Skip Status and header lines
+
if not line . strip () or not line . startswith ( "[" ):
+
continue
+
+
try :
+
rule = UFWRule . from_ufw_output ( line )
+
rules . append ( rule )
+
except Exception as e :
+
if DEBUG :
+
print ( f "Ошибка парсинга правила: { line } , ошибка: { str ( e ) } " )
+
continue
+
+
return UFWRulesResponse ( enabled = is_enabled , rules = rules )
+
+
+
+
[docs]
+
def get_ufw_status ( self ) -> UFWStatus :
+
"""
+
Проверяет статус UFW.
+
+
Returns:
+
UFWStatus: Объект с информацией о статусе UFW
+
"""
+
try :
+
status_result = self . _run_command ( "ufw status" )
+
first_line = ( status_result . splitlines ()[ 0 ]
+
if status_result . splitlines () else "" )
+
is_enabled = "Status: active" in first_line
+
+
message = "UFW активен" if is_enabled else "UFW неактивен"
+
+
if DEBUG :
+
print ( f "Результат статуса: { status_result } " )
+
print ( f "Включен: { is_enabled } " )
+
+
return UFWStatus ( enabled = is_enabled , message = message )
+
except Exception as e :
+
if DEBUG :
+
print ( f "Ошибка при проверке статуса UFW: { e } " )
+
return UFWStatus (
+
enabled = False ,
+
message = f "Ошибка при проверке статуса: { str ( e ) } " ,
+
)
+
+
+
+
[docs]
+
def enable_ufw ( self ) -> bool :
+
"""
+
Включает UFW.
+
+
Returns:
+
bool: True если успешно включен, False в противном случае
+
"""
+
try :
+
self . _run_command ( "ufw enable" )
+
return True
+
except Exception as e :
+
if DEBUG :
+
print ( f "Ошибка при включении UFW: { e } " )
+
return False
+
+
+
+
[docs]
+
def disable_ufw ( self ) -> bool :
+
"""
+
Выключает UFW.
+
+
Returns:
+
bool: True если успешно выключен, False в противном случае
+
"""
+
try :
+
self . _run_command ( "ufw disable" )
+
return True
+
except Exception as e :
+
if DEBUG :
+
print ( f "Ошибка при выключении UFW: { e } " )
+
return False
+
+
+
+
[docs]
+
def add_rules_base ( self ,
+
rules : List [ UFWRule ],
+
direction : str = "" ) -> bool :
+
"""
+
Базовый метод для добавления правил UFW.
+
+
Args:
+
rules: Список правил для добавления
+
direction: Направление трафика ("in" или "out")
+
+
Returns:
+
bool: True если все правила были успешно добавлены, False в противном случае
+
"""
+
try :
+
for rule in rules :
+
# Устанавливаем направление правила
+
rule . direction = Direction . IN if direction == "in" else Direction . OUT
+
+
# Формируем базовую команду
+
command = f "ufw { rule . action . value } "
+
+
# Добавляем направление
+
command += f " { rule . direction . value } "
+
+
# Добавляем from IP если он указан
+
if rule . from_ip and rule . from_ip != "any" :
+
command += f " from { rule . from_ip } "
+
else :
+
command += " from any"
+
+
# Добавляем to IP если он указан
+
if rule . to_ip and rule . to_ip != "any" :
+
command += f " to { rule . to_ip } "
+
else :
+
command += " to any"
+
+
# Добавляем порт если он указан
+
if rule . port :
+
command += f " port { rule . port } "
+
+
# Добавляем протокол
+
if rule . protocol != Protocol . BOTH :
+
command += f " proto { rule . protocol . value } "
+
+
# Добавляем комментарий если он есть
+
if rule . comment :
+
command += f " comment ' { rule . comment } '"
+
+
self . _run_command ( command )
+
+
return True
+
except Exception as e :
+
if DEBUG :
+
print ( f "Error adding UFW rules: { str ( e ) } " )
+
return False
+
+
+
+
[docs]
+
def add_ufw_in_rules ( self , rules : List [ UFWRule ]) -> bool :
+
"""
+
Добавляет входящие (in) правила UFW.
+
+
Args:
+
rules (List[UFWRule]): Список правил для добавления
+
+
Returns:
+
bool: True если все правила были успешно добавлены, False в противном случае
+
"""
+
return self . add_rules_base ( rules , "in" )
+
+
+
+
[docs]
+
def add_ufw_out_rules ( self , rules : List [ UFWRule ]) -> bool :
+
"""
+
Добавляет исходящие (out) правила UFW.
+
+
Args:
+
rules (List[UFWRule]): Список правил для добавления
+
+
Returns:
+
bool: True если все правила были успешно добавлены, False в противном случае
+
"""
+
return self . add_rules_base ( rules , "out" )
+
+
+
+
[docs]
+
def add_rules ( self , rules : List [ UFWRule ]) -> dict :
+
"""
+
Добавляет правила UFW.
+
+
Args:
+
rules: Список правил для добавления
+
+
Returns:
+
dict: Результат добавления правил
+
"""
+
results = []
+
+
for rule in rules :
+
try :
+
# Определяем версию IP
+
from_ip = ipaddress . ip_network ( rule . from_ip , strict = False )
+
is_ipv6 = isinstance ( from_ip , ipaddress . IPv6Network )
+
+
# Получаем список протоколов
+
protocols = rule . protocol . protocols
+
print ( f "Обработка правила для протоколов: { protocols } " )
+
+
for proto in protocols :
+
# Формируем базовую команду
+
command = f "ufw { rule . action . value } "
+
+
# Добавляем IP адреса
+
command += f " from { rule . from_ip } "
+
if rule . to_ip :
+
command += f " to { rule . to_ip } "
+
+
# Добавляем порт и протокол
+
port_args = self . _get_port_range_args ( rule . port )
+
command += f " port { ' ' . join ( port_args ) } proto { proto } "
+
+
# Добавляем флаг IPv6 если нужно
+
if is_ipv6 :
+
command += " v6"
+
+
# Добавляем комментарий если есть
+
if rule . comment :
+
command += f " comment ' { rule . comment } '"
+
+
print ( f "Подготовлена команда: { command } " )
+
+
# Выполняем команду
+
try :
+
output = self . _run_command ( command )
+
results . append ({ "success" : True , "message" : output })
+
except Exception as e :
+
error_msg = f "Ошибка выполнения команды: { str ( e ) } "
+
print ( error_msg )
+
results . append ({
+
"success" : False ,
+
"message" : error_msg
+
})
+
+
except Exception as e :
+
error_msg = f "Ошибка при добавлении правила: { str ( e ) } "
+
print ( error_msg )
+
results . append ({ "success" : False , "message" : error_msg })
+
+
# Выводим результаты
+
print ( " \n Результаты добавления правил:" )
+
for result in results :
+
status = "Успешно" if result [ "success" ] else "Ошибка"
+
print ( f " - { status } : { result [ 'message' ] } " )
+
+
return { "success" : True , "results" : results }
+
+
+
+
[docs]
+
def delete_rules ( self , rule_numbers : List [ int ]) -> dict :
+
"""
+
Удаляет правила UFW по их номерам.
+
+
Args:
+
rule_numbers (List[int]): Список номеров правил для удаления
+
+
Returns:
+
dict: Результат операции удаления
+
"""
+
try :
+
# Проверяем существование правил
+
status_result = self . _run_command ( "ufw status numbered" )
+
lines = status_result . splitlines ()
+
+
# Пропускаем заголовок (первые 4 строки)
+
rules = []
+
for line in lines [ 4 :]: # Skip header lines
+
if not line . strip () or not line . startswith ( "[" ):
+
continue
+
+
try :
+
# Извлекаем номер правила из строки [N]
+
rule_num = int ( line [ 1 : line . index ( "]" )])
+
rules . append ( rule_num )
+
except ( ValueError , IndexError ):
+
continue
+
+
if DEBUG :
+
print ( f "Найдены правила: { rules } " )
+
+
results = []
+
# Сортируем номера в обратном порядке, чтобы удаление происходило с конца
+
# Это предотвратит смещение номеров при удалении
+
for rule_number in sorted ( rule_numbers , reverse = True ):
+
try :
+
# Проверяем существует ли правило с таким номером
+
if rule_number not in rules :
+
results . append ({
+
"number" :
+
rule_number ,
+
"success" :
+
False ,
+
"error" :
+
f "Правило с номером { rule_number } не найдено" ,
+
})
+
continue
+
+
# Удаляем правило
+
self . _run_command ( f "ufw --force delete { rule_number } " )
+
+
results . append ({
+
"number" :
+
rule_number ,
+
"success" :
+
True ,
+
"message" :
+
f "Правило { rule_number } успешно удалено" ,
+
})
+
+
except Exception as e :
+
if DEBUG :
+
print (
+
f "Ошибка при удалении правила { rule_number } : { e } " )
+
results . append ({
+
"number" : rule_number ,
+
"success" : False ,
+
"error" : str ( e )
+
})
+
+
# Подсчитываем статистику
+
total = len ( rule_numbers )
+
successful = len ([ r for r in results if r [ "success" ]])
+
+
return {
+
"success" : True ,
+
"total" : total ,
+
"successful" : successful ,
+
"results" : results ,
+
}
+
+
except Exception as e :
+
if DEBUG :
+
print ( f "Ошибка при удалении правил: { e } " )
+
return { "success" : False , "error" : str ( e ), "results" : []}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/services/wifi_service.html b/SRV/docs/_build/html/_modules/services/wifi_service.html
new file mode 100644
index 0000000..7c26610
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/services/wifi_service.html
@@ -0,0 +1,720 @@
+
+
+
+
+
+
+
+ services.wifi_service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for services.wifi_service
+import time
+import logging
+import subprocess
+import re
+from functools import lru_cache
+from typing import List , Optional , Dict , Tuple
+from mac_vendor_lookup import MacLookup
+import pywifi
+from models.wifi import WifiAccessPoint , WifiScanResult
+from pathlib import Path
+import json
+from utils.i18n import get_translations
+from babel.support import Translations
+from fastapi import Depends
+
+# Настройка логирования
+logging . basicConfig (
+ level = logging . DEBUG ,
+ format = " %(asctime)s - %(name)s - %(levelname)s - %(message)s "
+)
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
class WifiStorage :
+
"""Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.
+
+
Формат::
+
+
{
+
"networks": [{"ssid": str, "note": Optional[str]}]
+
}
+
+
Поведение:
+
+
- Создаёт директорию хранения при инициализации, ведёт логирование путей.
+
- Валидирует структуру при чтении и безопасно записывает JSON с Unicode и отступами.
+
"""
+
def __init__ ( self , storage_path : str = "storage/wifi/wifi.json" , translations : Translations = Depends ( get_translations ())):
+
self . storage_file = Path ( storage_path )
+
self . storage_file . parent . mkdir ( parents = True , exist_ok = True )
+
self . translations = translations
+
logger . info ( f "Wi-Fi storage initialized at: { self . storage_file . absolute () } " )
+
+
+
[docs]
+
def load_notes ( self ) -> Dict [ str , Optional [ str ]]:
+
"""Загружает заметки по SSID из файла хранения."""
+
try :
+
if self . storage_file . exists ():
+
with open ( self . storage_file , "r" , encoding = "utf-8" ) as f :
+
data = json . load ( f )
+
if not isinstance ( data , dict ) or "networks" not in data :
+
logger . warning ( f "Invalid JSON structure in { self . storage_file } , expected { 'networks' : [...] } , got { data } " )
+
return {}
+
return { item [ "ssid" ]: item . get ( "note" ) for item in data . get ( "networks" , [])}
+
return {}
+
except json . JSONDecodeError as e :
+
logger . error ( f "JSON decode error in { self . storage_file } : { str ( e ) } " )
+
return {}
+
except Exception as e :
+
logger . error ( f "Error loading notes from { self . storage_file } : { str ( e ) } " )
+
return {}
+
+
+
+
[docs]
+
def save_notes ( self , notes : Dict [ str , Optional [ str ]]) -> None :
+
"""Сохраняет заметки по SSID в файл."""
+
try :
+
data = { "networks" : [{ "ssid" : ssid , "note" : note } for ssid , note in notes . items ()]}
+
with open ( self . storage_file , "w" , encoding = "utf-8" ) as f :
+
json . dump ( data , f , indent = 2 , ensure_ascii = False )
+
logger . info ( f "Saved { len ( notes ) } notes to { self . storage_file . absolute () } " )
+
except PermissionError as e :
+
logger . error ( f "Permission denied writing to { self . storage_file } : { str ( e ) } " )
+
raise
+
except Exception as e :
+
logger . error ( f "Error saving notes to { self . storage_file } : { str ( e ) } " )
+
raise Exception ( self . translations . gettext ( "Ошибка при сохранении заметок: {error} " ) . format ( error = str ( e )))
+
+
+
+
+
[docs]
+
class WifiService :
+
"""Сервис для сканирования текущих Wi-Fi точек доступа и заметок.
+
+
Источники данных:
+
- `pywifi` для базового сканирования и получения списка AP.
+
- `iw dev <iface> scan` для уточнения частоты, сигнала и безопасности.
+
- `MacLookup` для определения вендора по BSSID.
+
+
Политики:
+
- Выбор лучшего AP по SSID по уровню сигнала.
+
- Кэширование определения вендора.
+
- Сохранение SSID даже без заметок для будущих операций.
+
"""
+
def __init__ ( self , translations : Translations = Depends ( get_translations ())):
+
self . mac_lookup = MacLookup ()
+
self . wifi = pywifi . PyWiFi ()
+
self . interface = self . _get_wifi_interface ()
+
if not hasattr ( translations , 'gettext' ):
+
logger . warning ( "Translations object does not support gettext, using fallback" )
+
self . translations = Translations . load ( "utils/locales/en/LC_MESSAGES/services.mo" )
+
else :
+
self . translations = translations
+
self . storage = WifiStorage ()
+
+
def _get_wifi_interface ( self ) -> Optional [ object ]:
+
"""Автоматически определяет активный Wi-Fi-интерфейс."""
+
try :
+
interfaces = self . wifi . interfaces ()
+
if not interfaces :
+
logger . error ( self . translations . gettext ( "Не найдено сетевых интерфейсов" ))
+
return None
+
+
logger . debug ( f "Available interfaces: { [ iface . name () for iface in interfaces ] } " )
+
+
for iface in interfaces :
+
iface_name = iface . name ()
+
if "p2p" in iface_name . lower ():
+
logger . debug ( f "Skipped interface { iface_name } (P2P)" )
+
continue
+
try :
+
iface . scan ()
+
time . sleep ( 1 )
+
results = iface . scan_results ()
+
if results :
+
logger . info ( f "Selected Wi-Fi interface: { iface_name } " )
+
return iface
+
logger . debug ( f "Interface { iface_name } returned no scan results" )
+
except Exception as e :
+
logger . debug ( f "Interface { iface_name } inactive: { str ( e ) } " )
+
+
logger . error ( self . translations . gettext ( "Не найден активный Wi-Fi-интерфейс" ))
+
return None
+
except Exception as e :
+
logger . error ( f "Error determining Wi-Fi interface: { str ( e ) } " )
+
return None
+
+
+
[docs]
+
@lru_cache ( maxsize = 1000 )
+
def get_vendor ( self , mac : str ) -> str :
+
"""Определяет вендора по MAC-адресу с использованием кэширования."""
+
try :
+
return self . mac_lookup . lookup ( mac )
+
except Exception as e :
+
logger . warning ( f "Failed to determine vendor for MAC { mac } : { str ( e ) } " )
+
return self . translations . gettext ( "Неизвестный" )
+
+
+
def _get_encryption_from_iw ( self , ap_info : Dict ) -> str :
+
"""Определяет тип шифрования из данных, полученных через iw."""
+
security = ap_info . get ( "security" , "" )
+
logger . debug ( f "Encryption for BSSID { ap_info . get ( 'bssid' ) } : { security } " )
+
if "WPA3" in security :
+
return self . translations . gettext ( "WPA3" )
+
elif "WPA2" in security or "RSN" in security :
+
return self . translations . gettext ( "WPA2" )
+
elif "WPA" in security :
+
return self . translations . gettext ( "WPA" )
+
elif "WEP" in security :
+
return self . translations . gettext ( "WEP" )
+
return self . translations . gettext ( "Открытая" )
+
+
def _get_channel ( self , frequency : Optional [ int ]) -> Optional [ int ]:
+
"""Преобразует частоту в номер канала."""
+
if frequency is None :
+
return None
+
try :
+
if 2412 <= frequency <= 2472 :
+
return ( frequency - 2412 ) // 5 + 1
+
elif 5180 <= frequency <= 5825 :
+
return ( frequency - 5180 ) // 5 + 36
+
logger . debug ( f "Frequency { frequency } outside 2.4/5 GHz range" )
+
return None
+
except Exception as e :
+
logger . debug ( f "Error determining channel for frequency { frequency } : { str ( e ) } " )
+
return None
+
+
def _get_frequency ( self , frequency : Optional [ int ]) -> Optional [ str ]:
+
"""Определяет частотный диапазон на основе частоты."""
+
if frequency is None :
+
return None
+
try :
+
if 2412 <= frequency <= 2472 :
+
return self . translations . gettext ( "2.4 ГГц" )
+
elif 5180 <= frequency <= 5825 :
+
return self . translations . gettext ( "5 ГГц" )
+
logger . debug ( f "Frequency { frequency } outside 2.4/5 GHz range" )
+
return None
+
except Exception as e :
+
logger . debug ( f "Error determining band for frequency { frequency } : { str ( e ) } " )
+
return None
+
+
def _get_iw_scan_data ( self ) -> List [ Dict ]:
+
"""Получает данные сканирования Wi-Fi через команду iw."""
+
try :
+
if not self . interface :
+
logger . error ( self . translations . gettext ( "Интерфейс не определён" ))
+
return []
+
cmd = [ "sudo" , "iw" , "dev" , self . interface . name (), "scan" ]
+
logger . debug ( f "Executing command: { ' ' . join ( cmd ) } " )
+
result = subprocess . run ( cmd , capture_output = True , text = True , timeout = 10 )
+
if result . returncode != 0 :
+
logger . error ( f "iw scan error: { result . stderr } " )
+
return []
+
output = result . stdout
+
logger . debug ( f "iw scan output (first 500 chars): { output [: 500 ] } ..." )
+
+
aps = []
+
current_ap = None
+
bssid_pattern = re . compile ( r "BSS ([0-9a-f:] {17} )" )
+
ssid_pattern = re . compile ( r "SSID: ?(.*)" )
+
freq_pattern = re . compile ( r "freq: (\d+)" )
+
signal_pattern = re . compile ( r "signal: ([-\d.]+) dBm" )
+
rsn_pattern = re . compile ( r "RSN:" )
+
wpa_pattern = re . compile ( r "WPA:" )
+
security_type_pattern = re . compile ( r "\* (WPA3|WPA2|WPA|WEP)" )
+
+
for line in output . splitlines ():
+
line = line . strip ()
+
if bssid_match := bssid_pattern . match ( line ):
+
if current_ap :
+
aps . append ( current_ap )
+
current_ap = { "bssid" : bssid_match . group ( 1 ) . lower (), "security" : "" }
+
elif current_ap :
+
if ssid_match := ssid_pattern . match ( line ):
+
current_ap [ "ssid" ] = ssid_match . group ( 1 ) . strip () if ssid_match . group ( 1 ) . strip () else ""
+
elif freq_match := freq_pattern . match ( line ):
+
current_ap [ "frequency" ] = int ( freq_match . group ( 1 ))
+
elif signal_match := signal_pattern . match ( line ):
+
current_ap [ "signal" ] = float ( signal_match . group ( 1 ))
+
elif rsn_pattern . match ( line ):
+
current_ap [ "security" ] += "RSN;"
+
elif wpa_pattern . match ( line ):
+
current_ap [ "security" ] += "WPA;"
+
elif security_type_match := security_type_pattern . search ( line ):
+
current_ap [ "security" ] += security_type_match . group ( 1 ) + ";"
+
+
if current_ap :
+
aps . append ( current_ap )
+
+
logger . debug ( f "Found { len ( aps ) } access points via iw: { [ ap [ 'bssid' ] for ap in aps ] } " )
+
return aps
+
except subprocess . CalledProcessError as e :
+
logger . error ( f "Error executing iw scan: { e . stderr } " )
+
return []
+
except subprocess . TimeoutExpired :
+
logger . error ( self . translations . gettext ( "Таймаут при выполнении iw scan" ))
+
return []
+
except Exception as e :
+
logger . error ( f "Error parsing iw scan: { str ( e ) } " )
+
return []
+
+
+
[docs]
+
def scan_wifi ( self , timeout : int = 30 ) -> WifiScanResult :
+
"""Сканирует текущие Wi-Fi точки доступа."""
+
start_time = time . time ()
+
aps = []
+
+
if not self . interface :
+
logger . error ( self . translations . gettext ( "Wi-Fi-интерфейс не доступен" ))
+
return WifiScanResult ( access_points = aps )
+
+
try :
+
logger . info ( f "Starting Wi-Fi scan on interface { self . interface . name () } " )
+
max_attempts = 3
+
for attempt in range ( max_attempts ):
+
self . interface . scan ()
+
time . sleep ( min ( timeout , 5 ))
+
py_results = self . interface . scan_results ()
+
if py_results :
+
logger . info ( f "Found { len ( py_results ) } access points via pywifi after attempt { attempt + 1 } " )
+
break
+
else :
+
logger . warning ( f "Scan attempt { attempt + 1 } failed, trying again (FAIL-BUSY?)" )
+
else :
+
logger . error ( "All scan attempts failed" )
+
return WifiScanResult ( access_points = aps )
+
+
iw_results = self . _get_iw_scan_data ()
+
iw_by_bssid = { ap [ "bssid" ]: ap for ap in iw_results if ap . get ( "bssid" )}
+
+
best_aps_by_ssid : Dict [ str , WifiAccessPoint ] = {}
+
saved_notes = self . storage . load_notes ()
+
+
for ap in py_results :
+
bssid = ap . bssid . lower () if ap . bssid else None
+
if not bssid :
+
logger . debug ( "Skipped access point without BSSID" )
+
continue
+
ssid = ap . ssid if ap . ssid else f "Hidden_ { bssid } "
+
hidden = not ap . ssid
+
signal = ap . signal if ap . signal else None
+
iw_data = iw_by_bssid . get ( bssid , {})
+
+
frequency = iw_data . get ( "frequency" )
+
channel = self . _get_channel ( frequency )
+
freq_band = self . _get_frequency ( frequency )
+
encryption = self . _get_encryption_from_iw ( iw_data )
+
vendor = self . get_vendor ( bssid )
+
+
current_ap = WifiAccessPoint (
+
ssid = ssid ,
+
bssid = bssid ,
+
channel = channel ,
+
signal = signal if signal else iw_data . get ( "signal" ),
+
encryption = encryption ,
+
frequency = freq_band ,
+
vendor = vendor ,
+
hidden = hidden ,
+
note = saved_notes . get ( ssid ) if ap . ssid else None ,
+
)
+
logger . debug ( f "Processed AP: { current_ap . model_dump () } " )
+
+
if ssid not in best_aps_by_ssid or (
+
current_ap . signal and best_aps_by_ssid [ ssid ] . signal
+
and current_ap . signal > best_aps_by_ssid [ ssid ] . signal ):
+
current_ap . note = saved_notes . get ( ssid ) if ap . ssid else None
+
best_aps_by_ssid [ ssid ] = current_ap
+
+
aps = list ( best_aps_by_ssid . values ())
+
# Сохраняем хотя бы SSID, если заметок нет
+
notes_to_save = { ap . ssid : ap . note for ap in aps if ap . ssid }
+
if not notes_to_save and aps :
+
notes_to_save = { ap . ssid : None for ap in aps if ap . ssid } # Сохраняем SSID без заметок
+
if notes_to_save :
+
self . storage . save_notes ( notes_to_save )
+
else :
+
logger . warning ( "No notes or SSIDs to save, skipping file write" )
+
+
logger . info ( f "Wi-Fi scan completed, found { len ( aps ) } unique networks" )
+
return WifiScanResult ( access_points = aps )
+
+
except Exception as e :
+
logger . error ( f "Error scanning Wi-Fi: { str ( e ) } " )
+
return WifiScanResult ( access_points = aps )
+
+
+
+
[docs]
+
def add_note ( self , ssid : str , note : str ) -> Tuple [ bool , Optional [ str ]]:
+
"""Добавляет заметку к сети Wi-Fi по SSID."""
+
try :
+
saved_notes = self . storage . load_notes ()
+
if ssid not in saved_notes or saved_notes [ ssid ] is None :
+
logger . info ( f "Added new note for network with SSID { ssid } " )
+
elif saved_notes [ ssid ] is not None :
+
logger . error ( f "Note for network with SSID { ssid } already exists" )
+
return False , self . translations . gettext ( "Заметка для сети с SSID {ssid} уже существует" ) . format ( ssid = ssid )
+
saved_notes [ ssid ] = note
+
self . storage . save_notes ( saved_notes )
+
logger . info ( f "Added note ' { note } ' for network with SSID { ssid } " )
+
return True , None
+
except Exception as e :
+
logger . error ( f "Error adding note for SSID { ssid } : { str ( e ) } " )
+
return False , self . translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = str ( e ))
+
+
+
+
[docs]
+
def update_note ( self , ssid : str , note : str ) -> Tuple [ bool , Optional [ str ]]:
+
"""Обновляет заметку сети Wi-Fi по SSID."""
+
try :
+
saved_notes = self . storage . load_notes ()
+
if ssid not in saved_notes :
+
logger . warning ( f "Network with SSID { ssid } not found" )
+
return False , self . translations . gettext ( "Сеть с SSID {ssid} не найдена" ) . format ( ssid = ssid )
+
if saved_notes [ ssid ] is None :
+
logger . error ( f "Note for network with SSID { ssid } does not exist" )
+
return False , self . translations . gettext ( "Заметка для сети с SSID {ssid} не существует" ) . format ( ssid = ssid )
+
saved_notes [ ssid ] = note
+
self . storage . save_notes ( saved_notes )
+
logger . info ( f "Updated note ' { note } ' for network with SSID { ssid } " )
+
return True , None
+
except Exception as e :
+
logger . error ( f "Error updating note for SSID { ssid } : { str ( e ) } " )
+
return False , self . translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = str ( e ))
+
+
+
+
[docs]
+
def delete_note ( self , ssid : str ) -> Tuple [ bool , Optional [ str ]]:
+
"""Удаляет заметку сети Wi-Fi по SSID."""
+
try :
+
saved_notes = self . storage . load_notes ()
+
if ssid not in saved_notes :
+
logger . warning ( f "Network with SSID { ssid } not found" )
+
return False , self . translations . gettext ( "Сеть с SSID {ssid} не найдена" ) . format ( ssid = ssid )
+
if saved_notes [ ssid ] is None :
+
logger . error ( f "Note for network with SSID { ssid } does not exist" )
+
return False , self . translations . gettext ( "Заметка для сети с SSID {ssid} не существует" ) . format ( ssid = ssid )
+
saved_notes [ ssid ] = None
+
self . storage . save_notes ( saved_notes )
+
logger . info ( f "Deleted note for network with SSID { ssid } " )
+
return True , None
+
except Exception as e :
+
logger . error ( f "Error deleting note for SSID { ssid } : { str ( e ) } " )
+
return False , self . translations . gettext ( "Внутренняя ошибка сервера: {error} " ) . format ( error = str ( e ))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/storage/json_storage.html b/SRV/docs/_build/html/_modules/storage/json_storage.html
new file mode 100644
index 0000000..c47b992
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/storage/json_storage.html
@@ -0,0 +1,537 @@
+
+
+
+
+
+
+
+ storage.json_storage - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for storage.json_storage
+import json
+import os
+import logging
+from passlib.context import CryptContext
+from typing import Optional
+from models.auth import UserCreate , UserResponse
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+DATA_DIR = os . getenv ( "GONETS_DATA_DIR" , "/etc/gonets" )
+DEFAULT_USERS_PATH = os . getenv ( "GONETS_USERS_PATH" ,
+ os . path . join ( DATA_DIR , "users.json" ))
+
+
+
+
[docs]
+
class JsonStorage :
+
"""Хранилище пользователей в JSON-файле.
+
+
Назначение:
+
+
- Обеспечивает простое файловое хранилище учетных записей без полноценной СУБД.
+
- Подходит для встраиваемых окружений, где критична минимальная зависимость от внешних сервисов.
+
+
Формат данных::
+
+
{
+
"users": [
+
{"id": int, "username": str, "password_hash": str, "is_admin": bool},
+
...
+
],
+
"next_id": int
+
}
+
+
Безопасность:
+
+
- Пароли никогда не сохраняются в открытом виде: используется `passlib.context.CryptContext` со схемой `bcrypt`.
+
- Файл создаётся с правами доступа ``0o600`` (чтение/запись только владельцу).
+
+
Ограничения:
+
+
- Нет блокировок для конкурентного доступа — в многопроцессной среде требуется внешняя синхронизация.
+
- Значение пути по умолчанию ``/etc/gonets/users.json`` предполагает наличие прав на запись и предварительную настройку окружения.
+
"""
+
+
def __init__ ( self , file_path : Optional [ str ] = None ):
+
"""Инициализирует хранилище пользователей.
+
+
Args:
+
file_path (str, optional): Путь к JSON-файлу для хранения данных.
+
По умолчанию "/etc/gonets/users.json".
+
+
Создаёт объект CryptContext для хеширования паролей и проверяет/создаёт
+
файл, если он отсутствует.
+
"""
+
self . file_path = file_path or DEFAULT_USERS_PATH
+
self . pwd_context = CryptContext ( schemes = [ "bcrypt" ], deprecated = "auto" )
+
self . _ensure_file_exists ()
+
+
def _ensure_file_exists ( self ):
+
"""Создаёт JSON-файл, если он не существует.
+
+
Создаёт родительские директории и файл с начальной структурой данных,
+
устанавливая права доступа 600.
+
+
Raises:
+
Exception: Если произошла ошибка при создании файла или директории.
+
"""
+
try :
+
logger . info ( f "Проверка существования файла { self . file_path } " )
+
if not os . path . exists ( self . file_path ):
+
logger . info ( f "Создание директории { os . path . dirname ( self . file_path ) } " )
+
os . makedirs ( os . path . dirname ( self . file_path ), exist_ok = True )
+
logger . info ( f "Создание файла { self . file_path } " )
+
with open ( self . file_path , "w" ) as f :
+
json . dump ({ "users" : [], "next_id" : 1 }, f )
+
os . chmod ( self . file_path , 0o600 )
+
logger . info ( f "Создан файл { self . file_path } " )
+
else :
+
logger . info ( f "Файл { self . file_path } уже существует" )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при создании файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
def _read_data ( self ) -> dict :
+
"""Читает данные из JSON-файла.
+
+
Returns:
+
dict: Словарь с данными пользователей и следующим ID.
+
+
Raises:
+
FileNotFoundError: Если файл отсутствует (возвращает пустую структуру).
+
Exception: Если произошла ошибка при чтении файла.
+
"""
+
try :
+
with open ( self . file_path , "r" ) as f :
+
return json . load ( f )
+
except FileNotFoundError :
+
logger . info (
+
f "Файл { self . file_path } не существует, возвращаем пустую структуру"
+
)
+
return { "users" : [], "next_id" : 1 }
+
except Exception as e :
+
logger . error (
+
f "Ошибка при чтении файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
def _write_data ( self , data : dict ):
+
"""Записывает данные в JSON-файл.
+
+
Args:
+
data (dict): Словарь с данными для записи (пользователи и next_id).
+
+
Raises:
+
Exception: Если произошла ошибка при записи в файл.
+
"""
+
try :
+
with open ( self . file_path , "w" ) as f :
+
json . dump ( data , f , indent = 2 )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при записи файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
[docs]
+
def create_user ( self , user : UserCreate ) -> UserResponse :
+
"""Создаёт нового пользователя с хешированием пароля.
+
+
Args:
+
user (UserCreate): Объект с данными нового пользователя (username, password, is_admin).
+
+
Returns:
+
UserResponse: Объект с данными созданного пользователя (id, username, is_admin).
+
+
Raises:
+
ValueError: Если пользователь с таким именем уже существует.
+
Exception: Если произошла ошибка при записи данных.
+
"""
+
try :
+
data = self . _read_data ()
+
users = data [ "users" ]
+
next_id = data [ "next_id" ]
+
if any ( u [ "username" ] == user . username for u in users ):
+
logger . error ( f "Пользователь { user . username } уже существует" )
+
raise ValueError ( "Пользователь уже существует" )
+
password_hash = self . pwd_context . hash ( user . password )
+
new_user = {
+
"id" : next_id ,
+
"username" : user . username ,
+
"password_hash" : password_hash ,
+
"is_admin" : user . is_admin ,
+
}
+
users . append ( new_user )
+
data [ "next_id" ] = next_id + 1
+
self . _write_data ( data )
+
logger . info (
+
f "Пользователь { user . username } успешно зарегистрирован" )
+
return UserResponse (
+
id = new_user [ "id" ],
+
username = new_user [ "username" ],
+
is_admin = new_user [ "is_admin" ],
+
)
+
except Exception as e :
+
logger . error (
+
f "Ошибка при создании пользователя { user . username } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
[docs]
+
def get_user_by_username ( self , username : str ) -> Optional [ dict ]:
+
"""Получает данные пользователя по имени.
+
+
Args:
+
username (str): Имя пользователя для поиска.
+
+
Returns:
+
Optional[dict]: Словарь с данными пользователя или None, если пользователь не найден.
+
+
Raises:
+
Exception: Если произошла ошибка при чтении файла.
+
"""
+
try :
+
if not os . path . exists ( self . file_path ):
+
logger . info (
+
f "Файл { self . file_path } не существует, пользователь { username } не найден"
+
)
+
return None
+
data = self . _read_data ()
+
for user in data [ "users" ]:
+
if user [ "username" ] == username :
+
return user
+
return None
+
except Exception as e :
+
logger . error (
+
f "Ошибка при получении пользователя { username } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
[docs]
+
def verify_password ( self , plain_password : str , hashed_password : str ) -> bool :
+
"""Проверяет соответствие введённого пароля хэшированному.
+
+
Args:
+
plain_password (str): Введённый пользователем пароль.
+
hashed_password (str): Хэшированный пароль из хранилища.
+
+
Returns:
+
bool: True, если пароль верен, False в противном случае.
+
"""
+
return self . pwd_context . verify ( plain_password , hashed_password )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/storage/session_storage.html b/SRV/docs/_build/html/_modules/storage/session_storage.html
new file mode 100644
index 0000000..c007ef2
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/storage/session_storage.html
@@ -0,0 +1,602 @@
+
+
+
+
+
+
+
+ storage.session_storage - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for storage.session_storage
+import json
+import os
+import uuid
+import logging
+from typing import Optional , Dict
+from datetime import datetime , timedelta
+
+logging . basicConfig ( level = logging . INFO )
+logger = logging . getLogger ( __name__ )
+
+DATA_DIR = os . getenv ( "GONETS_DATA_DIR" , "/etc/gonets" )
+DEFAULT_SESSIONS_PATH = os . getenv ( "GONETS_SESSIONS_PATH" ,
+ os . path . join ( DATA_DIR , "sessions.json" ))
+
+
+
+
[docs]
+
class SessionStorage :
+
"""Хранилище сессий пользователей в JSON-файле.
+
+
Назначение:
+
+
- Лёгкое файловое хранилище сессионных токенов, сопоставленных с ``client_id`` (например, IP клиента).
+
- Предназначено для сред без БД и persistent-кэшей; обеспечивает простые операции и автоочистку истёкших сессий.
+
+
Формат данных::
+
+
{
+
"sessions": {
+
"<session_id>": {
+
"username": str,
+
"token": str,
+
"client_id": str,
+
"created_at": ISO8601 str,
+
"expiry": ISO8601 str
+
},
+
...
+
}
+
}
+
+
Политика TTL:
+
+
- Каждая сессия действует 30 минут; метод ``clean_expired_sessions`` удаляет записи с истёкшим сроком.
+
+
Ограничения:
+
+
- Отсутствуют файловые блокировки — в многопроцессной среде возможны гонки.
+
- Путь по умолчанию ``/etc/gonets/sessions.json`` требует прав на запись.
+
"""
+
+
def __init__ ( self , file_path : Optional [ str ] = None ):
+
"""Инициализирует хранилище сессий.
+
+
Args:
+
file_path (str, optional): Путь к JSON-файлу для хранения сессий.
+
По умолчанию "/etc/gonets/sessions.json".
+
+
Создаёт файл и директории, если они отсутствуют, с правами доступа 600.
+
"""
+
self . file_path = file_path or DEFAULT_SESSIONS_PATH
+
self . _ensure_file_exists ()
+
+
def _ensure_file_exists ( self ):
+
"""Создаёт JSON-файл, если он не существует.
+
+
Создаёт родительские директории и файл с начальной структурой данных,
+
устанавливая права доступа 600.
+
+
Raises:
+
Exception: Если произошла ошибка при создании файла или директории.
+
"""
+
try :
+
logger . info ( f "Проверка существования файла { self . file_path } " )
+
if not os . path . exists ( self . file_path ):
+
logger . info ( f "Создание директории { os . path . dirname ( self . file_path ) } " )
+
os . makedirs ( os . path . dirname ( self . file_path ), exist_ok = True )
+
logger . info ( f "Создание файла { self . file_path } " )
+
with open ( self . file_path , "w" ) as f :
+
json . dump ({ "sessions" : {}}, f )
+
os . chmod ( self . file_path , 0o600 )
+
logger . info ( f "Создан файл { self . file_path } " )
+
else :
+
logger . info ( f "Файл { self . file_path } уже существует" )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при создании файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
def _read_data ( self ) -> Dict :
+
"""Читает данные из JSON-файла.
+
+
Returns:
+
Dict: Словарь с данными сессий.
+
+
Raises:
+
Exception: Если произошла ошибка при чтении файла.
+
"""
+
try :
+
with open ( self . file_path , "r" ) as f :
+
return json . load ( f )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при чтении файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
def _write_data ( self , data : Dict ):
+
"""Записывает данные в JSON-файл.
+
+
Args:
+
data (Dict): Словарь с данными сессий для записи.
+
+
Raises:
+
Exception: Если произошла ошибка при записи в файл.
+
"""
+
try :
+
with open ( self . file_path , "w" ) as f :
+
json . dump ( data , f , indent = 2 )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при записи файла { self . file_path } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
[docs]
+
def create_session ( self , username : str , token : str , client_id : str ) -> str :
+
"""Создаёт новую сессию и возвращает уникальный session_id.
+
+
Args:
+
username (str): Имя пользователя, для которого создаётся сессия.
+
token (str): Токен аутентификации.
+
client_id (str): Идентификатор клиента.
+
+
Returns:
+
str: Уникальный идентификатор сессии (session_id).
+
+
Raises:
+
Exception: Если произошла ошибка при создании или записи сессии.
+
+
Note:
+
Удаляет истекшие сессии перед созданием новой и устанавливает срок
+
действия 30 минут.
+
"""
+
try :
+
self . clean_expired_sessions ()
+
data = self . _read_data ()
+
session_id = str ( uuid . uuid4 ())
+
created_at = datetime . utcnow () . isoformat ()
+
expiry = ( datetime . utcnow () + timedelta ( minutes = 30 )) . isoformat ()
+
data [ "sessions" ][ session_id ] = {
+
"username" : username ,
+
"token" : token ,
+
"client_id" : client_id ,
+
"created_at" : created_at ,
+
"expiry" : expiry ,
+
}
+
self . _write_data ( data )
+
logger . info (
+
f "Сессия создана для пользователя { username } с session_id { session_id } "
+
)
+
return session_id
+
except Exception as e :
+
logger . error (
+
f "Ошибка при создании сессии для { username } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
[docs]
+
def get_latest_session ( self , client_id : str ) -> Optional [ Dict ]:
+
"""Получает последнюю активную сессию для указанного client_id.
+
+
Args:
+
client_id (str): Идентификатор клиента.
+
+
Returns:
+
Optional[Dict]: Словарь с данными последней активной сессии, включая
+
"session_id", или None, если сессия не найдена.
+
+
Raises:
+
Exception: Если произошла ошибка при чтении или обработке данных.
+
+
Note:
+
Очищает истекшие сессии перед поиском и возвращает сессию с самым
+
поздним временем создания.
+
"""
+
try :
+
self . clean_expired_sessions ()
+
data = self . _read_data ()
+
latest_session = None
+
latest_time = None
+
for session_id , session in data [ "sessions" ] . items ():
+
if session [ "client_id" ] == client_id :
+
expiry = datetime . fromisoformat ( session [ "expiry" ])
+
if datetime . utcnow () > expiry :
+
logger . info ( f "Сессия { session_id } истекла" )
+
del data [ "sessions" ][ session_id ]
+
continue
+
created_at = datetime . fromisoformat ( session [ "created_at" ])
+
if latest_time is None or created_at > latest_time :
+
latest_time = created_at
+
latest_session = session
+
latest_session [ "session_id" ] = session_id
+
if latest_session :
+
logger . info ( f "Найдена сессия для client_id { client_id } " )
+
return latest_session
+
logger . info ( f "Сессия для client_id { client_id } не найдена" )
+
self . _write_data ( data )
+
return None
+
except Exception as e :
+
logger . error (
+
f "Ошибка при получении сессии для client_id { client_id } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
[docs]
+
def delete_session ( self , session_id : str ):
+
"""Удаляет сессию по указанному session_id.
+
+
Args:
+
session_id (str): Уникальный идентификатор сессии.
+
+
Raises:
+
Exception: Если произошла ошибка при чтении или записи данных.
+
+
Note:
+
Очищает истекшие сессии перед удалением.
+
"""
+
try :
+
self . clean_expired_sessions ()
+
data = self . _read_data ()
+
if session_id in data [ "sessions" ]:
+
del data [ "sessions" ][ session_id ]
+
self . _write_data ( data )
+
logger . info ( f "Сессия с session_id { session_id } удалена" )
+
except Exception as e :
+
logger . error (
+
f "Ошибка при удалении сессии { session_id } : { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
[docs]
+
def clean_expired_sessions ( self ):
+
"""Удаляет все истекшие сессии из хранилища.
+
+
Raises:
+
Exception: Если произошла ошибка при чтении или записи данных.
+
+
Note:
+
Проверяет срок действия каждой сессии и удаляет те, что истекли,
+
записывая обновлённые данные.
+
"""
+
try :
+
data = self . _read_data ()
+
expired_sessions = []
+
current_time = datetime . utcnow ()
+
+
for session_id , session in list ( data [ "sessions" ] . items ()):
+
expiry = datetime . fromisoformat ( session [ "expiry" ])
+
if current_time > expiry :
+
expired_sessions . append ( session_id )
+
+
for session_id in expired_sessions :
+
del data [ "sessions" ][ session_id ]
+
logger . info (
+
f "Удалена истекшая сессия с session_id { session_id } " )
+
+
if expired_sessions :
+
self . _write_data ( data )
+
logger . info ( f "Удалено { len ( expired_sessions ) } истекших сессий" )
+
else :
+
logger . debug ( "Истекшие сессии не найдены" )
+
+
except Exception as e :
+
logger . error (
+
f "Ошибка при очистке истекших сессий: { type ( e ) . __name__ } - { str ( e ) } "
+
)
+
raise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_modules/utils/i18n.html b/SRV/docs/_build/html/_modules/utils/i18n.html
new file mode 100644
index 0000000..c2cd81c
--- /dev/null
+++ b/SRV/docs/_build/html/_modules/utils/i18n.html
@@ -0,0 +1,392 @@
+
+
+
+
+
+
+
+ utils.i18n - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+ Source code for utils.i18n
+"""
+Утилиты локализации и зависимости переводов для FastAPI.
+
+Содержимое модуля:
+ - `get_translations()`: фабрика зависимости FastAPI, считывающая локаль из
+ query-параметра `locale`, cookie `locale` или заголовка `Accept-Language`
+ (по приоритету), с дефолтом `ru`. Возвращает функцию-зависимость, которая
+ подгружает `babel.support.Translations` из каталога `SRV/locales`.
+ - `_()`: удобная обёртка для получения перевода строки с использованием
+ текущей локали (или дефолтного `ru`, если контекст запроса не передан).
+
+Особенности:
+ - При отсутствии словаря перевода для выбранной локали выполняется fallback на
+ русскую локаль, что гарантирует наличие строк интерфейса.
+ - Локаль определяется только по первому языку из `Accept-Language` (без
+ региональных суффиксов) для упрощения.
+ - Модуль ведёт отладочное логирование выбора и загрузки локали.
+"""
+from fastapi import Request , Depends
+from babel.support import Translations
+import logging
+
+logger = logging . getLogger ( __name__ )
+
+
+
[docs]
+
def get_translations () -> callable :
+
"""
+
Создаёт зависимость для загрузки переводов на основе заголовка Accept-Language
+
или параметра запроса 'locale'. По умолчанию используется русский язык.
+
"""
+
def translations_dependency ( request : Request ) -> Translations :
+
# Проверяем параметр запроса 'locale', если он есть
+
locale = request . query_params . get ( "locale" )
+
if not locale :
+
# Если параметр отсутствует, проверяем куку 'locale'
+
locale = request . cookies . get ( "locale" )
+
if not locale :
+
# Если куки нет, используем заголовок Accept-Language
+
locale = request . headers . get ( "Accept-Language" , "ru" ) . split ( "," )[ 0 ] . split ( "-" )[ 0 ]
+
+
logger . debug ( f "Determined locale: { locale } " )
+
try :
+
translations = Translations . load ( "locales" , [ locale ], "messages" )
+
if not translations . _catalog :
+
logger . warning ( f "Translations for locale { locale } not found, falling back to ru" )
+
translations = Translations . load ( "locales" , [ "ru" ], "messages" )
+
logger . debug ( f "Loaded translations for locale { locale } , catalog size: { len ( translations . _catalog ) } " )
+
return translations
+
except Exception as e :
+
logger . error ( f "Failed to load translations for locale { locale } : { str ( e ) } " )
+
# Fallback на русский
+
logger . debug ( "Falling back to ru translations" )
+
return Translations . load ( "locales" , [ "ru" ], "messages" )
+
+
return translations_dependency
+
+
+def _ ( msgid : str , request : Request = None ) -> str :
+ """
+ Переводит строку msgid для текущей локали.
+
+ Args:
+ msgid (str): Строка для перевода.
+ request (Request, optional): Объект запроса FastAPI для получения локали.
+
+ Returns:
+ str: Переведенная строка.
+ """
+ if request is None :
+ # Язык, который будет отображаться по умолчанию
+ translations = Translations . load ( "locales" , [ "ru" ], "messages" )
+ logger . debug ( f "Using default ru translations for msgid: { msgid } " )
+ else :
+ translations = get_translations ()( request )
+ logger . debug ( f "Using translations for locale { request . cookies . get ( 'locale' , 'ru' ) } for msgid: { msgid } " )
+ return translations . gettext ( msgid )
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_sources/api/config.rst.txt b/SRV/docs/_build/html/_sources/api/config.rst.txt
new file mode 100644
index 0000000..3dfa707
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/config.rst.txt
@@ -0,0 +1,9 @@
+Конфигурация
+============
+
+.. toctree::
+ :maxdepth: 1
+
+ config/cors
+ config/init_config
+ config/ports
diff --git a/SRV/docs/_build/html/_sources/api/config/cors.rst.txt b/SRV/docs/_build/html/_sources/api/config/cors.rst.txt
new file mode 100644
index 0000000..6dfd244
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/config/cors.rst.txt
@@ -0,0 +1,7 @@
+config.cors
+===========
+
+.. automodule:: config.cors
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/config/init_config.rst.txt b/SRV/docs/_build/html/_sources/api/config/init_config.rst.txt
new file mode 100644
index 0000000..3386987
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/config/init_config.rst.txt
@@ -0,0 +1,7 @@
+config.init_config
+===================
+
+.. automodule:: config.init_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/config/ports.rst.txt b/SRV/docs/_build/html/_sources/api/config/ports.rst.txt
new file mode 100644
index 0000000..b893ee3
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/config/ports.rst.txt
@@ -0,0 +1,7 @@
+config.ports
+============
+
+.. automodule:: config.ports
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/core.rst.txt b/SRV/docs/_build/html/_sources/api/core.rst.txt
new file mode 100644
index 0000000..f859c03
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/core.rst.txt
@@ -0,0 +1,7 @@
+Core
+====
+
+.. toctree::
+ :maxdepth: 1
+
+ core/cli_executor
diff --git a/SRV/docs/_build/html/_sources/api/core/cli_executor.rst.txt b/SRV/docs/_build/html/_sources/api/core/cli_executor.rst.txt
new file mode 100644
index 0000000..a305ccb
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/core/cli_executor.rst.txt
@@ -0,0 +1,7 @@
+core.cli_executor
+==================
+
+.. automodule:: core.cli_executor
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints.rst.txt
new file mode 100644
index 0000000..ca6734e
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints.rst.txt
@@ -0,0 +1,13 @@
+API эндпоинты
+=============
+
+.. toctree::
+ :maxdepth: 1
+
+ endpoints/auth
+ endpoints/ip
+ endpoints/network
+ endpoints/settings
+ endpoints/subnet
+ endpoints/ufw
+ endpoints/websocket
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/auth.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/auth.rst.txt
new file mode 100644
index 0000000..df442bf
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/auth.rst.txt
@@ -0,0 +1,7 @@
+api.v1.auth
+===========
+
+.. automodule:: api.v1.auth
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/ip.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/ip.rst.txt
new file mode 100644
index 0000000..a3199b5
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/ip.rst.txt
@@ -0,0 +1,7 @@
+api.v1.ip
+=========
+
+.. automodule:: api.v1.ip
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/network.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/network.rst.txt
new file mode 100644
index 0000000..86ff9c4
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/network.rst.txt
@@ -0,0 +1,7 @@
+api.v1.network
+==============
+
+.. automodule:: api.v1.network
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/settings.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/settings.rst.txt
new file mode 100644
index 0000000..7958dd6
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/settings.rst.txt
@@ -0,0 +1,7 @@
+api.v1.settings
+================
+
+.. automodule:: api.v1.settings
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/subnet.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/subnet.rst.txt
new file mode 100644
index 0000000..05ddfac
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/subnet.rst.txt
@@ -0,0 +1,7 @@
+api.v1.subnet
+=============
+
+.. automodule:: api.v1.subnet
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/ufw.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/ufw.rst.txt
new file mode 100644
index 0000000..cd5fc6e
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/ufw.rst.txt
@@ -0,0 +1,7 @@
+api.v1.ufw
+==========
+
+.. automodule:: api.v1.ufw
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/endpoints/websocket.rst.txt b/SRV/docs/_build/html/_sources/api/endpoints/websocket.rst.txt
new file mode 100644
index 0000000..0b8116c
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/endpoints/websocket.rst.txt
@@ -0,0 +1,7 @@
+api.v1.websocket
+================
+
+.. automodule:: api.v1.websocket
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models.rst.txt b/SRV/docs/_build/html/_sources/api/models.rst.txt
new file mode 100644
index 0000000..49197ba
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models.rst.txt
@@ -0,0 +1,12 @@
+Модели
+======
+
+.. toctree::
+ :maxdepth: 1
+
+ models/auth
+ models/network
+ models/request
+ models/subnet
+ models/ufw
+ models/wifi
diff --git a/SRV/docs/_build/html/_sources/api/models/auth.rst.txt b/SRV/docs/_build/html/_sources/api/models/auth.rst.txt
new file mode 100644
index 0000000..2c266d2
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/auth.rst.txt
@@ -0,0 +1,7 @@
+models.auth
+===========
+
+.. automodule:: models.auth
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models/network.rst.txt b/SRV/docs/_build/html/_sources/api/models/network.rst.txt
new file mode 100644
index 0000000..2e88043
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/network.rst.txt
@@ -0,0 +1,7 @@
+models.network
+==============
+
+.. automodule:: models.network
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models/request.rst.txt b/SRV/docs/_build/html/_sources/api/models/request.rst.txt
new file mode 100644
index 0000000..33c43ba
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/request.rst.txt
@@ -0,0 +1,7 @@
+models.request
+==============
+
+.. automodule:: models.request
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models/subnet.rst.txt b/SRV/docs/_build/html/_sources/api/models/subnet.rst.txt
new file mode 100644
index 0000000..de0c59c
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/subnet.rst.txt
@@ -0,0 +1,7 @@
+models.subnet
+=============
+
+.. automodule:: models.subnet
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models/ufw.rst.txt b/SRV/docs/_build/html/_sources/api/models/ufw.rst.txt
new file mode 100644
index 0000000..881593a
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/ufw.rst.txt
@@ -0,0 +1,7 @@
+models.ufw
+==========
+
+.. automodule:: models.ufw
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/models/wifi.rst.txt b/SRV/docs/_build/html/_sources/api/models/wifi.rst.txt
new file mode 100644
index 0000000..399ba8a
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/models/wifi.rst.txt
@@ -0,0 +1,7 @@
+models.wifi
+===========
+
+.. automodule:: models.wifi
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services.rst.txt b/SRV/docs/_build/html/_sources/api/services.rst.txt
new file mode 100644
index 0000000..ebca7d0
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services.rst.txt
@@ -0,0 +1,21 @@
+Сервисы
+=======
+
+.. automodule:: services
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. toctree::
+ :maxdepth: 1
+
+ services/arp_service
+ services/auth_service
+ services/connect_service
+ services/firewalld_service
+ services/ip_service
+ services/network_service
+ services/settings_service
+ services/subnet_service
+ services/ufw_service
+ services/wifi_service
diff --git a/SRV/docs/_build/html/_sources/api/services/arp_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/arp_service.rst.txt
new file mode 100644
index 0000000..c99d729
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/arp_service.rst.txt
@@ -0,0 +1,7 @@
+ARP Service
+===========
+
+.. automodule:: services.arp_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/auth_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/auth_service.rst.txt
new file mode 100644
index 0000000..0cbe8ee
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/auth_service.rst.txt
@@ -0,0 +1,7 @@
+Auth Service
+============
+
+.. automodule:: services.auth_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/connect_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/connect_service.rst.txt
new file mode 100644
index 0000000..237c613
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/connect_service.rst.txt
@@ -0,0 +1,7 @@
+Connect Service
+===============
+
+.. automodule:: services.connect_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/firewalld_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/firewalld_service.rst.txt
new file mode 100644
index 0000000..9b6332f
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/firewalld_service.rst.txt
@@ -0,0 +1,7 @@
+Firewalld Service
+=================
+
+.. automodule:: services.firewalld_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/ip_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/ip_service.rst.txt
new file mode 100644
index 0000000..b976a29
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/ip_service.rst.txt
@@ -0,0 +1,7 @@
+IP Service
+==========
+
+.. automodule:: services.ip_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/network_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/network_service.rst.txt
new file mode 100644
index 0000000..2794801
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/network_service.rst.txt
@@ -0,0 +1,7 @@
+Network Service
+===============
+
+.. automodule:: services.network_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/settings_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/settings_service.rst.txt
new file mode 100644
index 0000000..c459fca
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/settings_service.rst.txt
@@ -0,0 +1,7 @@
+Settings Service
+================
+
+.. automodule:: services.settings_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/subnet_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/subnet_service.rst.txt
new file mode 100644
index 0000000..d059f8f
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/subnet_service.rst.txt
@@ -0,0 +1,7 @@
+Subnet Service
+==============
+
+.. automodule:: services.subnet_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/ufw_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/ufw_service.rst.txt
new file mode 100644
index 0000000..14ddc77
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/ufw_service.rst.txt
@@ -0,0 +1,7 @@
+UFW Service
+===========
+
+.. automodule:: services.ufw_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/services/wifi_service.rst.txt b/SRV/docs/_build/html/_sources/api/services/wifi_service.rst.txt
new file mode 100644
index 0000000..787b7c5
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/services/wifi_service.rst.txt
@@ -0,0 +1,7 @@
+Wifi Service
+============
+
+.. automodule:: services.wifi_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/storage.rst.txt b/SRV/docs/_build/html/_sources/api/storage.rst.txt
new file mode 100644
index 0000000..0a04d57
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/storage.rst.txt
@@ -0,0 +1,8 @@
+Хранилища
+=========
+
+.. toctree::
+ :maxdepth: 1
+
+ storage/json_storage
+ storage/session_storage
diff --git a/SRV/docs/_build/html/_sources/api/storage/json_storage.rst.txt b/SRV/docs/_build/html/_sources/api/storage/json_storage.rst.txt
new file mode 100644
index 0000000..0bb5039
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/storage/json_storage.rst.txt
@@ -0,0 +1,7 @@
+storage.json_storage
+=====================
+
+.. automodule:: storage.json_storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/storage/session_storage.rst.txt b/SRV/docs/_build/html/_sources/api/storage/session_storage.rst.txt
new file mode 100644
index 0000000..9ae2a9a
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/storage/session_storage.rst.txt
@@ -0,0 +1,7 @@
+storage.session_storage
+=======================
+
+.. automodule:: storage.session_storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/api/utils.rst.txt b/SRV/docs/_build/html/_sources/api/utils.rst.txt
new file mode 100644
index 0000000..c50b082
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/utils.rst.txt
@@ -0,0 +1,7 @@
+Утилиты
+=======
+
+.. toctree::
+ :maxdepth: 1
+
+ utils/i18n
diff --git a/SRV/docs/_build/html/_sources/api/utils/i18n.rst.txt b/SRV/docs/_build/html/_sources/api/utils/i18n.rst.txt
new file mode 100644
index 0000000..74cd021
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/api/utils/i18n.rst.txt
@@ -0,0 +1,7 @@
+utils.i18n
+==========
+
+.. automodule:: utils.i18n
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/_build/html/_sources/index.rst.txt b/SRV/docs/_build/html/_sources/index.rst.txt
new file mode 100644
index 0000000..4887d9d
--- /dev/null
+++ b/SRV/docs/_build/html/_sources/index.rst.txt
@@ -0,0 +1,19 @@
+GONETS SRV Documentation
+=========================
+
+Добро пожаловать в документацию серверной части GONETS (SRV).
+
+Содержание
+----------
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Разделы
+
+ api/services
+ api/models
+ api/endpoints
+ api/config
+ api/core
+ api/storage
+ api/utils
diff --git a/SRV/docs/_build/html/_static/basic.css b/SRV/docs/_build/html/_static/basic.css
new file mode 100644
index 0000000..4738b2e
--- /dev/null
+++ b/SRV/docs/_build/html/_static/basic.css
@@ -0,0 +1,906 @@
+/*
+ * Sphinx stylesheet -- basic theme.
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin-top: 10px;
+}
+
+ul.search li {
+ padding: 5px 0;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+aside.footnote > span,
+div.citation > span {
+ float: left;
+}
+aside.footnote > span:last-of-type,
+div.citation > span:last-of-type {
+ padding-right: 0.5em;
+}
+aside.footnote > p {
+ margin-left: 2em;
+}
+div.citation > p {
+ margin-left: 4em;
+}
+aside.footnote > p:last-of-type,
+div.citation > p:last-of-type {
+ margin-bottom: 0em;
+}
+aside.footnote > p:last-of-type:after,
+div.citation > p:last-of-type:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+ white-space: nowrap;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/debug.css b/SRV/docs/_build/html/_static/debug.css
new file mode 100644
index 0000000..74d4aec
--- /dev/null
+++ b/SRV/docs/_build/html/_static/debug.css
@@ -0,0 +1,69 @@
+/*
+ This CSS file should be overridden by the theme authors. It's
+ meant for debugging and developing the skeleton that this theme provides.
+*/
+body {
+ font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji";
+ background: lavender;
+}
+.sb-announcement {
+ background: rgb(131, 131, 131);
+}
+.sb-announcement__inner {
+ background: black;
+ color: white;
+}
+.sb-header {
+ background: lightskyblue;
+}
+.sb-header__inner {
+ background: royalblue;
+ color: white;
+}
+.sb-header-secondary {
+ background: lightcyan;
+}
+.sb-header-secondary__inner {
+ background: cornflowerblue;
+ color: white;
+}
+.sb-sidebar-primary {
+ background: lightgreen;
+}
+.sb-main {
+ background: blanchedalmond;
+}
+.sb-main__inner {
+ background: antiquewhite;
+}
+.sb-header-article {
+ background: lightsteelblue;
+}
+.sb-article-container {
+ background: snow;
+}
+.sb-article-main {
+ background: white;
+}
+.sb-footer-article {
+ background: lightpink;
+}
+.sb-sidebar-secondary {
+ background: lightgoldenrodyellow;
+}
+.sb-footer-content {
+ background: plum;
+}
+.sb-footer-content__inner {
+ background: palevioletred;
+}
+.sb-footer {
+ background: pink;
+}
+.sb-footer__inner {
+ background: salmon;
+}
+.sb-article {
+ background: white;
+}
diff --git a/SRV/docs/_build/html/_static/doctools.js b/SRV/docs/_build/html/_static/doctools.js
new file mode 100644
index 0000000..0398ebb
--- /dev/null
+++ b/SRV/docs/_build/html/_static/doctools.js
@@ -0,0 +1,149 @@
+/*
+ * Base JavaScript utilities for all Sphinx HTML documentation.
+ */
+"use strict";
+
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
+const _ready = (callback) => {
+ if (document.readyState !== "loading") {
+ callback();
+ } else {
+ document.addEventListener("DOMContentLoaded", callback);
+ }
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const Documentation = {
+ init: () => {
+ Documentation.initDomainIndexTable();
+ Documentation.initOnKeyListeners();
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS: {},
+ PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
+ LOCALE: "unknown",
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext: (string) => {
+ const translated = Documentation.TRANSLATIONS[string];
+ switch (typeof translated) {
+ case "undefined":
+ return string; // no translation
+ case "string":
+ return translated; // translation exists
+ default:
+ return translated[0]; // (singular, plural) translation tuple exists
+ }
+ },
+
+ ngettext: (singular, plural, n) => {
+ const translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated !== "undefined")
+ return translated[Documentation.PLURAL_EXPR(n)];
+ return n === 1 ? singular : plural;
+ },
+
+ addTranslations: (catalog) => {
+ Object.assign(Documentation.TRANSLATIONS, catalog.messages);
+ Documentation.PLURAL_EXPR = new Function(
+ "n",
+ `return (${catalog.plural_expr})`
+ );
+ Documentation.LOCALE = catalog.locale;
+ },
+
+ /**
+ * helper function to focus on search bar
+ */
+ focusSearchBar: () => {
+ document.querySelectorAll("input[name=q]")[0]?.focus();
+ },
+
+ /**
+ * Initialise the domain index toggle buttons
+ */
+ initDomainIndexTable: () => {
+ const toggler = (el) => {
+ const idNumber = el.id.substr(7);
+ const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
+ if (el.src.substr(-9) === "minus.png") {
+ el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
+ toggledRows.forEach((el) => (el.style.display = "none"));
+ } else {
+ el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
+ toggledRows.forEach((el) => (el.style.display = ""));
+ }
+ };
+
+ const togglerElements = document.querySelectorAll("img.toggler");
+ togglerElements.forEach((el) =>
+ el.addEventListener("click", (event) => toggler(event.currentTarget))
+ );
+ togglerElements.forEach((el) => (el.style.display = ""));
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
+ },
+
+ initOnKeyListeners: () => {
+ // only install a listener if it is really needed
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
+
+ if (!event.shiftKey) {
+ switch (event.key) {
+ case "ArrowLeft":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const prevLink = document.querySelector('link[rel="prev"]');
+ if (prevLink && prevLink.href) {
+ window.location.href = prevLink.href;
+ event.preventDefault();
+ }
+ break;
+ case "ArrowRight":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const nextLink = document.querySelector('link[rel="next"]');
+ if (nextLink && nextLink.href) {
+ window.location.href = nextLink.href;
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ // some keyboard layouts may need Shift to get /
+ switch (event.key) {
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
+ Documentation.focusSearchBar();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+// quick alias for translations
+const _ = Documentation.gettext;
+
+_ready(Documentation.init);
diff --git a/SRV/docs/_build/html/_static/documentation_options.js b/SRV/docs/_build/html/_static/documentation_options.js
new file mode 100644
index 0000000..74029b8
--- /dev/null
+++ b/SRV/docs/_build/html/_static/documentation_options.js
@@ -0,0 +1,13 @@
+const DOCUMENTATION_OPTIONS = {
+ VERSION: '0.0.3',
+ LANGUAGE: 'en',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'html',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '.txt',
+ NAVIGATION_WITH_KEYS: false,
+ SHOW_SEARCH_SUMMARY: true,
+ ENABLE_SEARCH_SHORTCUTS: true,
+};
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/file.png b/SRV/docs/_build/html/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/SRV/docs/_build/html/_static/file.png differ
diff --git a/SRV/docs/_build/html/_static/language_data.js b/SRV/docs/_build/html/_static/language_data.js
new file mode 100644
index 0000000..c7fe6c6
--- /dev/null
+++ b/SRV/docs/_build/html/_static/language_data.js
@@ -0,0 +1,192 @@
+/*
+ * This script contains the language-specific data used by searchtools.js,
+ * namely the list of stopwords, stemmer, scorer and splitter.
+ */
+
+var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
+
+
+/* Non-minified version is copied as a separate JS file, if available */
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+ var step2list = {
+ ational: 'ate',
+ tional: 'tion',
+ enci: 'ence',
+ anci: 'ance',
+ izer: 'ize',
+ bli: 'ble',
+ alli: 'al',
+ entli: 'ent',
+ eli: 'e',
+ ousli: 'ous',
+ ization: 'ize',
+ ation: 'ate',
+ ator: 'ate',
+ alism: 'al',
+ iveness: 'ive',
+ fulness: 'ful',
+ ousness: 'ous',
+ aliti: 'al',
+ iviti: 'ive',
+ biliti: 'ble',
+ logi: 'log'
+ };
+
+ var step3list = {
+ icate: 'ic',
+ ative: '',
+ alize: 'al',
+ iciti: 'ic',
+ ical: 'ic',
+ ful: '',
+ ness: ''
+ };
+
+ var c = "[^aeiou]"; // consonant
+ var v = "[aeiouy]"; // vowel
+ var C = c + "[^aeiouy]*"; // consonant sequence
+ var V = v + "[aeiou]*"; // vowel sequence
+
+ var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
+ var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
+ var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
+ var s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ this.stemWord = function (w) {
+ var stem;
+ var suffix;
+ var firstch;
+ var origword = w;
+
+ if (w.length < 3)
+ return w;
+
+ var re;
+ var re2;
+ var re3;
+ var re4;
+
+ firstch = w.substr(0,1);
+ if (firstch == "y")
+ w = firstch.toUpperCase() + w.substr(1);
+
+ // Step 1a
+ re = /^(.+?)(ss|i)es$/;
+ re2 = /^(.+?)([^s])s$/;
+
+ if (re.test(w))
+ w = w.replace(re,"$1$2");
+ else if (re2.test(w))
+ w = w.replace(re2,"$1$2");
+
+ // Step 1b
+ re = /^(.+?)eed$/;
+ re2 = /^(.+?)(ed|ing)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = new RegExp(mgr0);
+ if (re.test(fp[1])) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = new RegExp(s_v);
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = /(at|bl|iz)$/;
+ re3 = new RegExp("([^aeiouylsz])\\1$");
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re2.test(w))
+ w = w + "e";
+ else if (re3.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ else if (re4.test(w))
+ w = w + "e";
+ }
+ }
+
+ // Step 1c
+ re = /^(.+?)y$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(s_v);
+ if (re.test(stem))
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step2list[suffix];
+ }
+
+ // Step 3
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step3list[suffix];
+ }
+
+ // Step 4
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ re2 = /^(.+?)(s|t)(ion)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ if (re.test(stem))
+ w = stem;
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = new RegExp(mgr1);
+ if (re2.test(stem))
+ w = stem;
+ }
+
+ // Step 5
+ re = /^(.+?)e$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ re2 = new RegExp(meq1);
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+ w = stem;
+ }
+ re = /ll$/;
+ re2 = new RegExp(mgr1);
+ if (re.test(w) && re2.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+ if (firstch == "y")
+ w = firstch.toLowerCase() + w.substr(1);
+ return w;
+ }
+}
+
diff --git a/SRV/docs/_build/html/_static/minus.png b/SRV/docs/_build/html/_static/minus.png
new file mode 100644
index 0000000..d96755f
Binary files /dev/null and b/SRV/docs/_build/html/_static/minus.png differ
diff --git a/SRV/docs/_build/html/_static/plus.png b/SRV/docs/_build/html/_static/plus.png
new file mode 100644
index 0000000..7107cec
Binary files /dev/null and b/SRV/docs/_build/html/_static/plus.png differ
diff --git a/SRV/docs/_build/html/_static/pygments.css b/SRV/docs/_build/html/_static/pygments.css
new file mode 100644
index 0000000..9d1083b
--- /dev/null
+++ b/SRV/docs/_build/html/_static/pygments.css
@@ -0,0 +1,250 @@
+.highlight pre { line-height: 125%; }
+.highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+.highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+.highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight .hll { background-color: #fdf2e2 }
+.highlight { background: #f2f2f2; color: #1E1E1E }
+.highlight .c { color: #515151 } /* Comment */
+.highlight .err { color: #D71835 } /* Error */
+.highlight .k { color: #8045E5 } /* Keyword */
+.highlight .l { color: #7F4707 } /* Literal */
+.highlight .n { color: #1E1E1E } /* Name */
+.highlight .o { color: #163 } /* Operator */
+.highlight .p { color: #1E1E1E } /* Punctuation */
+.highlight .ch { color: #515151 } /* Comment.Hashbang */
+.highlight .cm { color: #515151 } /* Comment.Multiline */
+.highlight .cp { color: #515151 } /* Comment.Preproc */
+.highlight .cpf { color: #515151 } /* Comment.PreprocFile */
+.highlight .c1 { color: #515151 } /* Comment.Single */
+.highlight .cs { color: #515151 } /* Comment.Special */
+.highlight .gd { color: #00749C } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gh { color: #00749C } /* Generic.Heading */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #00749C } /* Generic.Subheading */
+.highlight .kc { color: #8045E5 } /* Keyword.Constant */
+.highlight .kd { color: #8045E5 } /* Keyword.Declaration */
+.highlight .kn { color: #8045E5 } /* Keyword.Namespace */
+.highlight .kp { color: #8045E5 } /* Keyword.Pseudo */
+.highlight .kr { color: #8045E5 } /* Keyword.Reserved */
+.highlight .kt { color: #7F4707 } /* Keyword.Type */
+.highlight .ld { color: #7F4707 } /* Literal.Date */
+.highlight .m { color: #7F4707 } /* Literal.Number */
+.highlight .s { color: #163 } /* Literal.String */
+.highlight .na { color: #7F4707 } /* Name.Attribute */
+.highlight .nb { color: #7F4707 } /* Name.Builtin */
+.highlight .nc { color: #00749C } /* Name.Class */
+.highlight .no { color: #00749C } /* Name.Constant */
+.highlight .nd { color: #7F4707 } /* Name.Decorator */
+.highlight .ni { color: #163 } /* Name.Entity */
+.highlight .ne { color: #8045E5 } /* Name.Exception */
+.highlight .nf { color: #00749C } /* Name.Function */
+.highlight .nl { color: #7F4707 } /* Name.Label */
+.highlight .nn { color: #1E1E1E } /* Name.Namespace */
+.highlight .nx { color: #1E1E1E } /* Name.Other */
+.highlight .py { color: #00749C } /* Name.Property */
+.highlight .nt { color: #00749C } /* Name.Tag */
+.highlight .nv { color: #D71835 } /* Name.Variable */
+.highlight .ow { color: #8045E5 } /* Operator.Word */
+.highlight .pm { color: #1E1E1E } /* Punctuation.Marker */
+.highlight .w { color: #1E1E1E } /* Text.Whitespace */
+.highlight .mb { color: #7F4707 } /* Literal.Number.Bin */
+.highlight .mf { color: #7F4707 } /* Literal.Number.Float */
+.highlight .mh { color: #7F4707 } /* Literal.Number.Hex */
+.highlight .mi { color: #7F4707 } /* Literal.Number.Integer */
+.highlight .mo { color: #7F4707 } /* Literal.Number.Oct */
+.highlight .sa { color: #163 } /* Literal.String.Affix */
+.highlight .sb { color: #163 } /* Literal.String.Backtick */
+.highlight .sc { color: #163 } /* Literal.String.Char */
+.highlight .dl { color: #163 } /* Literal.String.Delimiter */
+.highlight .sd { color: #163 } /* Literal.String.Doc */
+.highlight .s2 { color: #163 } /* Literal.String.Double */
+.highlight .se { color: #163 } /* Literal.String.Escape */
+.highlight .sh { color: #163 } /* Literal.String.Heredoc */
+.highlight .si { color: #163 } /* Literal.String.Interpol */
+.highlight .sx { color: #163 } /* Literal.String.Other */
+.highlight .sr { color: #D71835 } /* Literal.String.Regex */
+.highlight .s1 { color: #163 } /* Literal.String.Single */
+.highlight .ss { color: #00749C } /* Literal.String.Symbol */
+.highlight .bp { color: #7F4707 } /* Name.Builtin.Pseudo */
+.highlight .fm { color: #00749C } /* Name.Function.Magic */
+.highlight .vc { color: #D71835 } /* Name.Variable.Class */
+.highlight .vg { color: #D71835 } /* Name.Variable.Global */
+.highlight .vi { color: #D71835 } /* Name.Variable.Instance */
+.highlight .vm { color: #7F4707 } /* Name.Variable.Magic */
+.highlight .il { color: #7F4707 } /* Literal.Number.Integer.Long */
+@media not print {
+body[data-theme="dark"] .highlight pre { line-height: 125%; }
+body[data-theme="dark"] .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+body[data-theme="dark"] .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+body[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+body[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+body[data-theme="dark"] .highlight .hll { background-color: #404040 }
+body[data-theme="dark"] .highlight { background: #202020; color: #D0D0D0 }
+body[data-theme="dark"] .highlight .c { color: #ABABAB; font-style: italic } /* Comment */
+body[data-theme="dark"] .highlight .err { color: #A61717; background-color: #E3D2D2 } /* Error */
+body[data-theme="dark"] .highlight .esc { color: #D0D0D0 } /* Escape */
+body[data-theme="dark"] .highlight .g { color: #D0D0D0 } /* Generic */
+body[data-theme="dark"] .highlight .k { color: #6EBF26; font-weight: bold } /* Keyword */
+body[data-theme="dark"] .highlight .l { color: #D0D0D0 } /* Literal */
+body[data-theme="dark"] .highlight .n { color: #D0D0D0 } /* Name */
+body[data-theme="dark"] .highlight .o { color: #D0D0D0 } /* Operator */
+body[data-theme="dark"] .highlight .x { color: #D0D0D0 } /* Other */
+body[data-theme="dark"] .highlight .p { color: #D0D0D0 } /* Punctuation */
+body[data-theme="dark"] .highlight .ch { color: #ABABAB; font-style: italic } /* Comment.Hashbang */
+body[data-theme="dark"] .highlight .cm { color: #ABABAB; font-style: italic } /* Comment.Multiline */
+body[data-theme="dark"] .highlight .cp { color: #FF3A3A; font-weight: bold } /* Comment.Preproc */
+body[data-theme="dark"] .highlight .cpf { color: #ABABAB; font-style: italic } /* Comment.PreprocFile */
+body[data-theme="dark"] .highlight .c1 { color: #ABABAB; font-style: italic } /* Comment.Single */
+body[data-theme="dark"] .highlight .cs { color: #E50808; font-weight: bold; background-color: #520000 } /* Comment.Special */
+body[data-theme="dark"] .highlight .gd { color: #FF3A3A } /* Generic.Deleted */
+body[data-theme="dark"] .highlight .ge { color: #D0D0D0; font-style: italic } /* Generic.Emph */
+body[data-theme="dark"] .highlight .ges { color: #D0D0D0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+body[data-theme="dark"] .highlight .gr { color: #FF3A3A } /* Generic.Error */
+body[data-theme="dark"] .highlight .gh { color: #FFF; font-weight: bold } /* Generic.Heading */
+body[data-theme="dark"] .highlight .gi { color: #589819 } /* Generic.Inserted */
+body[data-theme="dark"] .highlight .go { color: #CCC } /* Generic.Output */
+body[data-theme="dark"] .highlight .gp { color: #AAA } /* Generic.Prompt */
+body[data-theme="dark"] .highlight .gs { color: #D0D0D0; font-weight: bold } /* Generic.Strong */
+body[data-theme="dark"] .highlight .gu { color: #FFF; text-decoration: underline } /* Generic.Subheading */
+body[data-theme="dark"] .highlight .gt { color: #FF3A3A } /* Generic.Traceback */
+body[data-theme="dark"] .highlight .kc { color: #6EBF26; font-weight: bold } /* Keyword.Constant */
+body[data-theme="dark"] .highlight .kd { color: #6EBF26; font-weight: bold } /* Keyword.Declaration */
+body[data-theme="dark"] .highlight .kn { color: #6EBF26; font-weight: bold } /* Keyword.Namespace */
+body[data-theme="dark"] .highlight .kp { color: #6EBF26 } /* Keyword.Pseudo */
+body[data-theme="dark"] .highlight .kr { color: #6EBF26; font-weight: bold } /* Keyword.Reserved */
+body[data-theme="dark"] .highlight .kt { color: #6EBF26; font-weight: bold } /* Keyword.Type */
+body[data-theme="dark"] .highlight .ld { color: #D0D0D0 } /* Literal.Date */
+body[data-theme="dark"] .highlight .m { color: #51B2FD } /* Literal.Number */
+body[data-theme="dark"] .highlight .s { color: #ED9D13 } /* Literal.String */
+body[data-theme="dark"] .highlight .na { color: #BBB } /* Name.Attribute */
+body[data-theme="dark"] .highlight .nb { color: #2FBCCD } /* Name.Builtin */
+body[data-theme="dark"] .highlight .nc { color: #71ADFF; text-decoration: underline } /* Name.Class */
+body[data-theme="dark"] .highlight .no { color: #40FFFF } /* Name.Constant */
+body[data-theme="dark"] .highlight .nd { color: #FFA500 } /* Name.Decorator */
+body[data-theme="dark"] .highlight .ni { color: #D0D0D0 } /* Name.Entity */
+body[data-theme="dark"] .highlight .ne { color: #BBB } /* Name.Exception */
+body[data-theme="dark"] .highlight .nf { color: #71ADFF } /* Name.Function */
+body[data-theme="dark"] .highlight .nl { color: #D0D0D0 } /* Name.Label */
+body[data-theme="dark"] .highlight .nn { color: #71ADFF; text-decoration: underline } /* Name.Namespace */
+body[data-theme="dark"] .highlight .nx { color: #D0D0D0 } /* Name.Other */
+body[data-theme="dark"] .highlight .py { color: #D0D0D0 } /* Name.Property */
+body[data-theme="dark"] .highlight .nt { color: #6EBF26; font-weight: bold } /* Name.Tag */
+body[data-theme="dark"] .highlight .nv { color: #40FFFF } /* Name.Variable */
+body[data-theme="dark"] .highlight .ow { color: #6EBF26; font-weight: bold } /* Operator.Word */
+body[data-theme="dark"] .highlight .pm { color: #D0D0D0 } /* Punctuation.Marker */
+body[data-theme="dark"] .highlight .w { color: #666 } /* Text.Whitespace */
+body[data-theme="dark"] .highlight .mb { color: #51B2FD } /* Literal.Number.Bin */
+body[data-theme="dark"] .highlight .mf { color: #51B2FD } /* Literal.Number.Float */
+body[data-theme="dark"] .highlight .mh { color: #51B2FD } /* Literal.Number.Hex */
+body[data-theme="dark"] .highlight .mi { color: #51B2FD } /* Literal.Number.Integer */
+body[data-theme="dark"] .highlight .mo { color: #51B2FD } /* Literal.Number.Oct */
+body[data-theme="dark"] .highlight .sa { color: #ED9D13 } /* Literal.String.Affix */
+body[data-theme="dark"] .highlight .sb { color: #ED9D13 } /* Literal.String.Backtick */
+body[data-theme="dark"] .highlight .sc { color: #ED9D13 } /* Literal.String.Char */
+body[data-theme="dark"] .highlight .dl { color: #ED9D13 } /* Literal.String.Delimiter */
+body[data-theme="dark"] .highlight .sd { color: #ED9D13 } /* Literal.String.Doc */
+body[data-theme="dark"] .highlight .s2 { color: #ED9D13 } /* Literal.String.Double */
+body[data-theme="dark"] .highlight .se { color: #ED9D13 } /* Literal.String.Escape */
+body[data-theme="dark"] .highlight .sh { color: #ED9D13 } /* Literal.String.Heredoc */
+body[data-theme="dark"] .highlight .si { color: #ED9D13 } /* Literal.String.Interpol */
+body[data-theme="dark"] .highlight .sx { color: #FFA500 } /* Literal.String.Other */
+body[data-theme="dark"] .highlight .sr { color: #ED9D13 } /* Literal.String.Regex */
+body[data-theme="dark"] .highlight .s1 { color: #ED9D13 } /* Literal.String.Single */
+body[data-theme="dark"] .highlight .ss { color: #ED9D13 } /* Literal.String.Symbol */
+body[data-theme="dark"] .highlight .bp { color: #2FBCCD } /* Name.Builtin.Pseudo */
+body[data-theme="dark"] .highlight .fm { color: #71ADFF } /* Name.Function.Magic */
+body[data-theme="dark"] .highlight .vc { color: #40FFFF } /* Name.Variable.Class */
+body[data-theme="dark"] .highlight .vg { color: #40FFFF } /* Name.Variable.Global */
+body[data-theme="dark"] .highlight .vi { color: #40FFFF } /* Name.Variable.Instance */
+body[data-theme="dark"] .highlight .vm { color: #40FFFF } /* Name.Variable.Magic */
+body[data-theme="dark"] .highlight .il { color: #51B2FD } /* Literal.Number.Integer.Long */
+@media (prefers-color-scheme: dark) {
+body:not([data-theme="light"]) .highlight pre { line-height: 125%; }
+body:not([data-theme="light"]) .highlight td.linenos .normal { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+body:not([data-theme="light"]) .highlight span.linenos { color: #aaaaaa; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+body:not([data-theme="light"]) .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+body:not([data-theme="light"]) .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+body:not([data-theme="light"]) .highlight .hll { background-color: #404040 }
+body:not([data-theme="light"]) .highlight { background: #202020; color: #D0D0D0 }
+body:not([data-theme="light"]) .highlight .c { color: #ABABAB; font-style: italic } /* Comment */
+body:not([data-theme="light"]) .highlight .err { color: #A61717; background-color: #E3D2D2 } /* Error */
+body:not([data-theme="light"]) .highlight .esc { color: #D0D0D0 } /* Escape */
+body:not([data-theme="light"]) .highlight .g { color: #D0D0D0 } /* Generic */
+body:not([data-theme="light"]) .highlight .k { color: #6EBF26; font-weight: bold } /* Keyword */
+body:not([data-theme="light"]) .highlight .l { color: #D0D0D0 } /* Literal */
+body:not([data-theme="light"]) .highlight .n { color: #D0D0D0 } /* Name */
+body:not([data-theme="light"]) .highlight .o { color: #D0D0D0 } /* Operator */
+body:not([data-theme="light"]) .highlight .x { color: #D0D0D0 } /* Other */
+body:not([data-theme="light"]) .highlight .p { color: #D0D0D0 } /* Punctuation */
+body:not([data-theme="light"]) .highlight .ch { color: #ABABAB; font-style: italic } /* Comment.Hashbang */
+body:not([data-theme="light"]) .highlight .cm { color: #ABABAB; font-style: italic } /* Comment.Multiline */
+body:not([data-theme="light"]) .highlight .cp { color: #FF3A3A; font-weight: bold } /* Comment.Preproc */
+body:not([data-theme="light"]) .highlight .cpf { color: #ABABAB; font-style: italic } /* Comment.PreprocFile */
+body:not([data-theme="light"]) .highlight .c1 { color: #ABABAB; font-style: italic } /* Comment.Single */
+body:not([data-theme="light"]) .highlight .cs { color: #E50808; font-weight: bold; background-color: #520000 } /* Comment.Special */
+body:not([data-theme="light"]) .highlight .gd { color: #FF3A3A } /* Generic.Deleted */
+body:not([data-theme="light"]) .highlight .ge { color: #D0D0D0; font-style: italic } /* Generic.Emph */
+body:not([data-theme="light"]) .highlight .ges { color: #D0D0D0; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+body:not([data-theme="light"]) .highlight .gr { color: #FF3A3A } /* Generic.Error */
+body:not([data-theme="light"]) .highlight .gh { color: #FFF; font-weight: bold } /* Generic.Heading */
+body:not([data-theme="light"]) .highlight .gi { color: #589819 } /* Generic.Inserted */
+body:not([data-theme="light"]) .highlight .go { color: #CCC } /* Generic.Output */
+body:not([data-theme="light"]) .highlight .gp { color: #AAA } /* Generic.Prompt */
+body:not([data-theme="light"]) .highlight .gs { color: #D0D0D0; font-weight: bold } /* Generic.Strong */
+body:not([data-theme="light"]) .highlight .gu { color: #FFF; text-decoration: underline } /* Generic.Subheading */
+body:not([data-theme="light"]) .highlight .gt { color: #FF3A3A } /* Generic.Traceback */
+body:not([data-theme="light"]) .highlight .kc { color: #6EBF26; font-weight: bold } /* Keyword.Constant */
+body:not([data-theme="light"]) .highlight .kd { color: #6EBF26; font-weight: bold } /* Keyword.Declaration */
+body:not([data-theme="light"]) .highlight .kn { color: #6EBF26; font-weight: bold } /* Keyword.Namespace */
+body:not([data-theme="light"]) .highlight .kp { color: #6EBF26 } /* Keyword.Pseudo */
+body:not([data-theme="light"]) .highlight .kr { color: #6EBF26; font-weight: bold } /* Keyword.Reserved */
+body:not([data-theme="light"]) .highlight .kt { color: #6EBF26; font-weight: bold } /* Keyword.Type */
+body:not([data-theme="light"]) .highlight .ld { color: #D0D0D0 } /* Literal.Date */
+body:not([data-theme="light"]) .highlight .m { color: #51B2FD } /* Literal.Number */
+body:not([data-theme="light"]) .highlight .s { color: #ED9D13 } /* Literal.String */
+body:not([data-theme="light"]) .highlight .na { color: #BBB } /* Name.Attribute */
+body:not([data-theme="light"]) .highlight .nb { color: #2FBCCD } /* Name.Builtin */
+body:not([data-theme="light"]) .highlight .nc { color: #71ADFF; text-decoration: underline } /* Name.Class */
+body:not([data-theme="light"]) .highlight .no { color: #40FFFF } /* Name.Constant */
+body:not([data-theme="light"]) .highlight .nd { color: #FFA500 } /* Name.Decorator */
+body:not([data-theme="light"]) .highlight .ni { color: #D0D0D0 } /* Name.Entity */
+body:not([data-theme="light"]) .highlight .ne { color: #BBB } /* Name.Exception */
+body:not([data-theme="light"]) .highlight .nf { color: #71ADFF } /* Name.Function */
+body:not([data-theme="light"]) .highlight .nl { color: #D0D0D0 } /* Name.Label */
+body:not([data-theme="light"]) .highlight .nn { color: #71ADFF; text-decoration: underline } /* Name.Namespace */
+body:not([data-theme="light"]) .highlight .nx { color: #D0D0D0 } /* Name.Other */
+body:not([data-theme="light"]) .highlight .py { color: #D0D0D0 } /* Name.Property */
+body:not([data-theme="light"]) .highlight .nt { color: #6EBF26; font-weight: bold } /* Name.Tag */
+body:not([data-theme="light"]) .highlight .nv { color: #40FFFF } /* Name.Variable */
+body:not([data-theme="light"]) .highlight .ow { color: #6EBF26; font-weight: bold } /* Operator.Word */
+body:not([data-theme="light"]) .highlight .pm { color: #D0D0D0 } /* Punctuation.Marker */
+body:not([data-theme="light"]) .highlight .w { color: #666 } /* Text.Whitespace */
+body:not([data-theme="light"]) .highlight .mb { color: #51B2FD } /* Literal.Number.Bin */
+body:not([data-theme="light"]) .highlight .mf { color: #51B2FD } /* Literal.Number.Float */
+body:not([data-theme="light"]) .highlight .mh { color: #51B2FD } /* Literal.Number.Hex */
+body:not([data-theme="light"]) .highlight .mi { color: #51B2FD } /* Literal.Number.Integer */
+body:not([data-theme="light"]) .highlight .mo { color: #51B2FD } /* Literal.Number.Oct */
+body:not([data-theme="light"]) .highlight .sa { color: #ED9D13 } /* Literal.String.Affix */
+body:not([data-theme="light"]) .highlight .sb { color: #ED9D13 } /* Literal.String.Backtick */
+body:not([data-theme="light"]) .highlight .sc { color: #ED9D13 } /* Literal.String.Char */
+body:not([data-theme="light"]) .highlight .dl { color: #ED9D13 } /* Literal.String.Delimiter */
+body:not([data-theme="light"]) .highlight .sd { color: #ED9D13 } /* Literal.String.Doc */
+body:not([data-theme="light"]) .highlight .s2 { color: #ED9D13 } /* Literal.String.Double */
+body:not([data-theme="light"]) .highlight .se { color: #ED9D13 } /* Literal.String.Escape */
+body:not([data-theme="light"]) .highlight .sh { color: #ED9D13 } /* Literal.String.Heredoc */
+body:not([data-theme="light"]) .highlight .si { color: #ED9D13 } /* Literal.String.Interpol */
+body:not([data-theme="light"]) .highlight .sx { color: #FFA500 } /* Literal.String.Other */
+body:not([data-theme="light"]) .highlight .sr { color: #ED9D13 } /* Literal.String.Regex */
+body:not([data-theme="light"]) .highlight .s1 { color: #ED9D13 } /* Literal.String.Single */
+body:not([data-theme="light"]) .highlight .ss { color: #ED9D13 } /* Literal.String.Symbol */
+body:not([data-theme="light"]) .highlight .bp { color: #2FBCCD } /* Name.Builtin.Pseudo */
+body:not([data-theme="light"]) .highlight .fm { color: #71ADFF } /* Name.Function.Magic */
+body:not([data-theme="light"]) .highlight .vc { color: #40FFFF } /* Name.Variable.Class */
+body:not([data-theme="light"]) .highlight .vg { color: #40FFFF } /* Name.Variable.Global */
+body:not([data-theme="light"]) .highlight .vi { color: #40FFFF } /* Name.Variable.Instance */
+body:not([data-theme="light"]) .highlight .vm { color: #40FFFF } /* Name.Variable.Magic */
+body:not([data-theme="light"]) .highlight .il { color: #51B2FD } /* Literal.Number.Integer.Long */
+}
+}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/scripts/furo-extensions.js b/SRV/docs/_build/html/_static/scripts/furo-extensions.js
new file mode 100644
index 0000000..e69de29
diff --git a/SRV/docs/_build/html/_static/scripts/furo.js b/SRV/docs/_build/html/_static/scripts/furo.js
new file mode 100644
index 0000000..87e1767
--- /dev/null
+++ b/SRV/docs/_build/html/_static/scripts/furo.js
@@ -0,0 +1,3 @@
+/*! For license information please see furo.js.LICENSE.txt */
+(()=>{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:"undefined"!=typeof window?window:this,o=function(){return function(t){"use strict";var e={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort(function(t,e){return o(t.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest("li");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n("gumshoeDeactivate",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest("li");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})}),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n("gumshoeActivate",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(function(){r(a),v.detect()})};return v.destroy=function(){d&&i(d,m),t.removeEventListener("scroll",h,!1),m.reflow&&t.removeEventListener("resize",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}}),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener("scroll",h,!1),m.reflow&&t.addEventListener("resize",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=n(856),e=n.n(t),o=null,r=null,c=document.documentElement.scrollTop;function s(){const t=localStorage.getItem("theme")||"auto";var e;"light"!==(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"auto"===t?"light":"light"==t?"dark":"auto":"auto"===t?"dark":"dark"==t?"light":"auto")&&"dark"!==e&&"auto"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e="auto"),document.body.dataset.theme=e,localStorage.setItem("theme",e),console.log(`Changed to ${e} mode.`)}function l(){!function(){const t=document.getElementsByClassName("theme-toggle");Array.from(t).forEach(t=>{t.addEventListener("click",s)})}(),function(){let t=0,e=!1;window.addEventListener("scroll",function(n){t=window.scrollY,e||(window.requestAnimationFrame(function(){var n;(function(t){t>0?r.classList.add("scrolled"):r.classList.remove("scrolled")})(n=t),function(t){t<64?document.documentElement.classList.remove("show-back-to-top"):tc&&document.documentElement.classList.remove("show-back-to-top"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(".scroll-current"))}(n),e=!1}),e=!0)}),window.scroll()}(),null!==o&&new(e())(".toc-tree a",{reflow:!0,recursive:!0,navClass:"scroll-current",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);const e=r.getBoundingClientRect();return e.top+e.height+2.5*t+1}})}document.addEventListener("DOMContentLoaded",function(){document.body.parentNode.classList.remove("no-js"),r=document.querySelector("header"),o=document.querySelector(".toc-scroll"),l()})})()})();
+//# sourceMappingURL=furo.js.map
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/scripts/furo.js.LICENSE.txt b/SRV/docs/_build/html/_static/scripts/furo.js.LICENSE.txt
new file mode 100644
index 0000000..1632189
--- /dev/null
+++ b/SRV/docs/_build/html/_static/scripts/furo.js.LICENSE.txt
@@ -0,0 +1,7 @@
+/*!
+ * gumshoejs v5.1.2 (patched by @pradyunsg)
+ * A simple, framework-agnostic scrollspy script.
+ * (c) 2019 Chris Ferdinandi
+ * MIT License
+ * http://github.com/cferdinandi/gumshoe
+ */
diff --git a/SRV/docs/_build/html/_static/scripts/furo.js.map b/SRV/docs/_build/html/_static/scripts/furo.js.map
new file mode 100644
index 0000000..3b316f3
--- /dev/null
+++ b/SRV/docs/_build/html/_static/scripts/furo.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"scripts/furo.js","mappings":";iCAAA,MAQWA,SAWS,IAAX,EAAAC,EACH,EAAAA,EACkB,oBAAXC,OACLA,OACAC,KAbO,EAAF,WACP,OAaJ,SAAUD,GACR,aAMA,IAAIE,EAAW,CAEbC,SAAU,SACVC,aAAc,SAGdC,QAAQ,EACRC,YAAa,SAGbC,OAAQ,EACRC,QAAQ,EAGRC,QAAQ,GA6BNC,EAAY,SAAUC,EAAMC,EAAMC,GAEpC,GAAKA,EAAOC,SAASL,OAArB,CAGA,IAAIM,EAAQ,IAAIC,YAAYL,EAAM,CAChCM,SAAS,EACTC,YAAY,EACZL,OAAQA,IAIVD,EAAKO,cAAcJ,EAVgB,CAWrC,EAOIK,EAAe,SAAUR,GAC3B,IAAIS,EAAW,EACf,GAAIT,EAAKU,aACP,KAAOV,GACLS,GAAYT,EAAKW,UACjBX,EAAOA,EAAKU,aAGhB,OAAOD,GAAY,EAAIA,EAAW,CACpC,EAMIG,EAAe,SAAUC,GACvBA,GACFA,EAASC,KAAK,SAAUC,EAAOC,GAG7B,OAFcR,EAAaO,EAAME,SACnBT,EAAaQ,EAAMC,UACF,EACxB,CACT,EAEJ,EAwCIC,EAAW,SAAUlB,EAAME,EAAUiB,GACvC,IAAIC,EAASpB,EAAKqB,wBACd1B,EAnCU,SAAUO,GAExB,MAA+B,mBAApBA,EAASP,OACX2B,WAAWpB,EAASP,UAItB2B,WAAWpB,EAASP,OAC7B,CA2Be4B,CAAUrB,GACvB,OAAIiB,EAEAK,SAASJ,EAAOD,OAAQ,KACvB/B,EAAOqC,aAAeC,SAASC,gBAAgBC,cAG7CJ,SAASJ,EAAOS,IAAK,KAAOlC,CACrC,EAMImC,EAAa,WACf,OACEC,KAAKC,KAAK5C,EAAOqC,YAAcrC,EAAO6C,cAnCjCF,KAAKG,IACVR,SAASS,KAAKC,aACdV,SAASC,gBAAgBS,aACzBV,SAASS,KAAKE,aACdX,SAASC,gBAAgBU,aACzBX,SAASS,KAAKP,aACdF,SAASC,gBAAgBC,aAkC7B,EAmBIU,EAAY,SAAUzB,EAAUX,GAClC,IAAIqC,EAAO1B,EAASA,EAAS2B,OAAS,GACtC,GAbgB,SAAUC,EAAMvC,GAChC,SAAI4B,MAAgBZ,EAASuB,EAAKxB,QAASf,GAAU,GAEvD,CAUMwC,CAAYH,EAAMrC,GAAW,OAAOqC,EACxC,IAAK,IAAII,EAAI9B,EAAS2B,OAAS,EAAGG,GAAK,EAAGA,IACxC,GAAIzB,EAASL,EAAS8B,GAAG1B,QAASf,GAAW,OAAOW,EAAS8B,EAEjE,EAOIC,EAAmB,SAAUC,EAAK3C,GAEpC,GAAKA,EAAST,QAAWoD,EAAIC,WAA7B,CAGA,IAAIC,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASR,aAG7BkD,EAAiBG,EAAI7C,GAV0B,CAWjD,EAOIiD,EAAa,SAAUC,EAAOlD,GAEhC,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASX,UAC7B6D,EAAMnC,QAAQgC,UAAUC,OAAOhD,EAASV,cAGxCoD,EAAiBG,EAAI7C,GAGrBJ,EAAU,oBAAqBiD,EAAI,CACjCM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,EAOIoD,EAAiB,SAAUT,EAAK3C,GAElC,GAAKA,EAAST,OAAd,CAGA,IAAIsD,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASR,aAG1B4D,EAAeP,EAAI7C,GAVS,CAW9B,EA6LA,OA1JkB,SAAUsD,EAAUC,GAKpC,IACIC,EAAU7C,EAAU8C,EAASC,EAAS1D,EADtC2D,EAAa,CAUjBA,MAAmB,WAEjBH,EAAWhC,SAASoC,iBAAiBN,GAGrC3C,EAAW,GAGXkD,MAAMC,UAAUC,QAAQC,KAAKR,EAAU,SAAUjB,GAE/C,IAAIxB,EAAUS,SAASyC,eACrBC,mBAAmB3B,EAAK4B,KAAKC,OAAO,KAEjCrD,GAGLJ,EAAS0D,KAAK,CACZ1B,IAAKJ,EACLxB,QAASA,GAEb,GAGAL,EAAaC,EACf,EAKAgD,OAAoB,WAElB,IAAIW,EAASlC,EAAUzB,EAAUX,GAG5BsE,EASDb,GAAWa,EAAOvD,UAAY0C,EAAQ1C,UAG1CkC,EAAWQ,EAASzD,GAzFT,SAAUkD,EAAOlD,GAE9B,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASX,UAC1B6D,EAAMnC,QAAQgC,UAAUM,IAAIrD,EAASV,cAGrC8D,EAAeP,EAAI7C,GAGnBJ,EAAU,kBAAmBiD,EAAI,CAC/BM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,CAqEIuE,CAASD,EAAQtE,GAGjByD,EAAUa,GAfJb,IACFR,EAAWQ,EAASzD,GACpByD,EAAU,KAchB,GAMIe,EAAgB,SAAUvE,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,sBAAsBf,EAAWgB,OACpD,EAMIC,EAAgB,SAAU3E,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,sBAAsB,WACrChE,EAAaC,GACbgD,EAAWgB,QACb,EACF,EAkDA,OA7CAhB,EAAWkB,QAAU,WAEfpB,GACFR,EAAWQ,EAASzD,GAItBd,EAAO4F,oBAAoB,SAAUN,GAAe,GAChDxE,EAASN,QACXR,EAAO4F,oBAAoB,SAAUF,GAAe,GAItDjE,EAAW,KACX6C,EAAW,KACXC,EAAU,KACVC,EAAU,KACV1D,EAAW,IACb,EAOEA,EA3XS,WACX,IAAI+E,EAAS,CAAC,EAOd,OANAlB,MAAMC,UAAUC,QAAQC,KAAKgB,UAAW,SAAUC,GAChD,IAAK,IAAIC,KAAOD,EAAK,CACnB,IAAKA,EAAIE,eAAeD,GAAM,OAC9BH,EAAOG,GAAOD,EAAIC,EACpB,CACF,GACOH,CACT,CAkXeK,CAAOhG,EAAUmE,GAAW,CAAC,GAGxCI,EAAW0B,QAGX1B,EAAWgB,SAGXzF,EAAOoG,iBAAiB,SAAUd,GAAe,GAC7CxE,EAASN,QACXR,EAAOoG,iBAAiB,SAAUV,GAAe,GAS9CjB,CACT,CAOF,CArcW4B,CAAQvG,EAChB,UAFM,SAEN,oB,GCXDwG,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CAGjDG,QAAS,CAAC,GAOX,OAHAE,EAAoBL,GAAU1B,KAAK8B,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAGpEK,EAAOD,OACf,CCrBAJ,EAAoBO,EAAKF,IACxB,IAAIG,EAASH,GAAUA,EAAOI,WAC7B,IAAOJ,EAAiB,QACxB,IAAM,EAEP,OADAL,EAAoBU,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,GCLRR,EAAoBU,EAAI,CAACN,EAASQ,KACjC,IAAI,IAAInB,KAAOmB,EACXZ,EAAoBa,EAAED,EAAYnB,KAASO,EAAoBa,EAAET,EAASX,IAC5EqB,OAAOC,eAAeX,EAASX,EAAK,CAAEuB,YAAY,EAAMC,IAAKL,EAAWnB,MCJ3EO,EAAoBxG,EAAI,WACvB,GAA0B,iBAAf0H,WAAyB,OAAOA,WAC3C,IACC,OAAOxH,MAAQ,IAAIyH,SAAS,cAAb,EAChB,CAAE,MAAOC,GACR,GAAsB,iBAAX3H,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxBuG,EAAoBa,EAAI,CAACrB,EAAK6B,IAAUP,OAAOzC,UAAUqB,eAAenB,KAAKiB,EAAK6B,G,yCCK9EC,EAAY,KACZC,EAAS,KACTC,EAAgBzF,SAASC,gBAAgByF,UA4E7C,SAASC,IACP,MAAMC,EAAeC,aAAaC,QAAQ,UAAY,OAZxD,IAAkBC,EACH,WADGA,EAaIrI,OAAOsI,WAAW,gCAAgCC,QAI/C,SAAjBL,EACO,QACgB,SAAhBA,EACA,OAEA,OAIU,SAAjBA,EACO,OACgB,QAAhBA,EACA,QAEA,SA9BoB,SAATG,GAA4B,SAATA,IACzCG,QAAQC,MAAM,2BAA2BJ,yBACzCA,EAAO,QAGT/F,SAASS,KAAK2F,QAAQC,MAAQN,EAC9BF,aAAaS,QAAQ,QAASP,GAC9BG,QAAQK,IAAI,cAAcR,UA0B5B,CAmDA,SAASlC,KART,WAEE,MAAM2C,EAAUxG,SAASyG,uBAAuB,gBAChDpE,MAAMqE,KAAKF,GAASjE,QAASoE,IAC3BA,EAAI7C,iBAAiB,QAAS6B,IAElC,CAGEiB,GA/CF,WAEE,IAAIC,EAA6B,EAC7BC,GAAU,EAEdpJ,OAAOoG,iBAAiB,SAAU,SAAUuB,GAC1CwB,EAA6BnJ,OAAOqJ,QAE/BD,IACHpJ,OAAOwF,sBAAsB,WAzDnC,IAAuB8D,GArDvB,SAAgCA,GAC1BA,EAAY,EACdxB,EAAOjE,UAAUM,IAAI,YAErB2D,EAAOjE,UAAUC,OAAO,WAE5B,EAgDEyF,CADqBD,EA0DDH,GAvGtB,SAAmCG,GAC7BA,EAXmB,GAYrBhH,SAASC,gBAAgBsB,UAAUC,OAAO,oBAEtCwF,EAAYvB,EACdzF,SAASC,gBAAgBsB,UAAUM,IAAI,oBAC9BmF,EAAYvB,GACrBzF,SAASC,gBAAgBsB,UAAUC,OAAO,oBAG9CiE,EAAgBuB,CAClB,CAoCEE,CAA0BF,GAlC5B,SAA6BA,GACT,OAAdzB,IAKa,GAAbyB,EACFzB,EAAU4B,SAAS,EAAG,GAGtB9G,KAAKC,KAAK0G,IACV3G,KAAK+G,MAAMpH,SAASC,gBAAgBS,aAAehD,OAAOqC,aAE1DwF,EAAU4B,SAAS,EAAG5B,EAAU7E,cAGhBV,SAASqH,cAAc,mBAc3C,CAKEC,CAAoBN,GAwDdF,GAAU,CACZ,GAEAA,GAAU,EAEd,GACApJ,OAAO6J,QACT,CA8BEC,GA3BkB,OAAdjC,GAKJ,IAAI,IAAJ,CAAY,cAAe,CACzBrH,QAAQ,EACRuJ,WAAW,EACX5J,SAAU,iBACVI,OAAQ,KACN,IAAIyJ,EAAM9H,WAAW+H,iBAAiB3H,SAASC,iBAAiB2H,UAChE,MAAMC,EAAarC,EAAO7F,wBAC1B,OAAOkI,EAAW1H,IAAM0H,EAAWC,OAAS,IAAMJ,EAAM,IAiB9D,CAcA1H,SAAS8D,iBAAiB,mBAT1B,WACE9D,SAASS,KAAKW,WAAWG,UAAUC,OAAO,SAE1CgE,EAASxF,SAASqH,cAAc,UAChC9B,EAAYvF,SAASqH,cAAc,eAEnCxD,GACF,E","sources":["webpack:///./src/furo/assets/scripts/gumshoe-patched.js","webpack:///webpack/bootstrap","webpack:///webpack/runtime/compat get default export","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/global","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./src/furo/assets/scripts/furo.js"],"sourcesContent":["/*!\n * gumshoejs v5.1.2 (patched by @pradyunsg)\n * A simple, framework-agnostic scrollspy script.\n * (c) 2019 Chris Ferdinandi\n * MIT License\n * http://github.com/cferdinandi/gumshoe\n */\n\n(function (root, factory) {\n if (typeof define === \"function\" && define.amd) {\n define([], function () {\n return factory(root);\n });\n } else if (typeof exports === \"object\") {\n module.exports = factory(root);\n } else {\n root.Gumshoe = factory(root);\n }\n})(\n typeof global !== \"undefined\"\n ? global\n : typeof window !== \"undefined\"\n ? window\n : this,\n function (window) {\n \"use strict\";\n\n //\n // Defaults\n //\n\n var defaults = {\n // Active classes\n navClass: \"active\",\n contentClass: \"active\",\n\n // Nested navigation\n nested: false,\n nestedClass: \"active\",\n\n // Offset & reflow\n offset: 0,\n reflow: false,\n\n // Event support\n events: true,\n };\n\n //\n // Methods\n //\n\n /**\n * Merge two or more objects together.\n * @param {Object} objects The objects to merge together\n * @returns {Object} Merged values of defaults and options\n */\n var extend = function () {\n var merged = {};\n Array.prototype.forEach.call(arguments, function (obj) {\n for (var key in obj) {\n if (!obj.hasOwnProperty(key)) return;\n merged[key] = obj[key];\n }\n });\n return merged;\n };\n\n /**\n * Emit a custom event\n * @param {String} type The event type\n * @param {Node} elem The element to attach the event to\n * @param {Object} detail Any details to pass along with the event\n */\n var emitEvent = function (type, elem, detail) {\n // Make sure events are enabled\n if (!detail.settings.events) return;\n\n // Create a new event\n var event = new CustomEvent(type, {\n bubbles: true,\n cancelable: true,\n detail: detail,\n });\n\n // Dispatch the event\n elem.dispatchEvent(event);\n };\n\n /**\n * Get an element's distance from the top of the Document.\n * @param {Node} elem The element\n * @return {Number} Distance from the top in pixels\n */\n var getOffsetTop = function (elem) {\n var location = 0;\n if (elem.offsetParent) {\n while (elem) {\n location += elem.offsetTop;\n elem = elem.offsetParent;\n }\n }\n return location >= 0 ? location : 0;\n };\n\n /**\n * Sort content from first to last in the DOM\n * @param {Array} contents The content areas\n */\n var sortContents = function (contents) {\n if (contents) {\n contents.sort(function (item1, item2) {\n var offset1 = getOffsetTop(item1.content);\n var offset2 = getOffsetTop(item2.content);\n if (offset1 < offset2) return -1;\n return 1;\n });\n }\n };\n\n /**\n * Get the offset to use for calculating position\n * @param {Object} settings The settings for this instantiation\n * @return {Float} The number of pixels to offset the calculations\n */\n var getOffset = function (settings) {\n // if the offset is a function run it\n if (typeof settings.offset === \"function\") {\n return parseFloat(settings.offset());\n }\n\n // Otherwise, return it as-is\n return parseFloat(settings.offset);\n };\n\n /**\n * Get the document element's height\n * @private\n * @returns {Number}\n */\n var getDocumentHeight = function () {\n return Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight,\n document.body.clientHeight,\n document.documentElement.clientHeight,\n );\n };\n\n /**\n * Determine if an element is in view\n * @param {Node} elem The element\n * @param {Object} settings The settings for this instantiation\n * @param {Boolean} bottom If true, check if element is above bottom of viewport instead\n * @return {Boolean} Returns true if element is in the viewport\n */\n var isInView = function (elem, settings, bottom) {\n var bounds = elem.getBoundingClientRect();\n var offset = getOffset(settings);\n if (bottom) {\n return (\n parseInt(bounds.bottom, 10) <\n (window.innerHeight || document.documentElement.clientHeight)\n );\n }\n return parseInt(bounds.top, 10) <= offset;\n };\n\n /**\n * Check if at the bottom of the viewport\n * @return {Boolean} If true, page is at the bottom of the viewport\n */\n var isAtBottom = function () {\n if (\n Math.ceil(window.innerHeight + window.pageYOffset) >=\n getDocumentHeight()\n )\n return true;\n return false;\n };\n\n /**\n * Check if the last item should be used (even if not at the top of the page)\n * @param {Object} item The last item\n * @param {Object} settings The settings for this instantiation\n * @return {Boolean} If true, use the last item\n */\n var useLastItem = function (item, settings) {\n if (isAtBottom() && isInView(item.content, settings, true)) return true;\n return false;\n };\n\n /**\n * Get the active content\n * @param {Array} contents The content areas\n * @param {Object} settings The settings for this instantiation\n * @return {Object} The content area and matching navigation link\n */\n var getActive = function (contents, settings) {\n var last = contents[contents.length - 1];\n if (useLastItem(last, settings)) return last;\n for (var i = contents.length - 1; i >= 0; i--) {\n if (isInView(contents[i].content, settings)) return contents[i];\n }\n };\n\n /**\n * Deactivate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var deactivateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested || !nav.parentNode) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Remove the active class\n li.classList.remove(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n deactivateNested(li, settings);\n };\n\n /**\n * Deactivate a nav and content area\n * @param {Object} items The nav item and content to deactivate\n * @param {Object} settings The settings for this instantiation\n */\n var deactivate = function (items, settings) {\n // Make sure there are items to deactivate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Remove the active class from the nav and content\n li.classList.remove(settings.navClass);\n items.content.classList.remove(settings.contentClass);\n\n // Deactivate any parent navs in a nested navigation\n deactivateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeDeactivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Activate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var activateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Add the active class\n li.classList.add(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n activateNested(li, settings);\n };\n\n /**\n * Activate a nav and content area\n * @param {Object} items The nav item and content to activate\n * @param {Object} settings The settings for this instantiation\n */\n var activate = function (items, settings) {\n // Make sure there are items to activate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Add the active class to the nav and content\n li.classList.add(settings.navClass);\n items.content.classList.add(settings.contentClass);\n\n // Activate any parent navs in a nested navigation\n activateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeActivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Create the Constructor object\n * @param {String} selector The selector to use for navigation items\n * @param {Object} options User options and settings\n */\n var Constructor = function (selector, options) {\n //\n // Variables\n //\n\n var publicAPIs = {};\n var navItems, contents, current, timeout, settings;\n\n //\n // Methods\n //\n\n /**\n * Set variables from DOM elements\n */\n publicAPIs.setup = function () {\n // Get all nav items\n navItems = document.querySelectorAll(selector);\n\n // Create contents array\n contents = [];\n\n // Loop through each item, get it's matching content, and push to the array\n Array.prototype.forEach.call(navItems, function (item) {\n // Get the content for the nav item\n var content = document.getElementById(\n decodeURIComponent(item.hash.substr(1)),\n );\n if (!content) return;\n\n // Push to the contents array\n contents.push({\n nav: item,\n content: content,\n });\n });\n\n // Sort contents by the order they appear in the DOM\n sortContents(contents);\n };\n\n /**\n * Detect which content is currently active\n */\n publicAPIs.detect = function () {\n // Get the active content\n var active = getActive(contents, settings);\n\n // if there's no active content, deactivate and bail\n if (!active) {\n if (current) {\n deactivate(current, settings);\n current = null;\n }\n return;\n }\n\n // If the active content is the one currently active, do nothing\n if (current && active.content === current.content) return;\n\n // Deactivate the current content and activate the new content\n deactivate(current, settings);\n activate(active, settings);\n\n // Update the currently active content\n current = active;\n };\n\n /**\n * Detect the active content on scroll\n * Debounced for performance\n */\n var scrollHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(publicAPIs.detect);\n };\n\n /**\n * Update content sorting on resize\n * Debounced for performance\n */\n var resizeHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(function () {\n sortContents(contents);\n publicAPIs.detect();\n });\n };\n\n /**\n * Destroy the current instantiation\n */\n publicAPIs.destroy = function () {\n // Undo DOM changes\n if (current) {\n deactivate(current, settings);\n }\n\n // Remove event listeners\n window.removeEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.removeEventListener(\"resize\", resizeHandler, false);\n }\n\n // Reset variables\n contents = null;\n navItems = null;\n current = null;\n timeout = null;\n settings = null;\n };\n\n /**\n * Initialize the current instantiation\n */\n var init = function () {\n // Merge user options into defaults\n settings = extend(defaults, options || {});\n\n // Setup variables based on the current DOM\n publicAPIs.setup();\n\n // Find the currently active content\n publicAPIs.detect();\n\n // Setup event listeners\n window.addEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.addEventListener(\"resize\", resizeHandler, false);\n }\n };\n\n //\n // Initialize and return the public APIs\n //\n\n init();\n return publicAPIs;\n };\n\n //\n // Return the Constructor\n //\n\n return Constructor;\n },\n);\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","import Gumshoe from \"./gumshoe-patched.js\";\n\n////////////////////////////////////////////////////////////////////////////////\n// Scroll Handling\n////////////////////////////////////////////////////////////////////////////////\nvar tocScroll = null;\nvar header = null;\nvar lastScrollTop = document.documentElement.scrollTop;\nconst GO_TO_TOP_OFFSET = 64;\n\nfunction scrollHandlerForHeader(positionY) {\n if (positionY > 0) {\n header.classList.add(\"scrolled\");\n } else {\n header.classList.remove(\"scrolled\");\n }\n}\n\nfunction scrollHandlerForBackToTop(positionY) {\n if (positionY < GO_TO_TOP_OFFSET) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n } else {\n if (positionY < lastScrollTop) {\n document.documentElement.classList.add(\"show-back-to-top\");\n } else if (positionY > lastScrollTop) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n }\n }\n lastScrollTop = positionY;\n}\n\nfunction scrollHandlerForTOC(positionY) {\n if (tocScroll === null) {\n return;\n }\n\n // top of page.\n if (positionY == 0) {\n tocScroll.scrollTo(0, 0);\n } else if (\n // bottom of page.\n Math.ceil(positionY) >=\n Math.floor(document.documentElement.scrollHeight - window.innerHeight)\n ) {\n tocScroll.scrollTo(0, tocScroll.scrollHeight);\n } else {\n // somewhere in the middle.\n const current = document.querySelector(\".scroll-current\");\n if (current == null) {\n return;\n }\n\n // https://github.com/pypa/pip/issues/9159 This breaks scroll behaviours.\n // // scroll the currently \"active\" heading in toc, into view.\n // const rect = current.getBoundingClientRect();\n // if (0 > rect.top) {\n // current.scrollIntoView(true); // the argument is \"alignTop\"\n // } else if (rect.bottom > window.innerHeight) {\n // current.scrollIntoView(false);\n // }\n }\n}\n\nfunction scrollHandler(positionY) {\n scrollHandlerForHeader(positionY);\n scrollHandlerForBackToTop(positionY);\n scrollHandlerForTOC(positionY);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Theme Toggle\n////////////////////////////////////////////////////////////////////////////////\nfunction setTheme(mode) {\n if (mode !== \"light\" && mode !== \"dark\" && mode !== \"auto\") {\n console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);\n mode = \"auto\";\n }\n\n document.body.dataset.theme = mode;\n localStorage.setItem(\"theme\", mode);\n console.log(`Changed to ${mode} mode.`);\n}\n\nfunction cycleThemeOnce() {\n const currentTheme = localStorage.getItem(\"theme\") || \"auto\";\n const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n if (prefersDark) {\n // Auto (dark) -> Light -> Dark\n if (currentTheme === \"auto\") {\n setTheme(\"light\");\n } else if (currentTheme == \"light\") {\n setTheme(\"dark\");\n } else {\n setTheme(\"auto\");\n }\n } else {\n // Auto (light) -> Dark -> Light\n if (currentTheme === \"auto\") {\n setTheme(\"dark\");\n } else if (currentTheme == \"dark\") {\n setTheme(\"light\");\n } else {\n setTheme(\"auto\");\n }\n }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Setup\n////////////////////////////////////////////////////////////////////////////////\nfunction setupScrollHandler() {\n // Taken from https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event\n let last_known_scroll_position = 0;\n let ticking = false;\n\n window.addEventListener(\"scroll\", function (e) {\n last_known_scroll_position = window.scrollY;\n\n if (!ticking) {\n window.requestAnimationFrame(function () {\n scrollHandler(last_known_scroll_position);\n ticking = false;\n });\n\n ticking = true;\n }\n });\n window.scroll();\n}\n\nfunction setupScrollSpy() {\n if (tocScroll === null) {\n return;\n }\n\n // Scrollspy -- highlight table on contents, based on scroll\n new Gumshoe(\".toc-tree a\", {\n reflow: true,\n recursive: true,\n navClass: \"scroll-current\",\n offset: () => {\n let rem = parseFloat(getComputedStyle(document.documentElement).fontSize);\n const headerRect = header.getBoundingClientRect();\n return headerRect.top + headerRect.height + 2.5 * rem + 1;\n },\n });\n}\n\nfunction setupTheme() {\n // Attach event handlers for toggling themes\n const buttons = document.getElementsByClassName(\"theme-toggle\");\n Array.from(buttons).forEach((btn) => {\n btn.addEventListener(\"click\", cycleThemeOnce);\n });\n}\n\nfunction setup() {\n setupTheme();\n setupScrollHandler();\n setupScrollSpy();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Main entrypoint\n////////////////////////////////////////////////////////////////////////////////\nfunction main() {\n document.body.parentNode.classList.remove(\"no-js\");\n\n header = document.querySelector(\"header\");\n tocScroll = document.querySelector(\".toc-scroll\");\n\n setup();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", main);\n"],"names":["root","g","window","this","defaults","navClass","contentClass","nested","nestedClass","offset","reflow","events","emitEvent","type","elem","detail","settings","event","CustomEvent","bubbles","cancelable","dispatchEvent","getOffsetTop","location","offsetParent","offsetTop","sortContents","contents","sort","item1","item2","content","isInView","bottom","bounds","getBoundingClientRect","parseFloat","getOffset","parseInt","innerHeight","document","documentElement","clientHeight","top","isAtBottom","Math","ceil","pageYOffset","max","body","scrollHeight","offsetHeight","getActive","last","length","item","useLastItem","i","deactivateNested","nav","parentNode","li","closest","classList","remove","deactivate","items","link","activateNested","add","selector","options","navItems","current","timeout","publicAPIs","querySelectorAll","Array","prototype","forEach","call","getElementById","decodeURIComponent","hash","substr","push","active","activate","scrollHandler","cancelAnimationFrame","requestAnimationFrame","detect","resizeHandler","destroy","removeEventListener","merged","arguments","obj","key","hasOwnProperty","extend","setup","addEventListener","factory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","n","getter","__esModule","d","a","definition","o","Object","defineProperty","enumerable","get","globalThis","Function","e","prop","tocScroll","header","lastScrollTop","scrollTop","cycleThemeOnce","currentTheme","localStorage","getItem","mode","matchMedia","matches","console","error","dataset","theme","setItem","log","buttons","getElementsByClassName","from","btn","setupTheme","last_known_scroll_position","ticking","scrollY","positionY","scrollHandlerForHeader","scrollHandlerForBackToTop","scrollTo","floor","querySelector","scrollHandlerForTOC","scroll","setupScrollHandler","recursive","rem","getComputedStyle","fontSize","headerRect","height"],"sourceRoot":""}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/searchtools.js b/SRV/docs/_build/html/_static/searchtools.js
new file mode 100644
index 0000000..91f4be5
--- /dev/null
+++ b/SRV/docs/_build/html/_static/searchtools.js
@@ -0,0 +1,635 @@
+/*
+ * Sphinx JavaScript utilities for the full-text search.
+ */
+"use strict";
+
+/**
+ * Simple result scoring code.
+ */
+if (typeof Scorer === "undefined") {
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [docname, title, anchor, descr, score, filename]
+ // and returns the new score.
+ /*
+ score: result => {
+ const [docname, title, anchor, descr, score, filename, kind] = result
+ return score
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {
+ 0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5, // used to be unimportantResults
+ },
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2,
+ };
+}
+
+// Global search result kind enum, used by themes to style search results.
+class SearchResultKind {
+ static get index() { return "index"; }
+ static get object() { return "object"; }
+ static get text() { return "text"; }
+ static get title() { return "title"; }
+}
+
+const _removeChildren = (element) => {
+ while (element && element.lastChild) element.removeChild(element.lastChild);
+};
+
+/**
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+ */
+const _escapeRegExp = (string) =>
+ string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+
+const _displayItem = (item, searchTerms, highlightTerms) => {
+ const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
+ const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
+ const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
+ const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
+ const contentRoot = document.documentElement.dataset.content_root;
+
+ const [docName, title, anchor, descr, score, _filename, kind] = item;
+
+ let listItem = document.createElement("li");
+ // Add a class representing the item's type:
+ // can be used by a theme's CSS selector for styling
+ // See SearchResultKind for the class names.
+ listItem.classList.add(`kind-${kind}`);
+ let requestUrl;
+ let linkUrl;
+ if (docBuilder === "dirhtml") {
+ // dirhtml builder
+ let dirname = docName + "/";
+ if (dirname.match(/\/index\/$/))
+ dirname = dirname.substring(0, dirname.length - 6);
+ else if (dirname === "index/") dirname = "";
+ requestUrl = contentRoot + dirname;
+ linkUrl = requestUrl;
+ } else {
+ // normal html builders
+ requestUrl = contentRoot + docName + docFileSuffix;
+ linkUrl = docName + docLinkSuffix;
+ }
+ let linkEl = listItem.appendChild(document.createElement("a"));
+ linkEl.href = linkUrl + anchor;
+ linkEl.dataset.score = score;
+ linkEl.innerHTML = title;
+ if (descr) {
+ listItem.appendChild(document.createElement("span")).innerHTML =
+ " (" + descr + ")";
+ // highlight search terms in the description
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ }
+ else if (showSearchSummary)
+ fetch(requestUrl)
+ .then((responseData) => responseData.text())
+ .then((data) => {
+ if (data)
+ listItem.appendChild(
+ Search.makeSearchSummary(data, searchTerms, anchor)
+ );
+ // highlight search terms in the summary
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ });
+ Search.output.appendChild(listItem);
+};
+const _finishSearch = (resultCount) => {
+ Search.stopPulse();
+ Search.title.innerText = _("Search Results");
+ if (!resultCount)
+ Search.status.innerText = Documentation.gettext(
+ "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
+ );
+ else
+ Search.status.innerText = Documentation.ngettext(
+ "Search finished, found one page matching the search query.",
+ "Search finished, found ${resultCount} pages matching the search query.",
+ resultCount,
+ ).replace('${resultCount}', resultCount);
+};
+const _displayNextItem = (
+ results,
+ resultCount,
+ searchTerms,
+ highlightTerms,
+) => {
+ // results left, load the summary and display it
+ // this is intended to be dynamic (don't sub resultsCount)
+ if (results.length) {
+ _displayItem(results.pop(), searchTerms, highlightTerms);
+ setTimeout(
+ () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
+ 5
+ );
+ }
+ // search finished, update title and status message
+ else _finishSearch(resultCount);
+};
+// Helper function used by query() to order search results.
+// Each input is an array of [docname, title, anchor, descr, score, filename, kind].
+// Order the results by score (in opposite order of appearance, since the
+// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
+const _orderResultsByScoreThenName = (a, b) => {
+ const leftScore = a[4];
+ const rightScore = b[4];
+ if (leftScore === rightScore) {
+ // same score: sort alphabetically
+ const leftTitle = a[1].toLowerCase();
+ const rightTitle = b[1].toLowerCase();
+ if (leftTitle === rightTitle) return 0;
+ return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
+ }
+ return leftScore > rightScore ? 1 : -1;
+};
+
+/**
+ * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
+ * custom function per language.
+ *
+ * The regular expression works by splitting the string on consecutive characters
+ * that are not Unicode letters, numbers, underscores, or emoji characters.
+ * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
+ */
+if (typeof splitQuery === "undefined") {
+ var splitQuery = (query) => query
+ .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
+ .filter(term => term) // remove remaining empty strings
+}
+
+/**
+ * Search Module
+ */
+const Search = {
+ _index: null,
+ _queued_query: null,
+ _pulse_status: -1,
+
+ htmlToText: (htmlString, anchor) => {
+ const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
+ for (const removalQuery of [".headerlink", "script", "style"]) {
+ htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
+ }
+ if (anchor) {
+ const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
+ if (anchorContent) return anchorContent.textContent;
+
+ console.warn(
+ `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
+ );
+ }
+
+ // if anchor not specified or not found, fall back to main content
+ const docContent = htmlElement.querySelector('[role="main"]');
+ if (docContent) return docContent.textContent;
+
+ console.warn(
+ "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
+ );
+ return "";
+ },
+
+ init: () => {
+ const query = new URLSearchParams(window.location.search).get("q");
+ document
+ .querySelectorAll('input[name="q"]')
+ .forEach((el) => (el.value = query));
+ if (query) Search.performSearch(query);
+ },
+
+ loadIndex: (url) =>
+ (document.body.appendChild(document.createElement("script")).src = url),
+
+ setIndex: (index) => {
+ Search._index = index;
+ if (Search._queued_query !== null) {
+ const query = Search._queued_query;
+ Search._queued_query = null;
+ Search.query(query);
+ }
+ },
+
+ hasIndex: () => Search._index !== null,
+
+ deferQuery: (query) => (Search._queued_query = query),
+
+ stopPulse: () => (Search._pulse_status = -1),
+
+ startPulse: () => {
+ if (Search._pulse_status >= 0) return;
+
+ const pulse = () => {
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ Search.dots.innerText = ".".repeat(Search._pulse_status);
+ if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
+ };
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch: (query) => {
+ // create the required interface elements
+ const searchText = document.createElement("h2");
+ searchText.textContent = _("Searching");
+ const searchSummary = document.createElement("p");
+ searchSummary.classList.add("search-summary");
+ searchSummary.innerText = "";
+ const searchList = document.createElement("ul");
+ searchList.setAttribute("role", "list");
+ searchList.classList.add("search");
+
+ const out = document.getElementById("search-results");
+ Search.title = out.appendChild(searchText);
+ Search.dots = Search.title.appendChild(document.createElement("span"));
+ Search.status = out.appendChild(searchSummary);
+ Search.output = out.appendChild(searchList);
+
+ const searchProgress = document.getElementById("search-progress");
+ // Some themes don't use the search progress node
+ if (searchProgress) {
+ searchProgress.innerText = _("Preparing search...");
+ }
+ Search.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (Search.hasIndex()) Search.query(query);
+ else Search.deferQuery(query);
+ },
+
+ _parseQuery: (query) => {
+ // stem the search terms and add them to the correct list
+ const stemmer = new Stemmer();
+ const searchTerms = new Set();
+ const excludedTerms = new Set();
+ const highlightTerms = new Set();
+ const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
+ splitQuery(query.trim()).forEach((queryTerm) => {
+ const queryTermLower = queryTerm.toLowerCase();
+
+ // maybe skip this "word"
+ // stopwords array is from language_data.js
+ if (
+ stopwords.indexOf(queryTermLower) !== -1 ||
+ queryTerm.match(/^\d+$/)
+ )
+ return;
+
+ // stem the word
+ let word = stemmer.stemWord(queryTermLower);
+ // select the correct list
+ if (word[0] === "-") excludedTerms.add(word.substr(1));
+ else {
+ searchTerms.add(word);
+ highlightTerms.add(queryTermLower);
+ }
+ });
+
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
+ // console.debug("SEARCH: searching for:");
+ // console.info("required: ", [...searchTerms]);
+ // console.info("excluded: ", [...excludedTerms]);
+
+ return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+ const allTitles = Search._index.alltitles;
+ const indexEntries = Search._index.indexentries;
+
+ // Collect multiple result groups to be sorted separately and then ordered.
+ // Each is an array of [docname, title, anchor, descr, score, filename, kind].
+ const normalResults = [];
+ const nonMainIndexResults = [];
+
+ _removeChildren(document.getElementById("search-progress"));
+
+ const queryLower = query.toLowerCase().trim();
+ for (const [title, foundTitles] of Object.entries(allTitles)) {
+ if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
+ for (const [file, id] of foundTitles) {
+ const score = Math.round(Scorer.title * queryLower.length / title.length);
+ const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
+ normalResults.push([
+ docNames[file],
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
+ id !== null ? "#" + id : "",
+ null,
+ score + boost,
+ filenames[file],
+ SearchResultKind.title,
+ ]);
+ }
+ }
+ }
+
+ // search for explicit entries in index directives
+ for (const [entry, foundEntries] of Object.entries(indexEntries)) {
+ if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
+ for (const [file, id, isMain] of foundEntries) {
+ const score = Math.round(100 * queryLower.length / entry.length);
+ const result = [
+ docNames[file],
+ titles[file],
+ id ? "#" + id : "",
+ null,
+ score,
+ filenames[file],
+ SearchResultKind.index,
+ ];
+ if (isMain) {
+ normalResults.push(result);
+ } else {
+ nonMainIndexResults.push(result);
+ }
+ }
+ }
+ }
+
+ // lookup as object
+ objectTerms.forEach((term) =>
+ normalResults.push(...Search.performObjectSearch(term, objectTerms))
+ );
+
+ // lookup as search terms in fulltext
+ normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ normalResults.forEach((item) => (item[4] = Scorer.score(item)));
+ nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
+ }
+
+ // Sort each group of results by score and then alphabetically by name.
+ normalResults.sort(_orderResultsByScoreThenName);
+ nonMainIndexResults.sort(_orderResultsByScoreThenName);
+
+ // Combine the result groups in (reverse) order.
+ // Non-main index entries are typically arbitrary cross-references,
+ // so display them after other results.
+ let results = [...nonMainIndexResults, ...normalResults];
+
+ // remove duplicate search results
+ // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
+ let seen = new Set();
+ results = results.reverse().reduce((acc, result) => {
+ let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
+ if (!seen.has(resultStr)) {
+ acc.push(result);
+ seen.add(resultStr);
+ }
+ return acc;
+ }, []);
+
+ return results.reverse();
+ },
+
+ query: (query) => {
+ const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
+ const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ // console.info("search results:", Search.lastresults);
+
+ // print the results
+ _displayNextItem(results, results.length, searchTerms, highlightTerms);
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch: (object, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const objects = Search._index.objects;
+ const objNames = Search._index.objnames;
+ const titles = Search._index.titles;
+
+ const results = [];
+
+ const objectSearchCallback = (prefix, match) => {
+ const name = match[4]
+ const fullname = (prefix ? prefix + "." : "") + name;
+ const fullnameLower = fullname.toLowerCase();
+ if (fullnameLower.indexOf(object) < 0) return;
+
+ let score = 0;
+ const parts = fullnameLower.split(".");
+
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullnameLower === object || parts.slice(-1)[0] === object)
+ score += Scorer.objNameMatch;
+ else if (parts.slice(-1)[0].indexOf(object) > -1)
+ score += Scorer.objPartialMatch; // matches in last name
+
+ const objName = objNames[match[1]][2];
+ const title = titles[match[0]];
+
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ const otherTerms = new Set(objectTerms);
+ otherTerms.delete(object);
+ if (otherTerms.size > 0) {
+ const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
+ if (
+ [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
+ )
+ return;
+ }
+
+ let anchor = match[3];
+ if (anchor === "") anchor = fullname;
+ else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
+
+ const descr = objName + _(", in ") + title;
+
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2]))
+ score += Scorer.objPrio[match[2]];
+ else score += Scorer.objPrioDefault;
+
+ results.push([
+ docNames[match[0]],
+ fullname,
+ "#" + anchor,
+ descr,
+ score,
+ filenames[match[0]],
+ SearchResultKind.object,
+ ]);
+ };
+ Object.keys(objects).forEach((prefix) =>
+ objects[prefix].forEach((array) =>
+ objectSearchCallback(prefix, array)
+ )
+ );
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch: (searchTerms, excludedTerms) => {
+ // prepare search
+ const terms = Search._index.terms;
+ const titleTerms = Search._index.titleterms;
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+
+ const scoreMap = new Map();
+ const fileMap = new Map();
+
+ // perform the search on the required terms
+ searchTerms.forEach((word) => {
+ const files = [];
+ // find documents, if any, containing the query word in their text/title term indices
+ // use Object.hasOwnProperty to avoid mismatching against prototype properties
+ const arr = [
+ { files: terms.hasOwnProperty(word) ? terms[word] : undefined, score: Scorer.term },
+ { files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, score: Scorer.title },
+ ];
+ // add support for partial matches
+ if (word.length > 2) {
+ const escapedWord = _escapeRegExp(word);
+ if (!terms.hasOwnProperty(word)) {
+ Object.keys(terms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: terms[term], score: Scorer.partialTerm });
+ });
+ }
+ if (!titleTerms.hasOwnProperty(word)) {
+ Object.keys(titleTerms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
+ });
+ }
+ }
+
+ // no match but word was a required one
+ if (arr.every((record) => record.files === undefined)) return;
+
+ // found search word in contents
+ arr.forEach((record) => {
+ if (record.files === undefined) return;
+
+ let recordFiles = record.files;
+ if (recordFiles.length === undefined) recordFiles = [recordFiles];
+ files.push(...recordFiles);
+
+ // set score for the word in each file
+ recordFiles.forEach((file) => {
+ if (!scoreMap.has(file)) scoreMap.set(file, new Map());
+ const fileScores = scoreMap.get(file);
+ fileScores.set(word, record.score);
+ });
+ });
+
+ // create the mapping
+ files.forEach((file) => {
+ if (!fileMap.has(file)) fileMap.set(file, [word]);
+ else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
+ });
+ });
+
+ // now check if the files don't contain excluded terms
+ const results = [];
+ for (const [file, wordList] of fileMap) {
+ // check if all requirements are matched
+
+ // as search terms with length < 3 are discarded
+ const filteredTermCount = [...searchTerms].filter(
+ (term) => term.length > 2
+ ).length;
+ if (
+ wordList.length !== searchTerms.size &&
+ wordList.length !== filteredTermCount
+ )
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ if (
+ [...excludedTerms].some(
+ (term) =>
+ terms[term] === file ||
+ titleTerms[term] === file ||
+ (terms[term] || []).includes(file) ||
+ (titleTerms[term] || []).includes(file)
+ )
+ )
+ break;
+
+ // select one (max) score for the file.
+ const score = Math.max(...wordList.map((w) => scoreMap.get(file).get(w)));
+ // add result to the result list
+ results.push([
+ docNames[file],
+ titles[file],
+ "",
+ null,
+ score,
+ filenames[file],
+ SearchResultKind.text,
+ ]);
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words.
+ */
+ makeSearchSummary: (htmlText, keywords, anchor) => {
+ const text = Search.htmlToText(htmlText, anchor);
+ if (text === "") return null;
+
+ const textLower = text.toLowerCase();
+ const actualStartPosition = [...keywords]
+ .map((k) => textLower.indexOf(k.toLowerCase()))
+ .filter((i) => i > -1)
+ .slice(-1)[0];
+ const startWithContext = Math.max(actualStartPosition - 120, 0);
+
+ const top = startWithContext === 0 ? "" : "...";
+ const tail = startWithContext + 240 < text.length ? "..." : "";
+
+ let summary = document.createElement("p");
+ summary.classList.add("context");
+ summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
+
+ return summary;
+ },
+};
+
+_ready(Search.init);
diff --git a/SRV/docs/_build/html/_static/skeleton.css b/SRV/docs/_build/html/_static/skeleton.css
new file mode 100644
index 0000000..467c878
--- /dev/null
+++ b/SRV/docs/_build/html/_static/skeleton.css
@@ -0,0 +1,296 @@
+/* Some sane resets. */
+html {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ min-height: 100%;
+}
+
+/* All the flexbox magic! */
+body,
+.sb-announcement,
+.sb-content,
+.sb-main,
+.sb-container,
+.sb-container__inner,
+.sb-article-container,
+.sb-footer-content,
+.sb-header,
+.sb-header-secondary,
+.sb-footer {
+ display: flex;
+}
+
+/* These order things vertically */
+body,
+.sb-main,
+.sb-article-container {
+ flex-direction: column;
+}
+
+/* Put elements in the center */
+.sb-header,
+.sb-header-secondary,
+.sb-container,
+.sb-content,
+.sb-footer,
+.sb-footer-content {
+ justify-content: center;
+}
+/* Put elements at the ends */
+.sb-article-container {
+ justify-content: space-between;
+}
+
+/* These elements grow. */
+.sb-main,
+.sb-content,
+.sb-container,
+article {
+ flex-grow: 1;
+}
+
+/* Because padding making this wider is not fun */
+article {
+ box-sizing: border-box;
+}
+
+/* The announcements element should never be wider than the page. */
+.sb-announcement {
+ max-width: 100%;
+}
+
+.sb-sidebar-primary,
+.sb-sidebar-secondary {
+ flex-shrink: 0;
+ width: 17rem;
+}
+
+.sb-announcement__inner {
+ justify-content: center;
+
+ box-sizing: border-box;
+ height: 3rem;
+
+ overflow-x: auto;
+ white-space: nowrap;
+}
+
+/* Sidebars, with checkbox-based toggle */
+.sb-sidebar-primary,
+.sb-sidebar-secondary {
+ position: fixed;
+ height: 100%;
+ top: 0;
+}
+
+.sb-sidebar-primary {
+ left: -17rem;
+ transition: left 250ms ease-in-out;
+}
+.sb-sidebar-secondary {
+ right: -17rem;
+ transition: right 250ms ease-in-out;
+}
+
+.sb-sidebar-toggle {
+ display: none;
+}
+.sb-sidebar-overlay {
+ position: fixed;
+ top: 0;
+ width: 0;
+ height: 0;
+
+ transition: width 0ms ease 250ms, height 0ms ease 250ms, opacity 250ms ease;
+
+ opacity: 0;
+ background-color: rgba(0, 0, 0, 0.54);
+}
+
+#sb-sidebar-toggle--primary:checked
+ ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--primary"],
+#sb-sidebar-toggle--secondary:checked
+ ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--secondary"] {
+ width: 100%;
+ height: 100%;
+ opacity: 1;
+ transition: width 0ms ease, height 0ms ease, opacity 250ms ease;
+}
+
+#sb-sidebar-toggle--primary:checked ~ .sb-container .sb-sidebar-primary {
+ left: 0;
+}
+#sb-sidebar-toggle--secondary:checked ~ .sb-container .sb-sidebar-secondary {
+ right: 0;
+}
+
+/* Full-width mode */
+.drop-secondary-sidebar-for-full-width-content
+ .hide-when-secondary-sidebar-shown {
+ display: none !important;
+}
+.drop-secondary-sidebar-for-full-width-content .sb-sidebar-secondary {
+ display: none !important;
+}
+
+/* Mobile views */
+.sb-page-width {
+ width: 100%;
+}
+
+.sb-article-container,
+.sb-footer-content__inner,
+.drop-secondary-sidebar-for-full-width-content .sb-article,
+.drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 100vw;
+}
+
+.sb-article,
+.match-content-width {
+ padding: 0 1rem;
+ box-sizing: border-box;
+}
+
+@media (min-width: 32rem) {
+ .sb-article,
+ .match-content-width {
+ padding: 0 2rem;
+ }
+}
+
+/* Tablet views */
+@media (min-width: 42rem) {
+ .sb-article-container {
+ width: auto;
+ }
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 42rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 42rem;
+ }
+}
+@media (min-width: 46rem) {
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 46rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 46rem;
+ }
+}
+@media (min-width: 50rem) {
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 50rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 50rem;
+ }
+}
+
+/* Tablet views */
+@media (min-width: 59rem) {
+ .sb-sidebar-secondary {
+ position: static;
+ }
+ .hide-when-secondary-sidebar-shown {
+ display: none !important;
+ }
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 59rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 42rem;
+ }
+}
+@media (min-width: 63rem) {
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 63rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 46rem;
+ }
+}
+@media (min-width: 67rem) {
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 67rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 50rem;
+ }
+}
+
+/* Desktop views */
+@media (min-width: 76rem) {
+ .sb-sidebar-primary {
+ position: static;
+ }
+ .hide-when-primary-sidebar-shown {
+ display: none !important;
+ }
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 59rem;
+ }
+ .sb-article,
+ .match-content-width {
+ width: 42rem;
+ }
+}
+
+/* Full desktop views */
+@media (min-width: 80rem) {
+ .sb-article,
+ .match-content-width {
+ width: 46rem;
+ }
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 63rem;
+ }
+}
+
+@media (min-width: 84rem) {
+ .sb-article,
+ .match-content-width {
+ width: 50rem;
+ }
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 67rem;
+ }
+}
+
+@media (min-width: 88rem) {
+ .sb-footer-content__inner,
+ .drop-secondary-sidebar-for-full-width-content .sb-article,
+ .drop-secondary-sidebar-for-full-width-content .match-content-width {
+ width: 67rem;
+ }
+ .sb-page-width {
+ width: 88rem;
+ }
+}
diff --git a/SRV/docs/_build/html/_static/sphinx_highlight.js b/SRV/docs/_build/html/_static/sphinx_highlight.js
new file mode 100644
index 0000000..8a96c69
--- /dev/null
+++ b/SRV/docs/_build/html/_static/sphinx_highlight.js
@@ -0,0 +1,154 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const rest = document.createTextNode(val.substr(pos + text.length));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ rest,
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+ /* There may be more occurrences of search term in this node. So call this
+ * function recursively on the remaining fragment.
+ */
+ _highlight(rest, addItems, text, className);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '' +
+ '' +
+ _("Hide Search Matches") +
+ "
"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(() => {
+ /* Do not call highlightSearchWords() when we are on the search page.
+ * It will highlight words from the *previous* search query.
+ */
+ if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
+ SphinxHighlight.initEscapeListener();
+});
diff --git a/SRV/docs/_build/html/_static/styles/furo-extensions.css b/SRV/docs/_build/html/_static/styles/furo-extensions.css
new file mode 100644
index 0000000..2d74267
--- /dev/null
+++ b/SRV/docs/_build/html/_static/styles/furo-extensions.css
@@ -0,0 +1,2 @@
+#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0s}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)}
+/*# sourceMappingURL=furo-extensions.css.map*/
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/styles/furo-extensions.css.map b/SRV/docs/_build/html/_static/styles/furo-extensions.css.map
new file mode 100644
index 0000000..68fb7fd
--- /dev/null
+++ b/SRV/docs/_build/html/_static/styles/furo-extensions.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"styles/furo-extensions.css","mappings":"AAGA,2BACE,oFACA,4CAKE,6CAHA,YACA,eAEA,CACA,kDACE,yCAEF,8CACE,sCAEJ,8CACE,kDAEJ,2BAGE,uBACA,cAHA,gBACA,UAEA,CAGA,yCACE,mBAEF,gDAEE,gDADA,YACA,CACA,sDACE,gDACF,yDACE,sCAEJ,+CACE,UACA,qDACE,UAGF,mDACE,eAEJ,yEAEE,4DAEA,mHASE,mBAPA,kBAEA,YADA,oBAGA,aADA,gBAIA,CAEA,qIAEE,WADA,UACA,CAEJ,uGACE,aAEF,iUAGE,cAEF,mHACE,aC1EJ,gCACE,mCAEF,0BAEE,mBAUA,8CACA,YAFA,mCAKA,eAZA,cAIA,YADA,YAYA,iCAdA,YAcA,CAEA,gCAEE,8CADA,gCACA,CAEF,gCAGE,6BADA,mCADA,YAEA,CAEF,kCAEE,cADA,mBACA,CACA,wCACE,cAEJ,8BACE,UCzCN,KAEE,6CAA8C,CAC9C,uDAAwD,CACxD,uDAAwD,CAGxD,iCAAsC,CAGtC,+CAAgD,CAChD,uDAAwD,CACxD,uDAAwD,CACxD,oDAAqD,CACrD,6DAA8D,CAC9D,6DAA8D,CAG9D,uDAAwD,CACxD,yDAA0D,CAC1D,4DAA6D,CAC7D,2DAA4D,CAC5D,8DAA+D,CAC/D,iEAAkE,CAClE,uDAAwD,CACxD,wDAAyD,CAG3D,gBACE,qFAGF,SACE,6EAEF,cACE,uFAEF,cACE,uFAEF,cACE,uFAGF,qBACE,eAEF,mBACE,WACA,eChDF,KACE,gDAAiD,CACjD,uDAAwD,CACxD,qDAAsD,CACtD,4DAA6D,CAC7D,oCAAqC,CACrC,2CAA4C,CAC5C,4CAA6C,CAC7C,mDAAoD,CACpD,wBAAyB,CACzB,oBAAqB,CACrB,6CAA8C,CAC9C,gCAAiC,CACjC,yDAA0D,CAC1D,uDAAwD,CACxD,8DAA+D,CCbjE,uBACE,eACA,eACA,gBAGF,iBACE,YACA,+EAGF,iBACE,mDACA","sources":["webpack:///./src/furo/assets/styles/extensions/_readthedocs.sass","webpack:///./src/furo/assets/styles/extensions/_copybutton.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-design.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-inline-tabs.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-panels.sass"],"sourcesContent":["// This file contains the styles used for tweaking how ReadTheDoc's embedded\n// contents would show up inside the theme.\n\n#furo-sidebar-ad-placement\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n .ethical-sidebar\n // Remove the border and box-shadow.\n border: none\n box-shadow: none\n // Manage the background colors.\n background: var(--color-background-secondary)\n &:hover\n background: var(--color-background-hover)\n // Ensure the text is legible.\n a\n color: var(--color-foreground-primary)\n\n .ethical-callout a\n color: var(--color-foreground-secondary) !important\n\n#furo-readthedocs-versions\n position: static\n width: 100%\n background: transparent\n display: block\n\n // Make the background color fit with the theme's aesthetic.\n .rst-versions\n background: rgb(26, 28, 30)\n\n .rst-current-version\n cursor: unset\n background: var(--color-sidebar-item-background)\n &:hover\n background: var(--color-sidebar-item-background)\n .fa-book\n color: var(--color-foreground-primary)\n\n > .rst-other-versions\n padding: 0\n small\n opacity: 1\n\n .injected\n .rst-versions\n position: unset\n\n &:hover,\n &:focus-within\n box-shadow: 0 0 0 1px var(--color-sidebar-background-border)\n\n .rst-current-version\n // Undo the tweaks done in RTD's CSS\n font-size: inherit\n line-height: inherit\n height: auto\n text-align: right\n padding: 12px\n\n // Match the rest of the body\n background: #1a1c1e\n\n .fa-book\n float: left\n color: white\n\n .fa-caret-down\n display: none\n\n .rst-current-version,\n .rst-other-versions,\n .injected\n display: block\n\n > .rst-current-version\n display: none\n",".highlight\n &:hover button.copybtn\n color: var(--color-code-foreground)\n\n button.copybtn\n // Align things correctly\n align-items: center\n\n height: 1.25em\n width: 1.25em\n\n top: 0.625rem // $code-spacing-vertical\n right: 0.5rem\n\n // Make it look better\n color: var(--color-background-item)\n background-color: var(--color-code-background)\n border: none\n\n // Change to cursor to make it obvious that you can click on it\n cursor: pointer\n\n // Transition smoothly, for aesthetics\n transition: color 300ms, opacity 300ms\n\n &:hover\n color: var(--color-brand-content)\n background-color: var(--color-code-background)\n\n &::after\n display: none\n color: var(--color-code-foreground)\n background-color: transparent\n\n &.success\n transition: color 0ms\n color: #22863a\n &::after\n display: block\n\n svg\n padding: 0\n","body\n // Colors\n --sd-color-primary: var(--color-brand-primary)\n --sd-color-primary-highlight: var(--color-brand-content)\n --sd-color-primary-text: var(--color-background-primary)\n\n // Shadows\n --sd-color-shadow: rgba(0, 0, 0, 0.05)\n\n // Cards\n --sd-color-card-border: var(--color-card-border)\n --sd-color-card-border-hover: var(--color-brand-content)\n --sd-color-card-background: var(--color-card-background)\n --sd-color-card-text: var(--color-foreground-primary)\n --sd-color-card-header: var(--color-card-marginals-background)\n --sd-color-card-footer: var(--color-card-marginals-background)\n\n // Tabs\n --sd-color-tabs-label-active: var(--color-brand-content)\n --sd-color-tabs-label-hover: var(--color-foreground-muted)\n --sd-color-tabs-label-inactive: var(--color-foreground-muted)\n --sd-color-tabs-underline-active: var(--color-brand-content)\n --sd-color-tabs-underline-hover: var(--color-foreground-border)\n --sd-color-tabs-underline-inactive: var(--color-background-border)\n --sd-color-tabs-overline: var(--color-background-border)\n --sd-color-tabs-underline: var(--color-background-border)\n\n// Tabs\n.sd-tab-content\n box-shadow: 0 -2px var(--sd-color-tabs-overline), 0 1px var(--sd-color-tabs-underline)\n\n// Shadows\n.sd-card // Have a shadow by default\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n.sd-shadow-sm\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-md\n box-shadow: 0 0.3rem 0.75rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-lg\n box-shadow: 0 0.6rem 1.5rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Cards\n.sd-card-hover:hover // Don't change scale on hover\n transform: none\n\n.sd-cards-carousel // Have a bit of gap in the carousel by default\n gap: 0.25rem\n padding: 0.25rem\n","// This file contains styles to tweak sphinx-inline-tabs to work well with Furo.\n\nbody\n --tabs--label-text: var(--color-foreground-muted)\n --tabs--label-text--hover: var(--color-foreground-muted)\n --tabs--label-text--active: var(--color-brand-content)\n --tabs--label-text--active--hover: var(--color-brand-content)\n --tabs--label-background: transparent\n --tabs--label-background--hover: transparent\n --tabs--label-background--active: transparent\n --tabs--label-background--active--hover: transparent\n --tabs--padding-x: 0.25em\n --tabs--margin-x: 1em\n --tabs--border: var(--color-background-border)\n --tabs--label-border: transparent\n --tabs--label-border--hover: var(--color-foreground-muted)\n --tabs--label-border--active: var(--color-brand-content)\n --tabs--label-border--active--hover: var(--color-brand-content)\n","// This file contains styles to tweak sphinx-panels to work well with Furo.\n\n// sphinx-panels includes Bootstrap 4, which uses .container which can conflict\n// with docutils' `.. container::` directive.\n[role=\"main\"] .container\n max-width: initial\n padding-left: initial\n padding-right: initial\n\n// Make the panels look nicer!\n.shadow.docutils\n border: none\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Make panel colors respond to dark mode\n.sphinx-bs .card\n background-color: var(--color-background-secondary)\n color: var(--color-foreground)\n"],"names":[],"sourceRoot":""}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/styles/furo.css b/SRV/docs/_build/html/_static/styles/furo.css
new file mode 100644
index 0000000..592d5bf
--- /dev/null
+++ b/SRV/docs/_build/html/_static/styles/furo.css
@@ -0,0 +1,2 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,p,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;background:var(--color-background-primary);border:0!important;color:var(--color-foreground-primary);white-space:nowrap!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:"SFMono-Regular",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-stack--headings:var(--font-stack);--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8, ');--icon-pencil:url('data:image/svg+xml;charset=utf-8, ');--icon-abstract:url('data:image/svg+xml;charset=utf-8, ');--icon-info:url('data:image/svg+xml;charset=utf-8, ');--icon-flame:url('data:image/svg+xml;charset=utf-8, ');--icon-question:url('data:image/svg+xml;charset=utf-8, ');--icon-warning:url('data:image/svg+xml;charset=utf-8, ');--icon-failure:url('data:image/svg+xml;charset=utf-8, ');--icon-spark:url('data:image/svg+xml;charset=utf-8, ');--color-admonition-title--caution:#ff9100;--color-admonition-title-background--caution:rgba(255,145,0,.2);--color-admonition-title--warning:#ff9100;--color-admonition-title-background--warning:rgba(255,145,0,.2);--color-admonition-title--danger:#ff5252;--color-admonition-title-background--danger:rgba(255,82,82,.2);--color-admonition-title--attention:#ff5252;--color-admonition-title-background--attention:rgba(255,82,82,.2);--color-admonition-title--error:#ff5252;--color-admonition-title-background--error:rgba(255,82,82,.2);--color-admonition-title--hint:#00c852;--color-admonition-title-background--hint:rgba(0,200,82,.2);--color-admonition-title--tip:#00c852;--color-admonition-title-background--tip:rgba(0,200,82,.2);--color-admonition-title--important:#00bfa5;--color-admonition-title-background--important:rgba(0,191,165,.2);--color-admonition-title--note:#00b0ff;--color-admonition-title-background--note:rgba(0,176,255,.2);--color-admonition-title--seealso:#448aff;--color-admonition-title-background--seealso:rgba(68,138,255,.2);--color-admonition-title--admonition-todo:grey;--color-admonition-title-background--admonition-todo:hsla(0,0%,50%,.2);--color-admonition-title:#651fff;--color-admonition-title-background:rgba(101,31,255,.2);--icon-admonition-default:var(--icon-abstract);--color-topic-title:#14b8a6;--color-topic-title-background:rgba(20,184,166,.2);--icon-topic-default:var(--icon-pencil);--color-problematic:#b30000;--color-foreground-primary:#000;--color-foreground-secondary:#5a5c63;--color-foreground-muted:#6b6f76;--color-foreground-border:#878787;--color-background-primary:#fff;--color-background-secondary:#f8f9fb;--color-background-hover:#efeff4;--color-background-hover--transparent:#efeff400;--color-background-border:#eeebee;--color-background-item:#ccc;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#0a4bff;--color-brand-content:#2757dd;--color-brand-visited:#872ee0;--color-api-background:var(--color-background-hover--transparent);--color-api-background-hover:var(--color-background-hover);--color-api-overall:var(--color-foreground-secondary);--color-api-name:var(--color-problematic);--color-api-pre-name:var(--color-problematic);--color-api-paren:var(--color-foreground-secondary);--color-api-keyword:var(--color-foreground-primary);--color-api-added:#21632c;--color-api-added-border:#38a84d;--color-api-changed:#046172;--color-api-changed-border:#06a1bc;--color-api-deprecated:#605706;--color-api-deprecated-border:#f0d90f;--color-api-removed:#b30000;--color-api-removed-border:#ff5c5c;--color-highlight-on-target:#ffc;--color-inline-code-background:var(--color-background-secondary);--color-highlighted-background:#def;--color-highlighted-text:var(--color-foreground-primary);--color-guilabel-background:#ddeeff80;--color-guilabel-border:#bedaf580;--color-guilabel-text:var(--color-foreground-primary);--color-admonition-background:transparent;--color-table-header-background:var(--color-background-secondary);--color-table-border:var(--color-background-border);--color-card-border:var(--color-background-secondary);--color-card-background:transparent;--color-card-marginals-background:var(--color-background-secondary);--color-header-background:var(--color-background-primary);--color-header-border:var(--color-background-border);--color-header-text:var(--color-foreground-primary);--color-sidebar-background:var(--color-background-secondary);--color-sidebar-background-border:var(--color-background-border);--color-sidebar-brand-text:var(--color-foreground-primary);--color-sidebar-caption-text:var(--color-foreground-muted);--color-sidebar-link-text:var(--color-foreground-secondary);--color-sidebar-link-text--top-level:var(--color-brand-primary);--color-sidebar-item-background:var(--color-sidebar-background);--color-sidebar-item-background--current:var( --color-sidebar-item-background );--color-sidebar-item-background--hover:linear-gradient(90deg,var(--color-background-hover--transparent) 0%,var(--color-background-hover) var(--sidebar-item-spacing-horizontal),var(--color-background-hover) 100%);--color-sidebar-item-expander-background:transparent;--color-sidebar-item-expander-background--hover:var( --color-background-hover );--color-sidebar-search-text:var(--color-foreground-primary);--color-sidebar-search-background:var(--color-background-secondary);--color-sidebar-search-background--focus:var(--color-background-primary);--color-sidebar-search-border:var(--color-background-border);--color-sidebar-search-icon:var(--color-foreground-muted);--color-toc-background:var(--color-background-primary);--color-toc-title-text:var(--color-foreground-muted);--color-toc-item-text:var(--color-foreground-secondary);--color-toc-item-text--hover:var(--color-foreground-primary);--color-toc-item-text--active:var(--color-brand-primary);--color-content-foreground:var(--color-foreground-primary);--color-content-background:transparent;--color-link:var(--color-brand-content);--color-link-underline:var(--color-background-border);--color-link--hover:var(--color-brand-content);--color-link-underline--hover:var(--color-foreground-border);--color-link--visited:var(--color-brand-visited);--color-link-underline--visited:var(--color-background-border);--color-link--visited--hover:var(--color-brand-visited);--color-link-underline--visited--hover:var(--color-foreground-border)}.only-light{display:block!important}html body .only-dark{display:none!important}@media not print{body[data-theme=dark]{--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body[data-theme=dark] .only-light{display:none!important}body[data-theme=dark] .only-dark{display:block!important}@media(prefers-color-scheme:dark){body:not([data-theme=light]){--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body:not([data-theme=light]) .only-light{display:none!important}body:not([data-theme=light]) .only-dark{display:block!important}}}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:block}@media(prefers-color-scheme:dark){body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-dark{display:block}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:none}}body[data-theme=dark] .theme-toggle svg.theme-icon-when-dark,body[data-theme=light] .theme-toggle svg.theme-icon-when-light{display:block}body{font-family:var(--font-stack)}code,kbd,pre,samp{font-family:var(--font-stack--monospace)}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article{line-height:1.5}h1,h2,h3,h4,h5,h6{border-radius:.5rem;font-family:var(--font-stack--headings);font-weight:700;line-height:1.25;margin:.5rem -.5rem;padding-left:.5rem;padding-right:.5rem}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0}h1{font-size:2.5em;margin-bottom:1rem}h1,h2{margin-top:1.75rem}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.25em}h5{font-size:1.125em}h6{font-size:1em}small{font-size:80%;opacity:75%}p{margin-bottom:.75rem;margin-top:.5rem}hr.docutils{background-color:var(--color-background-border);border:0;height:1px;margin:2rem 0;padding:0}.centered{text-align:center}a{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}a:visited{color:var(--color-link--visited);text-decoration-color:var(--color-link-underline--visited)}a:visited:hover{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}a:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link{color:inherit}a.muted-link:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link:hover:visited{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}html{overflow-x:hidden;overflow-y:scroll;scroll-behavior:smooth}.sidebar-scroll,.toc-scroll,article[role=main] *{scrollbar-color:var(--color-foreground-border) transparent;scrollbar-width:thin}body,html{height:100%}.skip-to-content,body,html{background:var(--color-background-primary);color:var(--color-foreground-primary)}.skip-to-content{border-radius:1rem;left:.25rem;padding:1rem;position:fixed;top:.25rem;transform:translateY(-200%);transition:transform .3s ease-in-out;z-index:40}.skip-to-content:focus-within{transform:translateY(0)}article{background:var(--color-content-background);color:var(--color-content-foreground);overflow-wrap:break-word}.page{display:flex;min-height:100%}.mobile-header{background-color:var(--color-header-background);border-bottom:1px solid var(--color-header-border);color:var(--color-header-text);display:none;height:var(--header-height);width:100%;z-index:10}.mobile-header.scrolled{border-bottom:none;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.mobile-header .header-center a{color:var(--color-header-text);text-decoration:none}.main{display:flex;flex:1}.sidebar-drawer{background:var(--color-sidebar-background);border-right:1px solid var(--color-sidebar-background-border);box-sizing:border-box;display:flex;justify-content:flex-end;min-width:15em;width:calc(50% - 26em)}.sidebar-container,.toc-drawer{box-sizing:border-box;width:15em}.toc-drawer{background:var(--color-toc-background);padding-right:1rem}.sidebar-sticky,.toc-sticky{display:flex;flex-direction:column;height:min(100%,100vh);height:100vh;position:sticky;top:0}.sidebar-scroll,.toc-scroll{flex-grow:1;flex-shrink:1;overflow:auto;scroll-behavior:smooth}.content{display:flex;flex-direction:column;justify-content:space-between;padding:0 3em;width:46em}.icon{display:inline-block;height:1rem;width:1rem}.icon svg{height:100%;width:100%}.announcement{align-items:center;background-color:var(--color-announcement-background);color:var(--color-announcement-text);display:flex;height:var(--header-height);overflow-x:auto}.announcement+.page{min-height:calc(100% - var(--header-height))}.announcement-content{box-sizing:border-box;min-width:100%;padding:.5rem;text-align:center;white-space:nowrap}.announcement-content a{color:var(--color-announcement-text);text-decoration-color:var(--color-announcement-text)}.announcement-content a:hover{color:var(--color-announcement-text);text-decoration-color:var(--color-link--hover)}.no-js .theme-toggle-container{display:none}.theme-toggle-container{display:flex}.theme-toggle{background:transparent;border:none;cursor:pointer;display:flex;padding:0}.theme-toggle svg{color:var(--color-foreground-primary);display:none;height:1.25rem;width:1.25rem}.theme-toggle-header{align-items:center;display:flex;justify-content:center}.nav-overlay-icon,.toc-overlay-icon{cursor:pointer;display:none}.nav-overlay-icon .icon,.toc-overlay-icon .icon{color:var(--color-foreground-secondary);height:1.5rem;width:1.5rem}.nav-overlay-icon,.toc-header-icon{align-items:center;justify-content:center}.toc-content-icon{height:1.5rem;width:1.5rem}.content-icon-container{display:flex;float:right;gap:.5rem;margin-bottom:1rem;margin-left:1rem;margin-top:1.5rem}.content-icon-container .edit-this-page svg,.content-icon-container .view-this-page svg{color:inherit;height:1.25rem;width:1.25rem}.sidebar-toggle{display:none;position:absolute}.sidebar-toggle[name=__toc]{left:20px}.sidebar-toggle:checked{left:40px}.overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0s,height 0s,opacity .25s ease-out;width:0}.sidebar-overlay{z-index:20}.toc-overlay{z-index:40}.sidebar-drawer{transition:left .25s ease-in-out;z-index:30}.toc-drawer{transition:right .25s ease-in-out;z-index:50}#__navigation:checked~.sidebar-overlay{height:100%;opacity:1;width:100%}#__navigation:checked~.page .sidebar-drawer{left:0;top:0}#__toc:checked~.toc-overlay{height:100%;opacity:1;width:100%}#__toc:checked~.page .toc-drawer{right:0;top:0}.back-to-top{background:var(--color-background-primary);border-radius:1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.502);display:none;font-size:.8125rem;left:0;margin-left:50%;padding:.5rem .75rem .5rem .5rem;position:fixed;text-decoration:none;top:1rem;transform:translateX(-50%);z-index:10}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top span{margin-left:.25rem}.show-back-to-top .back-to-top{align-items:center;display:flex}@media(min-width:97em){html{font-size:110%}}@media(max-width:82em){.toc-content-icon{display:flex}.toc-drawer{border-left:1px solid var(--color-background-muted);height:100vh;position:fixed;right:-15em;top:0}.toc-tree{border-left:none;font-size:var(--toc-font-size--mobile)}.sidebar-drawer{width:calc(50% - 18.5em)}}@media(max-width:67em){.content{margin-left:auto;margin-right:auto;padding:0 1em}}@media(max-width:63em){.nav-overlay-icon{display:flex}.sidebar-drawer{height:100vh;left:-15em;position:fixed;top:0;width:15em}.theme-toggle-header,.toc-header-icon{display:flex}.theme-toggle-content,.toc-content-icon{display:none}.mobile-header{align-items:center;display:flex;justify-content:space-between;position:sticky;top:0}.mobile-header .header-left,.mobile-header .header-right{display:flex;height:var(--header-height);padding:0 var(--header-padding)}.mobile-header .header-left label,.mobile-header .header-right label{height:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.nav-overlay-icon .icon,.theme-toggle svg{height:1.5rem;width:1.5rem}:target{scroll-margin-top:calc(var(--header-height) + 2.5rem)}.back-to-top{top:calc(var(--header-height) + .5rem)}.page{flex-direction:column;justify-content:center}}@media(max-width:48em){.content{overflow-x:auto;width:100%}}@media(max-width:46em){article[role=main] aside.sidebar{float:none;margin:1rem 0;width:100%}}.admonition,.topic{background:var(--color-admonition-background);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);font-size:var(--admonition-font-size);margin:1rem auto;overflow:hidden;padding:0 .5rem .5rem;page-break-inside:avoid}.admonition>:nth-child(2),.topic>:nth-child(2){margin-top:0}.admonition>:last-child,.topic>:last-child{margin-bottom:0}.admonition p.admonition-title,p.topic-title{font-size:var(--admonition-title-font-size);font-weight:500;line-height:1.3;margin:0 -.5rem .5rem;padding:.4rem .5rem .4rem 2rem;position:relative}.admonition p.admonition-title:before,p.topic-title:before{content:"";height:1rem;left:.5rem;position:absolute;width:1rem}p.admonition-title{background-color:var(--color-admonition-title-background)}p.admonition-title:before{background-color:var(--color-admonition-title);-webkit-mask-image:var(--icon-admonition-default);mask-image:var(--icon-admonition-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}p.topic-title{background-color:var(--color-topic-title-background)}p.topic-title:before{background-color:var(--color-topic-title);-webkit-mask-image:var(--icon-topic-default);mask-image:var(--icon-topic-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.admonition{border-left:.2rem solid var(--color-admonition-title)}.admonition.caution{border-left-color:var(--color-admonition-title--caution)}.admonition.caution>.admonition-title{background-color:var(--color-admonition-title-background--caution)}.admonition.caution>.admonition-title:before{background-color:var(--color-admonition-title--caution);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.warning{border-left-color:var(--color-admonition-title--warning)}.admonition.warning>.admonition-title{background-color:var(--color-admonition-title-background--warning)}.admonition.warning>.admonition-title:before{background-color:var(--color-admonition-title--warning);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.danger{border-left-color:var(--color-admonition-title--danger)}.admonition.danger>.admonition-title{background-color:var(--color-admonition-title-background--danger)}.admonition.danger>.admonition-title:before{background-color:var(--color-admonition-title--danger);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.attention{border-left-color:var(--color-admonition-title--attention)}.admonition.attention>.admonition-title{background-color:var(--color-admonition-title-background--attention)}.admonition.attention>.admonition-title:before{background-color:var(--color-admonition-title--attention);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.error{border-left-color:var(--color-admonition-title--error)}.admonition.error>.admonition-title{background-color:var(--color-admonition-title-background--error)}.admonition.error>.admonition-title:before{background-color:var(--color-admonition-title--error);-webkit-mask-image:var(--icon-failure);mask-image:var(--icon-failure)}.admonition.hint{border-left-color:var(--color-admonition-title--hint)}.admonition.hint>.admonition-title{background-color:var(--color-admonition-title-background--hint)}.admonition.hint>.admonition-title:before{background-color:var(--color-admonition-title--hint);-webkit-mask-image:var(--icon-question);mask-image:var(--icon-question)}.admonition.tip{border-left-color:var(--color-admonition-title--tip)}.admonition.tip>.admonition-title{background-color:var(--color-admonition-title-background--tip)}.admonition.tip>.admonition-title:before{background-color:var(--color-admonition-title--tip);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.important{border-left-color:var(--color-admonition-title--important)}.admonition.important>.admonition-title{background-color:var(--color-admonition-title-background--important)}.admonition.important>.admonition-title:before{background-color:var(--color-admonition-title--important);-webkit-mask-image:var(--icon-flame);mask-image:var(--icon-flame)}.admonition.note{border-left-color:var(--color-admonition-title--note)}.admonition.note>.admonition-title{background-color:var(--color-admonition-title-background--note)}.admonition.note>.admonition-title:before{background-color:var(--color-admonition-title--note);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition.seealso{border-left-color:var(--color-admonition-title--seealso)}.admonition.seealso>.admonition-title{background-color:var(--color-admonition-title-background--seealso)}.admonition.seealso>.admonition-title:before{background-color:var(--color-admonition-title--seealso);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.admonition-todo{border-left-color:var(--color-admonition-title--admonition-todo)}.admonition.admonition-todo>.admonition-title{background-color:var(--color-admonition-title-background--admonition-todo)}.admonition.admonition-todo>.admonition-title:before{background-color:var(--color-admonition-title--admonition-todo);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition-todo>.admonition-title{text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd{margin-left:2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:first-child{margin-top:.125rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list,dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:last-child{margin-bottom:.75rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list>dt{font-size:var(--font-size--small);text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd:empty{margin-bottom:.5rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul{margin-left:-1.2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p:nth-child(2){margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p+p:last-child:empty{margin-bottom:0;margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{color:var(--color-api-overall)}.sig:not(.sig-inline){background:var(--color-api-background);border-radius:.25rem;font-family:var(--font-stack--monospace);font-size:var(--api-font-size);font-weight:700;margin-left:-.25rem;margin-right:-.25rem;padding:.25rem .5rem .25rem 3em;text-indent:-2.5em;transition:background .1s ease-out}.sig:not(.sig-inline):hover{background:var(--color-api-background-hover)}.sig:not(.sig-inline) a.reference .viewcode-link{font-weight:400;width:4.25rem}em.property{font-style:normal}em.property:first-child{color:var(--color-api-keyword)}.sig-name{color:var(--color-api-name)}.sig-prename{color:var(--color-api-pre-name);font-weight:400}.sig-paren{color:var(--color-api-paren)}.sig-param{font-style:normal}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{border-left:.1875rem solid;border-radius:.125rem;padding-left:.75rem}div.deprecated p,div.versionadded p,div.versionchanged p,div.versionremoved p{margin-bottom:.125rem;margin-top:.125rem}div.versionadded{border-color:var(--color-api-added-border)}div.versionadded .versionmodified{color:var(--color-api-added)}div.versionchanged{border-color:var(--color-api-changed-border)}div.versionchanged .versionmodified{color:var(--color-api-changed)}div.deprecated{border-color:var(--color-api-deprecated-border)}div.deprecated .versionmodified{color:var(--color-api-deprecated)}div.versionremoved{border-color:var(--color-api-removed-border)}div.versionremoved .versionmodified{color:var(--color-api-removed)}.viewcode-back,.viewcode-link{float:right;text-align:right}.line-block{margin-bottom:.75rem;margin-top:.5rem}.line-block .line-block{margin-bottom:0;margin-top:0;padding-left:1rem}.code-block-caption,article p.caption,table>caption{font-size:var(--font-size--small);text-align:center}.toctree-wrapper.compound .caption,.toctree-wrapper.compound :not(.caption)>.caption-text{font-size:var(--font-size--small);margin-bottom:0;text-align:initial;text-transform:uppercase}.toctree-wrapper.compound>ul{margin-bottom:0;margin-top:0}.sig-inline,code.literal{background:var(--color-inline-code-background);border-radius:.2em;font-size:var(--font-size--small--2);padding:.1em .2em}pre.literal-block .sig-inline,pre.literal-block code.literal{font-size:inherit;padding:0}p .sig-inline,p code.literal{border:1px solid var(--color-background-border)}.sig-inline{font-family:var(--font-stack--monospace)}div[class*=" highlight-"],div[class^=highlight-]{display:flex;margin:1em 0}div[class*=" highlight-"] .table-wrapper,div[class^=highlight-] .table-wrapper,pre{margin:0;padding:0}pre{overflow:auto}article[role=main] .highlight pre{line-height:1.5}.highlight pre,pre.literal-block{font-size:var(--code-font-size);padding:.625rem .875rem}pre.literal-block{background-color:var(--color-code-background);border-radius:.2rem;color:var(--color-code-foreground);margin-bottom:1rem;margin-top:1rem}.highlight{border-radius:.2rem;width:100%}.highlight .gp,.highlight span.linenos{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight .hll{display:block;margin-left:-.875rem;margin-right:-.875rem;padding-left:.875rem;padding-right:.875rem}.code-block-caption{background-color:var(--color-code-background);border-bottom:1px solid;border-radius:.25rem;border-bottom-left-radius:0;border-bottom-right-radius:0;border-color:var(--color-background-border);color:var(--color-code-foreground);display:flex;font-weight:300;padding:.625rem .875rem}.code-block-caption+div[class]{margin-top:0}.code-block-caption+div[class]>.highlight{border-top-left-radius:0;border-top-right-radius:0}.highlighttable{display:block;width:100%}.highlighttable tbody{display:block}.highlighttable tr{display:flex}.highlighttable td.linenos{background-color:var(--color-code-background);border-bottom-left-radius:.2rem;border-top-left-radius:.2rem;color:var(--color-code-foreground);padding:.625rem 0 .625rem .875rem}.highlighttable .linenodiv{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;font-size:var(--code-font-size);padding-right:.875rem}.highlighttable td.code{display:block;flex:1;overflow:hidden;padding:0}.highlighttable td.code .highlight{border-bottom-left-radius:0;border-top-left-radius:0}.highlight span.linenos{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;display:inline-block;margin-right:.875rem;padding-left:0;padding-right:.875rem}.footnote-reference{font-size:var(--font-size--small--4);vertical-align:super}dl.footnote.brackets{color:var(--color-foreground-secondary);display:grid;font-size:var(--font-size--small);grid-template-columns:max-content auto}dl.footnote.brackets dt{margin:0}dl.footnote.brackets dt>.fn-backref{margin-left:.25rem}dl.footnote.brackets dt:after{content:":"}dl.footnote.brackets dt .brackets:before{content:"["}dl.footnote.brackets dt .brackets:after{content:"]"}dl.footnote.brackets dd{margin:0;padding:0 1rem}aside.footnote{color:var(--color-foreground-secondary);font-size:var(--font-size--small)}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>:not(span),div.citation>p{margin-left:2rem}img{box-sizing:border-box;height:auto;max-width:100%}article .figure,article figure{border-radius:.2rem;margin:0}article .figure :last-child,article figure :last-child{margin-bottom:0}article .align-left{clear:left;float:left;margin:0 1rem 1rem}article .align-right{clear:right;float:right;margin:0 1rem 1rem}article .align-center,article .align-default{display:block;margin-left:auto;margin-right:auto;text-align:center}article table.align-default{display:table;text-align:initial}.domainindex-jumpbox,.genindex-jumpbox{border-bottom:1px solid var(--color-background-border);border-top:1px solid var(--color-background-border);padding:.25rem}.domainindex-section h2,.genindex-section h2{margin-bottom:.5rem;margin-top:.75rem}.domainindex-section ul,.genindex-section ul{margin-bottom:0;margin-top:0}ol,ul{margin-bottom:1rem;margin-top:1rem;padding-left:1.2rem}ol li>p:first-child,ul li>p:first-child{margin-bottom:.25rem;margin-top:.25rem}ol li>p:last-child,ul li>p:last-child{margin-top:.25rem}ol li>ol,ol li>ul,ul li>ol,ul li>ul{margin-bottom:.5rem;margin-top:.5rem}ol.arabic{list-style:decimal}ol.loweralpha{list-style:lower-alpha}ol.upperalpha{list-style:upper-alpha}ol.lowerroman{list-style:lower-roman}ol.upperroman{list-style:upper-roman}.simple li>ol,.simple li>ul,.toctree-wrapper li>ol,.toctree-wrapper li>ul{margin-bottom:0;margin-top:0}.field-list dt,.option-list dt,dl.footnote dt,dl.glossary dt,dl.simple dt,dl:not([class]) dt{font-weight:500;margin-top:.25rem}.field-list dt+dt,.option-list dt+dt,dl.footnote dt+dt,dl.glossary dt+dt,dl.simple dt+dt,dl:not([class]) dt+dt{margin-top:0}.field-list dt .classifier:before,.option-list dt .classifier:before,dl.footnote dt .classifier:before,dl.glossary dt .classifier:before,dl.simple dt .classifier:before,dl:not([class]) dt .classifier:before{content:":";margin-left:.2rem;margin-right:.2rem}.field-list dd ul,.field-list dd>p:first-child,.option-list dd ul,.option-list dd>p:first-child,dl.footnote dd ul,dl.footnote dd>p:first-child,dl.glossary dd ul,dl.glossary dd>p:first-child,dl.simple dd ul,dl.simple dd>p:first-child,dl:not([class]) dd ul,dl:not([class]) dd>p:first-child{margin-top:.125rem}.field-list dd ul,.option-list dd ul,dl.footnote dd ul,dl.glossary dd ul,dl.simple dd ul,dl:not([class]) dd ul{margin-bottom:.125rem}.math-wrapper{overflow-x:auto;width:100%}div.math{position:relative;text-align:center}div.math .headerlink,div.math:focus .headerlink{display:none}div.math:hover .headerlink{display:inline-block}div.math span.eqno{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);z-index:1}abbr[title]{cursor:help}.problematic{color:var(--color-problematic)}kbd:not(.compound){background-color:var(--color-background-secondary);border:1px solid var(--color-foreground-border);border-radius:.2rem;box-shadow:0 .0625rem 0 rgba(0,0,0,.2),inset 0 0 0 .125rem var(--color-background-primary);color:var(--color-foreground-primary);display:inline-block;font-size:var(--font-size--small--3);margin:0 .2rem;padding:0 .2rem;vertical-align:text-bottom}blockquote{background:var(--color-background-secondary);border-left:4px solid var(--color-background-border);margin-left:0;margin-right:0;padding:.5rem 1rem}blockquote .attribution{font-weight:600;text-align:right}blockquote.highlights,blockquote.pull-quote{font-size:1.25em}blockquote.epigraph,blockquote.pull-quote{border-left-width:0;border-radius:.5rem}blockquote.highlights{background:transparent;border-left-width:0}p .reference img{vertical-align:middle}p.rubric{font-size:1.125em;font-weight:700;line-height:1.25}dd p.rubric{font-size:var(--font-size--small);font-weight:inherit;line-height:inherit;text-transform:uppercase}article .sidebar{background-color:var(--color-background-secondary);border:1px solid var(--color-background-border);border-radius:.2rem;clear:right;float:right;margin-left:1rem;margin-right:0;width:30%}article .sidebar>*{padding-left:1rem;padding-right:1rem}article .sidebar>ol,article .sidebar>ul{padding-left:2.2rem}article .sidebar .sidebar-title{border-bottom:1px solid var(--color-background-border);font-weight:500;margin:0;padding:.5rem 1rem}[role=main] .table-wrapper.container{margin-bottom:.5rem;margin-top:1rem;overflow-x:auto;padding:.2rem .2rem .75rem;width:100%}table.docutils{border-collapse:collapse;border-radius:.2rem;border-spacing:0;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)}table.docutils th{background:var(--color-table-header-background)}table.docutils td,table.docutils th{border-bottom:1px solid var(--color-table-border);border-left:1px solid var(--color-table-border);border-right:1px solid var(--color-table-border);padding:0 .25rem}table.docutils td p,table.docutils th p{margin:.25rem}table.docutils td:first-child,table.docutils th:first-child{border-left:none}table.docutils td:last-child,table.docutils th:last-child{border-right:none}table.docutils td.text-left,table.docutils th.text-left{text-align:left}table.docutils td.text-right,table.docutils th.text-right{text-align:right}table.docutils td.text-center,table.docutils th.text-center{text-align:center}:target{scroll-margin-top:2.5rem}@media(max-width:67em){:target{scroll-margin-top:calc(2.5rem + var(--header-height))}section>span:target{scroll-margin-top:calc(2.8rem + var(--header-height))}}.headerlink{font-weight:100;-webkit-user-select:none;-moz-user-select:none;user-select:none}.code-block-caption>.headerlink,dl dt>.headerlink,figcaption p>.headerlink,h1>.headerlink,h2>.headerlink,h3>.headerlink,h4>.headerlink,h5>.headerlink,h6>.headerlink,p.caption>.headerlink,table>caption>.headerlink{margin-left:.5rem;visibility:hidden}.code-block-caption:hover>.headerlink,dl dt:hover>.headerlink,figcaption p:hover>.headerlink,h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,p.caption:hover>.headerlink,table>caption:hover>.headerlink{visibility:visible}.code-block-caption>.toc-backref,dl dt>.toc-backref,figcaption p>.toc-backref,h1>.toc-backref,h2>.toc-backref,h3>.toc-backref,h4>.toc-backref,h5>.toc-backref,h6>.toc-backref,p.caption>.toc-backref,table>caption>.toc-backref{color:inherit;text-decoration-line:none}figure:hover>figcaption>p>.headerlink,table:hover>caption>.headerlink{visibility:visible}:target>h1:first-of-type,:target>h2:first-of-type,:target>h3:first-of-type,:target>h4:first-of-type,:target>h5:first-of-type,:target>h6:first-of-type,span:target~h1:first-of-type,span:target~h2:first-of-type,span:target~h3:first-of-type,span:target~h4:first-of-type,span:target~h5:first-of-type,span:target~h6:first-of-type{background-color:var(--color-highlight-on-target)}:target>h1:first-of-type code.literal,:target>h2:first-of-type code.literal,:target>h3:first-of-type code.literal,:target>h4:first-of-type code.literal,:target>h5:first-of-type code.literal,:target>h6:first-of-type code.literal,span:target~h1:first-of-type code.literal,span:target~h2:first-of-type code.literal,span:target~h3:first-of-type code.literal,span:target~h4:first-of-type code.literal,span:target~h5:first-of-type code.literal,span:target~h6:first-of-type code.literal{background-color:transparent}.literal-block-wrapper:target .code-block-caption,.this-will-duplicate-information-and-it-is-still-useful-here li :target,figure:target,table:target>caption{background-color:var(--color-highlight-on-target)}dt:target{background-color:var(--color-highlight-on-target)!important}.footnote-reference:target,.footnote>dt:target+dd{background-color:var(--color-highlight-on-target)}.guilabel{background-color:var(--color-guilabel-background);border:1px solid var(--color-guilabel-border);border-radius:.5em;color:var(--color-guilabel-text);font-size:.9em;padding:0 .3em}footer{display:flex;flex-direction:column;font-size:var(--font-size--small);margin-top:2rem}.bottom-of-page{align-items:center;border-top:1px solid var(--color-background-border);color:var(--color-foreground-secondary);display:flex;justify-content:space-between;line-height:1.5;margin-top:1rem;padding-bottom:1rem;padding-top:1rem}@media(max-width:46em){.bottom-of-page{flex-direction:column-reverse;gap:.25rem;text-align:center}}.bottom-of-page .left-details{font-size:var(--font-size--small)}.bottom-of-page .right-details{display:flex;flex-direction:column;gap:.25rem;text-align:right}.bottom-of-page .icons{display:flex;font-size:1rem;gap:.25rem;justify-content:flex-end}.bottom-of-page .icons a{text-decoration:none}.bottom-of-page .icons img,.bottom-of-page .icons svg{font-size:1.125rem;height:1em;width:1em}.related-pages a{align-items:center;display:flex;text-decoration:none}.related-pages a:hover .page-info .title{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}.related-pages a svg.furo-related-icon,.related-pages a svg.furo-related-icon>use{color:var(--color-foreground-border);flex-shrink:0;height:.75rem;margin:0 .5rem;width:.75rem}.related-pages a.next-page{clear:right;float:right;max-width:50%;text-align:right}.related-pages a.prev-page{clear:left;float:left;max-width:50%}.related-pages a.prev-page svg{transform:rotate(180deg)}.page-info{display:flex;flex-direction:column;overflow-wrap:anywhere}.next-page .page-info{align-items:flex-end}.page-info .context{align-items:center;color:var(--color-foreground-muted);display:flex;font-size:var(--font-size--small);padding-bottom:.1rem;text-decoration:none}ul.search{list-style:none;padding-left:0}ul.search li{border-bottom:1px solid var(--color-background-border);padding:1rem 0}[role=main] .highlighted{background-color:var(--color-highlighted-background);color:var(--color-highlighted-text)}.sidebar-brand{display:flex;flex-direction:column;flex-shrink:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none}.sidebar-brand-text{color:var(--color-sidebar-brand-text);font-size:1.5rem;overflow-wrap:break-word}.sidebar-brand-text,.sidebar-logo-container{margin:var(--sidebar-item-spacing-vertical) 0}.sidebar-logo{display:block;margin:0 auto;max-width:100%}.sidebar-search-container{align-items:center;background:var(--color-sidebar-search-background);display:flex;margin-top:var(--sidebar-search-space-above);position:relative}.sidebar-search-container:focus-within,.sidebar-search-container:hover{background:var(--color-sidebar-search-background--focus)}.sidebar-search-container:before{background-color:var(--color-sidebar-search-icon);content:"";height:var(--sidebar-search-icon-size);left:var(--sidebar-item-spacing-horizontal);-webkit-mask-image:var(--icon-search);mask-image:var(--icon-search);position:absolute;width:var(--sidebar-search-icon-size)}.sidebar-search{background:transparent;border:none;border-bottom:1px solid var(--color-sidebar-search-border);border-top:1px solid var(--color-sidebar-search-border);box-sizing:border-box;color:var(--color-sidebar-search-foreground);padding:var(--sidebar-search-input-spacing-vertical) var(--sidebar-search-input-spacing-horizontal) var(--sidebar-search-input-spacing-vertical) calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size));width:100%;z-index:10}.sidebar-search:focus{outline:none}.sidebar-search::-moz-placeholder{font-size:var(--sidebar-search-input-font-size)}.sidebar-search::placeholder{font-size:var(--sidebar-search-input-font-size)}#searchbox .highlight-link{margin:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0;text-align:center}#searchbox .highlight-link a{color:var(--color-sidebar-search-icon);font-size:var(--font-size--small--2)}.sidebar-tree{font-size:var(--sidebar-item-font-size);margin-bottom:var(--sidebar-item-spacing-vertical);margin-top:var(--sidebar-tree-space-above)}.sidebar-tree ul{display:flex;flex-direction:column;list-style:none;margin-bottom:0;margin-top:0;padding:0}.sidebar-tree li{margin:0;position:relative}.sidebar-tree li>ul{margin-left:var(--sidebar-item-spacing-horizontal)}.sidebar-tree .icon,.sidebar-tree .reference{color:var(--color-sidebar-link-text)}.sidebar-tree .reference{box-sizing:border-box;display:inline-block;height:100%;line-height:var(--sidebar-item-line-height);overflow-wrap:anywhere;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none;width:100%}.sidebar-tree .reference:hover{background:var(--color-sidebar-item-background--hover);color:var(--color-sidebar-link-text)}.sidebar-tree .reference.external:after{color:var(--color-sidebar-link-text);content:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23607d8b' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' viewBox='0 0 24 24'%3E%3Cpath stroke='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M11 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-5M10 14 20 4M15 4h5v5'/%3E%3C/svg%3E");margin:0 .25rem;vertical-align:middle}.sidebar-tree .current-page>.reference{font-weight:700}.sidebar-tree label{align-items:center;cursor:pointer;display:flex;height:var(--sidebar-item-height);justify-content:center;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:var(--sidebar-expander-width)}.sidebar-tree .caption,.sidebar-tree :not(.caption)>.caption-text{color:var(--color-sidebar-caption-text);font-size:var(--sidebar-caption-font-size);font-weight:700;margin:var(--sidebar-caption-space-above) 0 0 0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-transform:uppercase}.sidebar-tree li.has-children>.reference{padding-right:var(--sidebar-expander-width)}.sidebar-tree .toctree-l1>.reference,.sidebar-tree .toctree-l1>label .icon{color:var(--color-sidebar-link-text--top-level)}.sidebar-tree label{background:var(--color-sidebar-item-expander-background)}.sidebar-tree label:hover{background:var(--color-sidebar-item-expander-background--hover)}.sidebar-tree .current>.reference{background:var(--color-sidebar-item-background--current)}.sidebar-tree .current>.reference:hover{background:var(--color-sidebar-item-background--hover)}.toctree-checkbox{display:none;position:absolute}.toctree-checkbox~ul{display:none}.toctree-checkbox~label .icon svg{transform:rotate(90deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label .icon svg{transform:rotate(-90deg)}.toc-title-container{padding:var(--toc-title-padding);padding-top:var(--toc-spacing-vertical)}.toc-title{color:var(--color-toc-title-text);font-size:var(--toc-title-font-size);padding-left:var(--toc-spacing-horizontal);text-transform:uppercase}.no-toc{display:none}.toc-tree-container{padding-bottom:var(--toc-spacing-vertical)}.toc-tree{border-left:1px solid var(--color-background-border);font-size:var(--toc-font-size);line-height:1.3;padding-left:calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))}.toc-tree>ul>li:first-child{padding-top:0}.toc-tree>ul>li:first-child>ul{padding-left:0}.toc-tree>ul>li:first-child>a{display:none}.toc-tree ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:var(--toc-item-spacing-horizontal)}.toc-tree li{padding-top:var(--toc-item-spacing-vertical)}.toc-tree li.scroll-current>.reference{color:var(--color-toc-item-text--active);font-weight:700}.toc-tree a.reference{color:var(--color-toc-item-text);overflow-wrap:anywhere;text-decoration:none}.toc-scroll{max-height:100vh;overflow-y:scroll}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here){background:rgba(255,0,0,.25);color:var(--color-problematic)}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here):before{content:"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch."}.text-align\:left>p{text-align:left}.text-align\:center>p{text-align:center}.text-align\:right>p{text-align:right}
+/*# sourceMappingURL=furo.css.map*/
\ No newline at end of file
diff --git a/SRV/docs/_build/html/_static/styles/furo.css.map b/SRV/docs/_build/html/_static/styles/furo.css.map
new file mode 100644
index 0000000..280b3fe
--- /dev/null
+++ b/SRV/docs/_build/html/_static/styles/furo.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"styles/furo.css","mappings":"AAAA,2EAA2E,CAU3E,KACE,gBAAiB,CACjB,6BACF,CASA,KACE,QACF,CAMA,KACE,aACF,CAOA,GACE,aAAc,CACd,cACF,CAUA,GACE,sBAAuB,CACvB,QAAS,CACT,gBACF,CAOA,IACE,+BAAiC,CACjC,aACF,CASA,EACE,4BACF,CAOA,YACE,kBAAmB,CACnB,yBAA0B,CAC1B,gCACF,CAMA,SAEE,kBACF,CAOA,cAGE,+BAAiC,CACjC,aACF,CAeA,QAEE,aAAc,CACd,aAAc,CACd,iBAAkB,CAClB,uBACF,CAEA,IACE,aACF,CAEA,IACE,SACF,CASA,IACE,iBACF,CAUA,sCAKE,mBAAoB,CACpB,cAAe,CACf,gBAAiB,CACjB,QACF,CAOA,aAEE,gBACF,CAOA,cAEE,mBACF,CAMA,gDAIE,yBACF,CAMA,wHAIE,iBAAkB,CAClB,SACF,CAMA,4GAIE,6BACF,CAMA,SACE,0BACF,CASA,OACE,qBAAsB,CACtB,aAAc,CACd,aAAc,CACd,cAAe,CACf,SAAU,CACV,kBACF,CAMA,SACE,uBACF,CAMA,SACE,aACF,CAOA,6BAEE,qBAAsB,CACtB,SACF,CAMA,kFAEE,WACF,CAOA,cACE,4BAA6B,CAC7B,mBACF,CAMA,yCACE,uBACF,CAOA,6BACE,yBAA0B,CAC1B,YACF,CASA,QACE,aACF,CAMA,QACE,iBACF,CAiBA,kBACE,YACF,CCvVA,aAcE,kEACE,uBAOF,WACE,iDAMF,kCACE,wBAEF,qCAEE,uBADA,uBACA,CAEF,SACE,wBAtBA,CCpBJ,iBAGE,qBAEA,sBACA,0BAFA,oBAHA,4BACA,oBAKA,6BAIA,2CAFA,mBACA,sCAFA,4BAGA,CAEF,gBACE,aCPF,KCCE,mHAGA,wGAGA,wCAAyC,CAEzC,wBAAyB,CACzB,wBAAyB,CACzB,4BAA6B,CAC7B,yBAA0B,CAC1B,2BAA4B,CAG5B,sDAAuD,CACvD,gDAAiD,CACjD,wDAAyD,CAGzD,0CAA2C,CAC3C,gDAAiD,CACjD,gDAAiD,CAKjD,gCAAiC,CACjC,sCAAuC,CAGvC,2CAA4C,CAG5C,uCAAwC,CCnCxC,+FAIA,uBAAwB,CAGxB,iCAAkC,CAClC,kCAAmC,CAEnC,+BAAgC,CAChC,sCAAuC,CACvC,sCAAuC,CACvC,qGAIA,mDAAoD,CAEpD,mCAAoC,CACpC,8CAA+C,CAC/C,gDAAiD,CACjD,kCAAmC,CACnC,6DAA8D,CAG9D,6BAA8B,CAC9B,6BAA8B,CAC9B,+BAAgC,CAChC,kCAAmC,CACnC,kCAAmC,CCRjC,+jBCaA,iqCAZF,iaCXA,8KAOA,4SAWA,4SAUA,0CACA,gEAGA,0CAGA,gEAGA,yCACA,+DAIA,4CACA,kEAGA,wCAUA,8DACA,uCAGA,4DACA,sCACA,2DAGA,4CACA,kEACA,uCAGA,6DACA,2GAGA,sHAEA,yFAEA,+CACA,+EAGA,4MAOA,gCACA,sHAIA,kCACA,uEACA,gEACA,4DACA,kEAGA,2DACA,sDACA,0CACA,8CACA,wGAGA,0BACA,iCAGA,+DACA,+BACA,sCACA,+DAEA,kGACA,oCACA,yDACA,sCL3HF,kCAEA,sDAIA,0CKyHE,kEAIA,oDACA,sDAGA,oCACA,oEAEA,0DACA,qDAIA,oDACA,6DAIA,iEAIA,2DAIA,2DAGA,4DACA,gEAIA,gEAEA,gFAEA,oNASA,qDLtKE,gFAGE,4DAIF,oEKgHF,yEAEA,6DAGA,0DAEA,uDACA,qDACA,wDAIA,6DAIA,yDACA,2DAIA,uCAGA,wCACA,sDAGA,+CAGA,6DAEA,iDACA,+DAEA,wDAEA,sEAMA,0DACA,sBACA,mEL5JI,wEAEA,iCACE,+BAMN,wEAGA,iCACE,kFAEA,uEAIF,gEACE,8BAGF,qEMzDA,sCAKA,wFAKA,iCAIA,0BAWA,iCACA,4BACA,mCAGA,+BAEA,sCACA,4BAEA,mCAEA,sCAKA,sDAIA,gCAEA,gEAQF,wCAME,sBACA,kCAKA,uBAEA,gEAIA,2BAIA,mCAEA,qCACA,iCAGE,+BACA,wEAEE,iCACA,kFAGF,6BACA,0CACF,kCAEE,8BACE,8BACA,qEAEE,sCACA,wFClFN,iCAGF,2DACE,4BACA,oCAKF,8BAGE,sCACA,+DAIA,sCAEA,sDAGA,gCACA,gEAGA,+CAEA,sBACE,yCAGF,uBACA,sEAIA,aAEA,mCAIA,kEACA,aACA,oEACA,YAIA,EAQE,4HAGA,gDACE,mBACA,wCAON,wCAGE,0DACA,mBAKA,mBACA,CANA,uCAKA,iBALA,iBAWA,mBAGF,mBACE,mDAIF,+BAEE,CAEA,yBAFA,kBAMA,CAJA,GACA,aAGA,mBAEF,wBAEE,iBACA,iBAEA,OACA,aAGF,CAHE,WAGF,GAEE,oBAEA,CAJF,gBAIE,aAEA,+CAKA,UANA,WACA,cADA,SAMA,WACA,iBAEE,GAMF,wBANE,yBAMF,kDACA,WAEA,gCACA,2DAGA,iBACE,uCAEJ,kEAIE,uCAGA,yDACE,cACA,+DAEA,yDAEE,mEAMJ,kEAMA,uBACA,kBAEA,uBACA,kDAKA,0DAIA,CALA,oBAKA,WACA,WAQA,4BAFF,0CAEE,CARA,qCAsBA,CAdA,iBAEA,kBACE,aADF,4BACE,WAMF,2BAGF,qCAEE,CAXE,UAWF,+BAGA,uBAEA,SAEA,0CAIE,CANF,qCAEA,CAIE,2DACE,gBAIN,+CAIA,CAEA,kDAKE,CAPF,8BAEA,CAOE,YACA,CAjBI,2BAGN,CAHM,WAcJ,UAGA,CAEA,2GAIF,iCAGE,8BAIA,qBACA,oBACF,uBAOI,0CAIA,CATF,6DAKE,CALF,sBASE,qCAKF,CACE,cACA,CAFF,sBAEE,CACA,+BAEA,qBAEE,WAKN,aACE,sCAGA,mBAEA,6BAMA,kCACA,CAJA,sBACA,aAEA,CAJA,eACA,MAIA,2FAEA,UAGA,YACA,sBACE,8BAEA,CALF,aACA,WAIE,OACA,oBAEF,uBACE,WAEF,YAFE,UAEF,eAgBA,kBACE,CAhBA,qDAQF,qCAGF,CAGI,YACF,CAJF,2BAGI,CAEA,eACA,qBAGA,mEAEA,qBACA,8BAIA,kBADF,kBACE,yBAEJ,oCAGI,qDAIJ,+BAGI,oCAEA,+CAQF,4CACE,yBACF,2BAOE,sBACA,CAHA,WACA,CAFF,cACE,CAJA,YAGF,CAEE,SAEA,mBAGA,kDAEE,CAJF,cAEA,cAEE,sBAEA,mBADA,YACA,uBACA,mDACE,CADF,YACE,iDAEA,uCAEN,+DAOE,mBADF,sBACE,mBAGF,aACE,sCAIA,aADF,WACE,CAKF,SACE,CAHJ,kBAEE,CAJE,gBAEJ,CAHI,iBAMA,yFAKA,aACA,eACA,cCxaJ,iBAEE,aADA,iBACA,6BAEA,kCAEA,SACA,UAIA,gCACA,CALA,SAEA,SAEA,CAJA,wEAEA,CAFA,OAKA,CAGA,mDACE,iBAGF,gCACE,CADF,UACE,aAEJ,iCAEE,CAFF,UAEE,wCAEA,WACA,WADA,UACA,CACA,4CAGA,MACA,CADA,KACA,wCACA,UAGA,CAJA,UAIA,6DAUA,0CACE,CAFF,mBAEE,wEACA,CAVA,YACA,CAMF,mBAJE,OAOA,gBAJJ,gCACE,CANE,cACA,CAHA,oBACA,CAGA,QAGJ,CAII,0BACA,CADA,UACA,wCAEJ,kBACE,0DACA,gCACE,kBACA,CADA,YACA,oEACA,2CAMF,mDAII,CALN,YACE,CANE,cAKJ,CACE,iBAII,kEACA,yCACE,kDACA,yDACE,+CACA,uBANN,CAMM,+BANN,uCACE,qDACA,4BAEE,mBADA,0CACA,CADA,qBACA,0DACE,wCACA,sGALJ,oCACA,sBACE,kBAFF,UAEE,2CACA,wFACE,cACA,kEANN,uBACE,iDACA,CADA,UACA,0DACE,wDAEE,iEACA,qEANN,sCACE,CAGE,iBAHF,gBAGE,qBACE,CAJJ,uBACA,gDACE,wDACA,6DAHF,2CACA,CADA,gBACA,eACE,CAGE,sBANN,8BACE,CAII,iBAFF,4DACA,WACE,YADF,uCACE,6EACA,2BANN,8CACE,kDACA,0CACE,8BACA,yFACE,sBACA,sFALJ,mEACA,sBACE,kEACA,6EACE,uCACA,kEALJ,qGAEE,kEACA,6EACE,uCACA,kEALJ,8CACA,uDACE,sEACA,2EACE,sCACA,iEALJ,mGACA,qCACE,oDACA,0DACE,6GACA,gDAGR,yDCvEA,sEACE,CACA,6GACE,gEACF,iGAIF,wFACE,qDAGA,mGAEE,2CAEF,4FACE,gCACF,wGACE,8DAEE,6FAIA,iJAKN,6GACE,gDAKF,yDACA,qCAGA,6BACA,kBACA,qDAKA,oCAEA,+DAGA,2CAGE,oDAIA,oEAEE,qBAGJ,wDAEE,uCAEF,kEAGA,8CAEA,uDAIF,gEAIE,6BACA,gEAIA,+CACE,0EAIF,sDAEE,+DAGF,sCACA,8BACE,oCAEJ,wBACE,4FAEE,gBAEJ,yGAGI,kBAGJ,CCnHE,2MCFF,oBAGE,wGAKA,iCACE,CADF,wBACE,8GAQA,mBCjBJ,2GAIE,mBACA,6HAMA,YACE,mIAYF,eACA,CAHF,YAGE,4FAGE,8BAKF,uBAkBE,sCACA,CADA,qBAbA,wCAIA,CALF,8BACE,CADF,gBAKE,wCACA,CAOA,kDACA,CACA,kCAKF,6BAGA,4CACE,kDACA,eAGF,cACE,aACA,iBACA,yBACA,8BACA,WAGJ,2BACE,cAGA,+BACA,CAHA,eAGA,wCACA,YACA,iBACA,uEAGA,0BACA,2CAEA,8EAGI,qBACA,CAFF,kBAEE,4DAMJ,mCACE,4BAGA,oBAGF,4CACE,qCACA,8BACA,gBACA,+CAEA,iCAEF,iCACE,oBACA,4CACA,qCAGF,8BAEE,+BAEA,WAEA,8BACE,oBACA,CADA,gBACA,yBAKF,gBADF,YACE,CACA,iBACA,qDAEA,mDCvIJ,2FAMA,iCACE,CACA,eAEA,CAFA,mBADA,wBAIA,8BACA,gBADA,YACA,0BAEE,8CAGA,wDAIE,gFAGE,iBAEN,wCAKF,+CACE,CACA,oDAEF,kDAIE,YAEF,CAHE,YAGF,CCpCE,mFAFA,QACA,UAIA,CAHA,IAGA,gDAGE,eACA,iEAGF,wBAEE,mBAMA,6CAEF,CAJE,mBACA,CAGF,kCAGE,CARF,kBACE,CAHA,eAUA,YACA,mBACA,CAFA,UAEA,wCC/BJ,mBACE,CDkCE,wBACA,sBCpCJ,iBACE,mDACA,2CACA,sBAGA,qBCDA,6CAIE,CATJ,uBAKE,CDGE,oBACF,yDAEE,CCDE,2CAGF,CAJA,kCACE,CDJJ,aAKE,eCXJ,CDME,uBCOE,gCACE,YAEF,2CAEE,wBACA,0BAIF,iBAEA,cADF,UACE,uBAEA,iCAEA,wCAEA,6CAMA,CAYF,gCATI,4BASJ,CAZE,mCAEE,iCAUJ,4BAGE,4DADA,+BACA,CAHF,qBAGE,sCACE,OAEF,iBAHA,SAGA,iHACE,2DAKF,CANA,8EAMA,uSAEE,kBAEF,+FACE,yCCjEJ,WACA,yBAGA,uBACA,gBAEA,uCAIA,CAJA,iCAIA,uCAGA,UACE,gBACA,qBAEA,0CClBJ,gBACE,KAGF,qBACE,YAGF,CAHE,cAGF,gCAEE,mBACA,iEAEA,oCACA,wCAEA,sBACA,WAEA,CAFA,YAEA,8EAEA,mCAFA,iBAEA,6BAIA,wEAKA,sDAIE,CARF,mDAIA,CAIE,cAEF,8CAIA,oBAFE,iBAEF,8CAGE,eAEF,CAFE,YAEF,OAEE,kBAGJ,CAJI,eACA,CAFF,mBAKF,yCCjDE,oBACA,CAFA,iBAEA,uCAKE,iBACA,qCAGA,mBCZJ,CDWI,gBCXJ,6BAEE,eACA,sBAGA,eAEA,sBACA,oDACA,iGAMA,gBAFE,YAEF,8FAME,iJCnBF,YACA,gNAWE,gDAEF,iSAaE,kBACE,gHAKF,oCACE,eACF,CADE,UACF,8CACE,gDACF,wCACE,oBCtCJ,oBAEF,6BACE,QACE,kDAGF,yBACE,kDAmBA,kDAEF,CAhBA,+CAaA,CAbA,oBAaA,0FACE,CADF,gGAfF,cACE,gBACA,CAaA,0BAGA,mQACE,gBAGF,oMACE,iBACA,CAFF,eACE,CADF,gBAEE,aAGJ,iCAEE,CAFF,wCAEE,wBAUE,+VAIE,uEAHA,2BAGA,wXAKJ,iDAGF,CARM,+CACE,iDAIN,CALI,gBAQN,mHACE,gBAGF,2DACE,0EAOA,0EAGF,gBAEE,6DCjFA,kDACA,gCACA,qDAGA,qBACA,qDCDA,cACA,eAEA,yBAGF,sBAEE,iBACA,sNAWA,iBACE,kBACA,wRAgBA,kBAEA,iOAgBA,uCACE,uEAEA,kBAEF,qUAuBE,iDAIJ,CACA,geCzFF,4BAEE,CAQA,6JACA,iDAIA,sEAGA,mDAOF,iDAGE,4DAIA,8CACA,qDAEE,eAFF,cAEE,oBAEF,uBAFE,kCAGA,eACA,iBACA,mBAIA,mDACA,CAHA,uCAEA,CAJA,0CACA,CAIA,gBAJA,gBACA,oBADA,gBAIA,wBAEJ,gBAGE,6BACA,YAHA,iBAGA,gCACA,iEAEA,6CACA,sDACA,0BADA,wBACA,0BACA,oIAIA,mBAFA,YAEA,qBACA,0CAIE,uBAEF,CAHA,yBACE,CAEF,iDACE,mFAKJ,oCACE,CANE,aAKJ,CACE,qEAIA,YAFA,WAEA,CAHA,aACA,CAEA,gBACE,4BACA,sBADA,aACA,gCAMF,oCACA,yDACA,2CAEA,qBAGE,kBAEA,CACA,mCAIF,CARE,YACA,CAOF,iCAEE,CAPA,oBACA,CAQA,oBACE,uDAEJ,sDAGA,CAHA,cAGA,0BACE,oDAIA,oCACA,4BACA,sBAGA,cAEA,oFAGA,sBAEA,yDACE,CAIF,iBAJE,wBAIF,6CAHE,6CAKA,eACA,aACA,CADA,cACA,yCAGJ,kBACE,CAKA,iDAEA,CARF,aACE,4CAGA,kBAIA,wEAGA,wDAGA,kCAOA,iDAGA,CAPF,WAEE,sCAEA,CAJF,2CACE,CAMA,qCACA,+BARF,kBACE,qCAOA,iBAsBA,sBACE,CAvBF,WAKA,CACE,0DAIF,CALA,uDACE,CANF,sBAqBA,4CACA,CALA,gRAIA,YAEE,6CAEN,mCAEE,+CASA,6EAIA,4BChNA,SDmNA,qFCnNA,gDACA,sCAGA,qCACA,sDACA,CAKA,kDAGA,CARA,0CAQA,kBAGA,YACA,sBACA,iBAFA,gBADF,YACE,CAHA,SAKA,kBAEA,SAFA,iBAEA,uEAGA,CAEE,6CAFF,oCAgBI,CAdF,yBACE,qBACF,CAGF,oBACE,CAIF,WACE,CALA,2CAGA,uBACF,CACE,mFAGE,CALF,qBAEA,UAGE,gCAIF,sDAEA,CALE,oCAKF,yCC7CJ,oCACE,CD+CA,yXAQE,sCCrDJ,wCAGA,oCACE","sources":["webpack:///./node_modules/normalize.css/normalize.css","webpack:///./src/furo/assets/styles/base/_print.sass","webpack:///./src/furo/assets/styles/base/_screen-readers.sass","webpack:///./src/furo/assets/styles/base/_theme.sass","webpack:///./src/furo/assets/styles/variables/_fonts.scss","webpack:///./src/furo/assets/styles/variables/_spacing.scss","webpack:///./src/furo/assets/styles/variables/_icons.scss","webpack:///./src/furo/assets/styles/variables/_admonitions.scss","webpack:///./src/furo/assets/styles/variables/_colors.scss","webpack:///./src/furo/assets/styles/base/_typography.sass","webpack:///./src/furo/assets/styles/_scaffold.sass","webpack:///./src/furo/assets/styles/content/_admonitions.sass","webpack:///./src/furo/assets/styles/content/_api.sass","webpack:///./src/furo/assets/styles/content/_blocks.sass","webpack:///./src/furo/assets/styles/content/_captions.sass","webpack:///./src/furo/assets/styles/content/_code.sass","webpack:///./src/furo/assets/styles/content/_footnotes.sass","webpack:///./src/furo/assets/styles/content/_images.sass","webpack:///./src/furo/assets/styles/content/_indexes.sass","webpack:///./src/furo/assets/styles/content/_lists.sass","webpack:///./src/furo/assets/styles/content/_math.sass","webpack:///./src/furo/assets/styles/content/_misc.sass","webpack:///./src/furo/assets/styles/content/_rubrics.sass","webpack:///./src/furo/assets/styles/content/_sidebar.sass","webpack:///./src/furo/assets/styles/content/_tables.sass","webpack:///./src/furo/assets/styles/content/_target.sass","webpack:///./src/furo/assets/styles/content/_gui-labels.sass","webpack:///./src/furo/assets/styles/components/_footer.sass","webpack:///./src/furo/assets/styles/components/_sidebar.sass","webpack:///./src/furo/assets/styles/components/_table_of_contents.sass","webpack:///./src/furo/assets/styles/_shame.sass"],"sourcesContent":["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n","// This file contains styles for managing print media.\n\n////////////////////////////////////////////////////////////////////////////////\n// Hide elements not relevant to print media.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Hide icon container.\n .content-icon-container\n display: none !important\n\n // Hide showing header links if hovering over when printing.\n .headerlink\n display: none !important\n\n // Hide mobile header.\n .mobile-header\n display: none !important\n\n // Hide navigation links.\n .related-pages\n display: none !important\n\n////////////////////////////////////////////////////////////////////////////////\n// Tweaks related to decolorization.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Apply a border around code which no longer have a color background.\n .highlight\n border: 0.1pt solid var(--color-foreground-border)\n\n////////////////////////////////////////////////////////////////////////////////\n// Avoid page break in some relevant cases.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n ul, ol, dl, a, table, pre, blockquote, p\n page-break-inside: avoid\n\n h1, h2, h3, h4, h5, h6, img, figure, caption\n page-break-inside: avoid\n page-break-after: avoid\n\n ul, ol, dl\n page-break-before: avoid\n",".visually-hidden\n position: absolute !important\n width: 1px !important\n height: 1px !important\n padding: 0 !important\n margin: -1px !important\n overflow: hidden !important\n clip: rect(0,0,0,0) !important\n white-space: nowrap !important\n border: 0 !important\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n:-moz-focusring\n outline: auto\n","// This file serves as the \"skeleton\" of the theming logic.\n//\n// This contains the bulk of the logic for handling dark mode, color scheme\n// toggling and the handling of color-scheme-specific hiding of elements.\n\n@use \"../variables\" as *\n\nbody\n @include fonts\n @include spacing\n @include icons\n @include admonitions\n @include default-admonition(#651fff, \"abstract\")\n @include default-topic(#14B8A6, \"pencil\")\n\n @include colors\n\n.only-light\n display: block !important\nhtml body .only-dark\n display: none !important\n\n// Ignore dark-mode hints if print media.\n@media not print\n // Enable dark-mode, if requested.\n body[data-theme=\"dark\"]\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n // Enable dark mode, unless explicitly told to avoid.\n @media (prefers-color-scheme: dark)\n body:not([data-theme=\"light\"])\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n//\n// Theme toggle presentation\n//\nbody[data-theme=\"auto\"]\n .theme-toggle svg.theme-icon-when-auto-light\n display: block\n\n @media (prefers-color-scheme: dark)\n .theme-toggle svg.theme-icon-when-auto-dark\n display: block\n .theme-toggle svg.theme-icon-when-auto-light\n display: none\n\nbody[data-theme=\"dark\"]\n .theme-toggle svg.theme-icon-when-dark\n display: block\n\nbody[data-theme=\"light\"]\n .theme-toggle svg.theme-icon-when-light\n display: block\n","// Fonts used by this theme.\n//\n// There are basically two things here -- using the system font stack and\n// defining sizes for various elements in %ages. We could have also used `em`\n// but %age is easier to reason about for me.\n\n@mixin fonts {\n // These are adapted from https://systemfontstack.com/\n --font-stack:\n -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,\n Apple Color Emoji, Segoe UI Emoji;\n --font-stack--monospace:\n \"SFMono-Regular\", Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,\n monospace;\n --font-stack--headings: var(--font-stack);\n\n --font-size--normal: 100%;\n --font-size--small: 87.5%;\n --font-size--small--2: 81.25%;\n --font-size--small--3: 75%;\n --font-size--small--4: 62.5%;\n\n // Sidebar\n --sidebar-caption-font-size: var(--font-size--small--2);\n --sidebar-item-font-size: var(--font-size--small);\n --sidebar-search-input-font-size: var(--font-size--small);\n\n // Table of Contents\n --toc-font-size: var(--font-size--small--3);\n --toc-font-size--mobile: var(--font-size--normal);\n --toc-title-font-size: var(--font-size--small--4);\n\n // Admonitions\n //\n // These aren't defined in terms of %ages, since nesting these is permitted.\n --admonition-font-size: 0.8125rem;\n --admonition-title-font-size: 0.8125rem;\n\n // Code\n --code-font-size: var(--font-size--small--2);\n\n // API\n --api-font-size: var(--font-size--small);\n}\n","// Spacing for various elements on the page\n//\n// If the user wants to tweak things in a certain way, they are permitted to.\n// They also have to deal with the consequences though!\n\n@mixin spacing {\n // Header!\n --header-height: calc(\n var(--sidebar-item-line-height) + 4 *\n #{var(--sidebar-item-spacing-vertical)}\n );\n --header-padding: 0.5rem;\n\n // Sidebar\n --sidebar-tree-space-above: 1.5rem;\n --sidebar-caption-space-above: 1rem;\n\n --sidebar-item-line-height: 1rem;\n --sidebar-item-spacing-vertical: 0.5rem;\n --sidebar-item-spacing-horizontal: 1rem;\n --sidebar-item-height: calc(\n var(--sidebar-item-line-height) + 2 *#{var(--sidebar-item-spacing-vertical)}\n );\n\n --sidebar-expander-width: var(--sidebar-item-height); // be square\n\n --sidebar-search-space-above: 0.5rem;\n --sidebar-search-input-spacing-vertical: 0.5rem;\n --sidebar-search-input-spacing-horizontal: 0.5rem;\n --sidebar-search-input-height: 1rem;\n --sidebar-search-icon-size: var(--sidebar-search-input-height);\n\n // Table of Contents\n --toc-title-padding: 0.25rem 0;\n --toc-spacing-vertical: 1.5rem;\n --toc-spacing-horizontal: 1.5rem;\n --toc-item-spacing-vertical: 0.4rem;\n --toc-item-spacing-horizontal: 1rem;\n}\n","// Expose theme icons as CSS variables.\n\n$icons: (\n // Adapted from tabler-icons\n // url: https://tablericons.com/\n \"search\":\n url('data:image/svg+xml;charset=utf-8, '),\n // Factored out from mkdocs-material on 24-Aug-2020.\n // url: https://squidfunk.github.io/mkdocs-material/reference/admonitions/\n \"pencil\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"abstract\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"info\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"flame\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"question\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"warning\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"failure\":\n url('data:image/svg+xml;charset=utf-8, '),\n \"spark\":\n url('data:image/svg+xml;charset=utf-8, ')\n);\n\n@mixin icons {\n @each $name, $glyph in $icons {\n --icon-#{$name}: #{$glyph};\n }\n}\n","@use \"sass:list\";\n// Admonitions\n\n// Structure of these is:\n// admonition-class: color \"icon-name\";\n//\n// The colors are translated into CSS variables below. The icons are\n// used directly in the main declarations to set the `mask-image` in\n// the title.\n\n// prettier-ignore\n$admonitions: (\n // Each of these has an reST directives for it.\n \"caution\": #ff9100 \"spark\",\n \"warning\": #ff9100 \"warning\",\n \"danger\": #ff5252 \"spark\",\n \"attention\": #ff5252 \"warning\",\n \"error\": #ff5252 \"failure\",\n \"hint\": #00c852 \"question\",\n \"tip\": #00c852 \"info\",\n \"important\": #00bfa5 \"flame\",\n \"note\": #00b0ff \"pencil\",\n \"seealso\": #448aff \"info\",\n \"admonition-todo\": #808080 \"pencil\"\n);\n\n@mixin default-admonition($color, $icon-name) {\n --color-admonition-title: #{$color};\n --color-admonition-title-background: #{rgba($color, 0.2)};\n\n --icon-admonition-default: var(--icon-#{$icon-name});\n}\n\n@mixin default-topic($color, $icon-name) {\n --color-topic-title: #{$color};\n --color-topic-title-background: #{rgba($color, 0.2)};\n\n --icon-topic-default: var(--icon-#{$icon-name});\n}\n\n@mixin admonitions {\n @each $name, $values in $admonitions {\n --color-admonition-title--#{$name}: #{list.nth($values, 1)};\n --color-admonition-title-background--#{$name}: #{rgba(\n list.nth($values, 1),\n 0.2\n )};\n }\n}\n","// Colors used throughout this theme.\n//\n// The aim is to give the user more control. Thus, instead of hard-coding colors\n// in various parts of the stylesheet, the approach taken is to define all\n// colors as CSS variables and reusing them in all the places.\n//\n// `colors-dark` depends on `colors` being included at a lower specificity.\n\n@mixin colors {\n --color-problematic: #b30000;\n\n // Base Colors\n --color-foreground-primary: black; // for main text and headings\n --color-foreground-secondary: #5a5c63; // for secondary text\n --color-foreground-muted: #6b6f76; // for muted text\n --color-foreground-border: #878787; // for content borders\n\n --color-background-primary: white; // for content\n --color-background-secondary: #f8f9fb; // for navigation + ToC\n --color-background-hover: #efeff4ff; // for navigation-item hover\n --color-background-hover--transparent: #efeff400;\n --color-background-border: #eeebee; // for UI borders\n --color-background-item: #ccc; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #0a4bff;\n --color-brand-content: #2757dd;\n --color-brand-visited: #872ee0;\n\n // API documentation\n --color-api-background: var(--color-background-hover--transparent);\n --color-api-background-hover: var(--color-background-hover);\n --color-api-overall: var(--color-foreground-secondary);\n --color-api-name: var(--color-problematic);\n --color-api-pre-name: var(--color-problematic);\n --color-api-paren: var(--color-foreground-secondary);\n --color-api-keyword: var(--color-foreground-primary);\n\n --color-api-added: #21632c;\n --color-api-added-border: #38a84d;\n --color-api-changed: #046172;\n --color-api-changed-border: #06a1bc;\n --color-api-deprecated: #605706;\n --color-api-deprecated-border: #f0d90f;\n --color-api-removed: #b30000;\n --color-api-removed-border: #ff5c5c;\n\n --color-highlight-on-target: #ffffcc;\n\n // Inline code background\n --color-inline-code-background: var(--color-background-secondary);\n\n // Highlighted text (search)\n --color-highlighted-background: #ddeeff;\n --color-highlighted-text: var(--color-foreground-primary);\n\n // GUI Labels\n --color-guilabel-background: #ddeeff80;\n --color-guilabel-border: #bedaf580;\n --color-guilabel-text: var(--color-foreground-primary);\n\n // Admonitions!\n --color-admonition-background: transparent;\n\n //////////////////////////////////////////////////////////////////////////////\n // Everything below this should be one of:\n // - var(...)\n // - *-gradient(...)\n // - special literal values (eg: transparent, none)\n //////////////////////////////////////////////////////////////////////////////\n\n // Tables\n --color-table-header-background: var(--color-background-secondary);\n --color-table-border: var(--color-background-border);\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: transparent;\n --color-card-marginals-background: var(--color-background-secondary);\n\n // Header\n --color-header-background: var(--color-background-primary);\n --color-header-border: var(--color-background-border);\n --color-header-text: var(--color-foreground-primary);\n\n // Sidebar (left)\n --color-sidebar-background: var(--color-background-secondary);\n --color-sidebar-background-border: var(--color-background-border);\n\n --color-sidebar-brand-text: var(--color-foreground-primary);\n --color-sidebar-caption-text: var(--color-foreground-muted);\n --color-sidebar-link-text: var(--color-foreground-secondary);\n --color-sidebar-link-text--top-level: var(--color-brand-primary);\n\n --color-sidebar-item-background: var(--color-sidebar-background);\n --color-sidebar-item-background--current: var(\n --color-sidebar-item-background\n );\n --color-sidebar-item-background--hover: linear-gradient(\n 90deg,\n var(--color-background-hover--transparent) 0%,\n var(--color-background-hover) var(--sidebar-item-spacing-horizontal),\n var(--color-background-hover) 100%\n );\n\n --color-sidebar-item-expander-background: transparent;\n --color-sidebar-item-expander-background--hover: var(\n --color-background-hover\n );\n\n --color-sidebar-search-text: var(--color-foreground-primary);\n --color-sidebar-search-background: var(--color-background-secondary);\n --color-sidebar-search-background--focus: var(--color-background-primary);\n --color-sidebar-search-border: var(--color-background-border);\n --color-sidebar-search-icon: var(--color-foreground-muted);\n\n // Table of Contents (right)\n --color-toc-background: var(--color-background-primary);\n --color-toc-title-text: var(--color-foreground-muted);\n --color-toc-item-text: var(--color-foreground-secondary);\n --color-toc-item-text--hover: var(--color-foreground-primary);\n --color-toc-item-text--active: var(--color-brand-primary);\n\n // Actual page contents\n --color-content-foreground: var(--color-foreground-primary);\n --color-content-background: transparent;\n\n // Links\n --color-link: var(--color-brand-content);\n --color-link-underline: var(--color-background-border);\n --color-link--hover: var(--color-brand-content);\n --color-link-underline--hover: var(--color-foreground-border);\n\n --color-link--visited: var(--color-brand-visited);\n --color-link-underline--visited: var(--color-background-border);\n --color-link--visited--hover: var(--color-brand-visited);\n --color-link-underline--visited--hover: var(--color-foreground-border);\n}\n\n@mixin colors-dark {\n --color-problematic: #ee5151;\n\n // Base Colors\n --color-foreground-primary: #cfd0d0; // for main text and headings\n --color-foreground-secondary: #9ca0a5; // for secondary text\n --color-foreground-muted: #81868d; // for muted text\n --color-foreground-border: #666666; // for content borders\n\n --color-background-primary: #131416; // for content\n --color-background-secondary: #1a1c1e; // for navigation + ToC\n --color-background-hover: #1e2124ff; // for navigation-item hover\n --color-background-hover--transparent: #1e212400;\n --color-background-border: #303335; // for UI borders\n --color-background-item: #444; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #3d94ff;\n --color-brand-content: #5ca5ff;\n --color-brand-visited: #b27aeb;\n\n // Highlighted text (search)\n --color-highlighted-background: #083563;\n\n // GUI Labels\n --color-guilabel-background: #08356380;\n --color-guilabel-border: #13395f80;\n\n // API documentation\n --color-api-keyword: var(--color-foreground-secondary);\n --color-highlight-on-target: #333300;\n\n --color-api-added: #3db854;\n --color-api-added-border: #267334;\n --color-api-changed: #09b0ce;\n --color-api-changed-border: #056d80;\n --color-api-deprecated: #b1a10b;\n --color-api-deprecated-border: #6e6407;\n --color-api-removed: #ff7575;\n --color-api-removed-border: #b03b3b;\n\n // Admonitions\n --color-admonition-background: #18181a;\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: #18181a;\n --color-card-marginals-background: var(--color-background-hover);\n}\n","// This file contains the styling for making the content throughout the page,\n// including fonts, paragraphs, headings and spacing among these elements.\n\nbody\n font-family: var(--font-stack)\npre,\ncode,\nkbd,\nsamp\n font-family: var(--font-stack--monospace)\n\n// Make fonts look slightly nicer.\nbody\n -webkit-font-smoothing: antialiased\n -moz-osx-font-smoothing: grayscale\n\n// Line height from Bootstrap 4.1\narticle\n line-height: 1.5\n\n//\n// Headings\n//\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n line-height: 1.25\n font-family: var(--font-stack--headings)\n font-weight: bold\n\n border-radius: 0.5rem\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n margin-left: -0.5rem\n margin-right: -0.5rem\n padding-left: 0.5rem\n padding-right: 0.5rem\n\n + p\n margin-top: 0\n\nh1\n font-size: 2.5em\n margin-top: 1.75rem\n margin-bottom: 1rem\nh2\n font-size: 2em\n margin-top: 1.75rem\nh3\n font-size: 1.5em\nh4\n font-size: 1.25em\nh5\n font-size: 1.125em\nh6\n font-size: 1em\n\nsmall\n opacity: 75%\n font-size: 80%\n\n// Paragraph\np\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n\n// Horizontal rules\nhr.docutils\n height: 1px\n padding: 0\n margin: 2rem 0\n background-color: var(--color-background-border)\n border: 0\n\n.centered\n text-align: center\n\n// Links\na\n text-decoration: underline\n\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n &:visited\n color: var(--color-link--visited)\n text-decoration-color: var(--color-link-underline--visited)\n &:hover\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &.muted-link\n color: inherit\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &:visited\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n","// This file contains the styles for the overall layouting of the documentation\n// skeleton, including the responsive changes as well as sidebar toggles.\n//\n// This is implemented as a mobile-last design, which isn't ideal, but it is\n// reasonably good-enough and I got pretty tired by the time I'd finished this\n// to move the rules around to fix this. Shouldn't take more than 3-4 hours,\n// if you know what you're doing tho.\n\n// HACK: Not all browsers account for the scrollbar width in media queries.\n// This results in horizontal scrollbars in the breakpoint where we go\n// from displaying everything to hiding the ToC. We accomodate for this by\n// adding a bit of padding to the TOC drawer, disabling the horizontal\n// scrollbar and allowing the scrollbars to cover the padding.\n// https://www.456bereastreet.com/archive/201301/media_query_width_and_vertical_scrollbars/\n\n// HACK: Always having the scrollbar visible, prevents certain browsers from\n// causing the content to stutter horizontally between taller-than-viewport and\n// not-taller-than-viewport pages.\n@use \"variables\" as *\n\nhtml\n overflow-x: hidden\n overflow-y: scroll\n scroll-behavior: smooth\n\n.sidebar-scroll, .toc-scroll, article[role=main] *\n scrollbar-width: thin\n scrollbar-color: var(--color-foreground-border) transparent\n\n//\n// Overalls\n//\nhtml,\nbody\n height: 100%\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n.skip-to-content\n position: fixed\n padding: 1rem\n border-radius: 1rem\n left: 0.25rem\n top: 0.25rem\n z-index: 40\n background: var(--color-background-primary)\n color: var(--color-foreground-primary)\n\n transform: translateY(-200%)\n transition: transform 300ms ease-in-out\n\n &:focus-within\n transform: translateY(0%)\n\narticle\n color: var(--color-content-foreground)\n background: var(--color-content-background)\n overflow-wrap: break-word\n\n.page\n display: flex\n // fill the viewport for pages with little content.\n min-height: 100%\n\n.mobile-header\n width: 100%\n height: var(--header-height)\n background-color: var(--color-header-background)\n color: var(--color-header-text)\n border-bottom: 1px solid var(--color-header-border)\n\n // Looks like sub-script/super-script have this, and we need this to\n // be \"on top\" of those.\n z-index: 10\n\n // We don't show the header on large screens.\n display: none\n\n // Add shadow when scrolled\n &.scrolled\n border-bottom: none\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2)\n\n .header-center\n a\n color: var(--color-header-text)\n text-decoration: none\n\n.main\n display: flex\n flex: 1\n\n// Sidebar (left) also covers the entire left portion of screen.\n.sidebar-drawer\n box-sizing: border-box\n\n border-right: 1px solid var(--color-sidebar-background-border)\n background: var(--color-sidebar-background)\n\n display: flex\n justify-content: flex-end\n // These next two lines took me two days to figure out.\n width: calc((100% - #{$full-width}) / 2 + #{$sidebar-width})\n min-width: $sidebar-width\n\n// Scroll-along sidebars\n.sidebar-container,\n.toc-drawer\n box-sizing: border-box\n width: $sidebar-width\n\n.toc-drawer\n background: var(--color-toc-background)\n // See HACK described on top of this document\n padding-right: 1rem\n\n.sidebar-sticky,\n.toc-sticky\n position: sticky\n top: 0\n height: min(100%, 100vh)\n height: 100vh\n\n display: flex\n flex-direction: column\n\n.sidebar-scroll,\n.toc-scroll\n flex-grow: 1\n flex-shrink: 1\n\n overflow: auto\n scroll-behavior: smooth\n\n// Central items.\n.content\n padding: 0 $content-padding\n width: $content-width\n\n display: flex\n flex-direction: column\n justify-content: space-between\n\n.icon\n display: inline-block\n height: 1rem\n width: 1rem\n svg\n width: 100%\n height: 100%\n\n//\n// Accommodate announcement banner\n//\n.announcement\n background-color: var(--color-announcement-background)\n color: var(--color-announcement-text)\n\n height: var(--header-height)\n display: flex\n align-items: center\n overflow-x: auto\n & + .page\n min-height: calc(100% - var(--header-height))\n\n.announcement-content\n box-sizing: border-box\n padding: 0.5rem\n min-width: 100%\n white-space: nowrap\n text-align: center\n\n a\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-announcement-text)\n\n &:hover\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-link--hover)\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for theme\n////////////////////////////////////////////////////////////////////////////////\n.no-js .theme-toggle-container // don't show theme toggle if there's no JS\n display: none\n\n.theme-toggle-container\n display: flex\n\n.theme-toggle\n display: flex\n cursor: pointer\n border: none\n padding: 0\n background: transparent\n\n.theme-toggle svg\n height: 1.25rem\n width: 1.25rem\n color: var(--color-foreground-primary)\n display: none\n\n.theme-toggle-header\n display: flex\n align-items: center\n justify-content: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for elements\n////////////////////////////////////////////////////////////////////////////////\n.toc-overlay-icon, .nav-overlay-icon\n display: none\n cursor: pointer\n\n .icon\n color: var(--color-foreground-secondary)\n height: 1.5rem\n width: 1.5rem\n\n.toc-header-icon, .nav-overlay-icon\n // for when we set display: flex\n justify-content: center\n align-items: center\n\n.toc-content-icon\n height: 1.5rem\n width: 1.5rem\n\n.content-icon-container\n float: right\n display: flex\n margin-top: 1.5rem\n margin-left: 1rem\n margin-bottom: 1rem\n gap: 0.5rem\n\n .edit-this-page, .view-this-page\n svg\n color: inherit\n height: 1.25rem\n width: 1.25rem\n\n.sidebar-toggle\n position: absolute\n display: none\n// \n.sidebar-toggle[name=\"__toc\"]\n left: 20px\n.sidebar-toggle:checked\n left: 40px\n// \n\n.overlay\n position: fixed\n top: 0\n width: 0\n height: 0\n\n transition: width 0ms, height 0ms, opacity 250ms ease-out\n\n opacity: 0\n background-color: rgba(0, 0, 0, 0.54)\n.sidebar-overlay\n z-index: 20\n.toc-overlay\n z-index: 40\n\n// Keep things on top and smooth.\n.sidebar-drawer\n z-index: 30\n transition: left 250ms ease-in-out\n.toc-drawer\n z-index: 50\n transition: right 250ms ease-in-out\n\n// Show the Sidebar\n#__navigation:checked\n & ~ .sidebar-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .sidebar-drawer\n top: 0\n left: 0\n // Show the toc sidebar\n#__toc:checked\n & ~ .toc-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .toc-drawer\n top: 0\n right: 0\n\n////////////////////////////////////////////////////////////////////////////////\n// Back to top\n////////////////////////////////////////////////////////////////////////////////\n.back-to-top\n text-decoration: none\n\n display: none\n position: fixed\n left: 0\n top: 1rem\n padding: 0.5rem\n padding-right: 0.75rem\n border-radius: 1rem\n font-size: 0.8125rem\n\n background: var(--color-background-primary)\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), #6b728080 0px 0px 1px 0px\n\n z-index: 10\n\n margin-left: 50%\n transform: translateX(-50%)\n svg\n height: 1rem\n width: 1rem\n fill: currentColor\n display: inline-block\n\n span\n margin-left: 0.25rem\n\n .show-back-to-top &\n display: flex\n align-items: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Responsive layouting\n////////////////////////////////////////////////////////////////////////////////\n// Make things a bit bigger on bigger screens.\n@media (min-width: $full-width + $sidebar-width)\n html\n font-size: 110%\n\n@media (max-width: $full-width)\n // Collapse \"toc\" into the icon.\n .toc-content-icon\n display: flex\n .toc-drawer\n position: fixed\n height: 100vh\n top: 0\n right: -$sidebar-width\n border-left: 1px solid var(--color-background-muted)\n .toc-tree\n border-left: none\n font-size: var(--toc-font-size--mobile)\n\n // Accomodate for a changed content width.\n .sidebar-drawer\n width: calc((100% - #{$full-width - $sidebar-width}) / 2 + #{$sidebar-width})\n\n@media (max-width: $content-padded-width + $sidebar-width)\n // Center the page\n .content\n margin-left: auto\n margin-right: auto\n padding: 0 $content-padding--small\n\n@media (max-width: $content-padded-width--small + $sidebar-width)\n // Collapse \"navigation\".\n .nav-overlay-icon\n display: flex\n .sidebar-drawer\n position: fixed\n height: 100vh\n width: $sidebar-width\n\n top: 0\n left: -$sidebar-width\n\n // Swap which icon is visible.\n .toc-header-icon, .theme-toggle-header\n display: flex\n .toc-content-icon, .theme-toggle-content\n display: none\n\n // Show the header.\n .mobile-header\n position: sticky\n top: 0\n display: flex\n justify-content: space-between\n align-items: center\n\n .header-left,\n .header-right\n display: flex\n height: var(--header-height)\n padding: 0 var(--header-padding)\n label\n height: 100%\n width: 100%\n user-select: none\n\n .nav-overlay-icon .icon,\n .theme-toggle svg\n height: 1.5rem\n width: 1.5rem\n\n // Add a scroll margin for the content\n :target\n scroll-margin-top: calc(var(--header-height) + 2.5rem)\n\n // Show back-to-top below the header\n .back-to-top\n top: calc(var(--header-height) + 0.5rem)\n\n // Accommodate for the header.\n .page\n flex-direction: column\n justify-content: center\n\n@media (max-width: $content-width + 2* $content-padding--small)\n // Content should respect window limits.\n .content\n width: 100%\n overflow-x: auto\n\n@media (max-width: $content-width)\n article[role=main] aside.sidebar\n float: none\n width: 100%\n margin: 1rem 0\n","@use \"sass:list\"\n@use \"../variables\" as *\n\n// The design here is strongly inspired by mkdocs-material.\n.admonition, .topic\n margin: 1rem auto\n padding: 0 0.5rem 0.5rem 0.5rem\n\n background: var(--color-admonition-background)\n\n border-radius: 0.2rem\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n font-size: var(--admonition-font-size)\n\n overflow: hidden\n page-break-inside: avoid\n\n // First element should have no margin, since the title has it.\n > :nth-child(2)\n margin-top: 0\n\n // Last item should have no margin, since we'll control that w/ padding\n > :last-child\n margin-bottom: 0\n\n.admonition p.admonition-title,\np.topic-title\n position: relative\n margin: 0 -0.5rem 0.5rem\n padding-left: 2rem\n padding-right: .5rem\n padding-top: .4rem\n padding-bottom: .4rem\n\n font-weight: 500\n font-size: var(--admonition-title-font-size)\n line-height: 1.3\n\n // Our fancy icon\n &::before\n content: \"\"\n position: absolute\n left: 0.5rem\n width: 1rem\n height: 1rem\n\n// Default styles\np.admonition-title\n background-color: var(--color-admonition-title-background)\n &::before\n background-color: var(--color-admonition-title)\n mask-image: var(--icon-admonition-default)\n mask-repeat: no-repeat\n\np.topic-title\n background-color: var(--color-topic-title-background)\n &::before\n background-color: var(--color-topic-title)\n mask-image: var(--icon-topic-default)\n mask-repeat: no-repeat\n\n//\n// Variants\n//\n.admonition\n border-left: 0.2rem solid var(--color-admonition-title)\n\n @each $type, $value in $admonitions\n &.#{$type}\n border-left-color: var(--color-admonition-title--#{$type})\n > .admonition-title\n background-color: var(--color-admonition-title-background--#{$type})\n &::before\n background-color: var(--color-admonition-title--#{$type})\n mask-image: var(--icon-#{list.nth($value, 2)})\n\n.admonition-todo > .admonition-title\n text-transform: uppercase\n","// This file stylizes the API documentation (stuff generated by autodoc). It's\n// deeply nested due to how autodoc structures the HTML without enough classes\n// to select the relevant items.\n\n// API docs!\ndl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)\n // Tweak the spacing of all the things!\n dd\n margin-left: 2rem\n > :first-child\n margin-top: 0.125rem\n > :last-child\n margin-bottom: 0.75rem\n\n // This is used for the arguments\n .field-list\n margin-bottom: 0.75rem\n\n // \"Headings\" (like \"Parameters\" and \"Return\")\n > dt\n text-transform: uppercase\n font-size: var(--font-size--small)\n\n dd:empty\n margin-bottom: 0.5rem\n dd > ul\n margin-left: -1.2rem\n > li\n > p:nth-child(2)\n margin-top: 0\n // When the last-empty-paragraph follows a paragraph, it doesn't need\n // to augument the existing spacing.\n > p + p:last-child:empty\n margin-top: 0\n margin-bottom: 0\n\n // Colorize the elements\n > dt\n color: var(--color-api-overall)\n\n.sig:not(.sig-inline)\n font-weight: bold\n\n font-size: var(--api-font-size)\n font-family: var(--font-stack--monospace)\n\n margin-left: -0.25rem\n margin-right: -0.25rem\n padding-top: 0.25rem\n padding-bottom: 0.25rem\n padding-right: 0.5rem\n\n // These are intentionally em, to properly match the font size.\n padding-left: 3em\n text-indent: -2.5em\n\n border-radius: 0.25rem\n\n background: var(--color-api-background)\n transition: background 100ms ease-out\n\n &:hover\n background: var(--color-api-background-hover)\n\n // adjust the size of the [source] link on the right.\n a.reference\n .viewcode-link\n font-weight: normal\n width: 4.25rem\n\nem.property\n font-style: normal\n &:first-child\n color: var(--color-api-keyword)\n.sig-name\n color: var(--color-api-name)\n.sig-prename\n font-weight: normal\n color: var(--color-api-pre-name)\n.sig-paren\n color: var(--color-api-paren)\n.sig-param\n font-style: normal\n\ndiv.versionadded,\ndiv.versionchanged,\ndiv.deprecated,\ndiv.versionremoved\n border-left: 0.1875rem solid\n border-radius: 0.125rem\n\n padding-left: 0.75rem\n\n p\n margin-top: 0.125rem\n margin-bottom: 0.125rem\n\ndiv.versionadded\n border-color: var(--color-api-added-border)\n .versionmodified\n color: var(--color-api-added)\n\ndiv.versionchanged\n border-color: var(--color-api-changed-border)\n .versionmodified\n color: var(--color-api-changed)\n\ndiv.deprecated\n border-color: var(--color-api-deprecated-border)\n .versionmodified\n color: var(--color-api-deprecated)\n\ndiv.versionremoved\n border-color: var(--color-api-removed-border)\n .versionmodified\n color: var(--color-api-removed)\n\n// Align the [docs] and [source] to the right.\n.viewcode-link, .viewcode-back\n float: right\n text-align: right\n",".line-block\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n .line-block\n margin-top: 0rem\n margin-bottom: 0rem\n padding-left: 1rem\n","// Captions\narticle p.caption,\ntable > caption,\n.code-block-caption\n font-size: var(--font-size--small)\n text-align: center\n\n// Caption above a TOCTree\n.toctree-wrapper.compound\n .caption, :not(.caption) > .caption-text\n font-size: var(--font-size--small)\n text-transform: uppercase\n\n text-align: initial\n margin-bottom: 0\n\n > ul\n margin-top: 0\n margin-bottom: 0\n","// Inline code\ncode.literal, .sig-inline\n background: var(--color-inline-code-background)\n border-radius: 0.2em\n // Make the font smaller, and use padding to recover.\n font-size: var(--font-size--small--2)\n padding: 0.1em 0.2em\n\n pre.literal-block &\n font-size: inherit\n padding: 0\n\n p &\n border: 1px solid var(--color-background-border)\n\n.sig-inline\n font-family: var(--font-stack--monospace)\n\n// Code and Literal Blocks\n$code-spacing-vertical: 0.625rem\n$code-spacing-horizontal: 0.875rem\n\n// Wraps every literal block + line numbers.\ndiv[class*=\" highlight-\"],\ndiv[class^=\"highlight-\"]\n margin: 1em 0\n display: flex\n\n .table-wrapper\n margin: 0\n padding: 0\n\npre\n margin: 0\n padding: 0\n overflow: auto\n\n // Needed to have more specificity than pygments' \"pre\" selector. :(\n article[role=\"main\"] .highlight &\n line-height: 1.5\n\n &.literal-block,\n .highlight &\n font-size: var(--code-font-size)\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n // Make it look like all the other blocks.\n &.literal-block\n margin-top: 1rem\n margin-bottom: 1rem\n\n border-radius: 0.2rem\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n\n// All code is always contained in this.\n.highlight\n width: 100%\n border-radius: 0.2rem\n\n // Make line numbers and prompts un-selectable.\n .gp, span.linenos\n user-select: none\n pointer-events: none\n\n // Expand the line-highlighting.\n .hll\n display: block\n margin-left: -$code-spacing-horizontal\n margin-right: -$code-spacing-horizontal\n padding-left: $code-spacing-horizontal\n padding-right: $code-spacing-horizontal\n\n/* Make code block captions be nicely integrated */\n.code-block-caption\n display: flex\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n border-radius: 0.25rem\n border-bottom-left-radius: 0\n border-bottom-right-radius: 0\n font-weight: 300\n border-bottom: 1px solid\n\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n border-color: var(--color-background-border)\n\n + div[class]\n margin-top: 0\n > .highlight\n border-top-left-radius: 0\n border-top-right-radius: 0\n\n// When `html_codeblock_linenos_style` is table.\n.highlighttable\n width: 100%\n display: block\n tbody\n display: block\n\n tr\n display: flex\n\n // Line numbers\n td.linenos\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n padding: $code-spacing-vertical $code-spacing-horizontal\n padding-right: 0\n border-top-left-radius: 0.2rem\n border-bottom-left-radius: 0.2rem\n\n .linenodiv\n padding-right: $code-spacing-horizontal\n font-size: var(--code-font-size)\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n\n // Actual code\n td.code\n padding: 0\n display: block\n flex: 1\n overflow: hidden\n\n .highlight\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n\n// When `html_codeblock_linenos_style` is inline.\n.highlight\n span.linenos\n display: inline-block\n padding-left: 0\n padding-right: $code-spacing-horizontal\n margin-right: $code-spacing-horizontal\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n","// Inline Footnote Reference\n.footnote-reference\n font-size: var(--font-size--small--4)\n vertical-align: super\n\n// Definition list, listing the content of each note.\n// docutils <= 0.17\ndl.footnote.brackets\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\n display: grid\n grid-template-columns: max-content auto\n dt\n margin: 0\n > .fn-backref\n margin-left: 0.25rem\n\n &:after\n content: \":\"\n\n .brackets\n &:before\n content: \"[\"\n &:after\n content: \"]\"\n\n dd\n margin: 0\n padding: 0 1rem\n\n// docutils >= 0.18\naside.footnote\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\naside.footnote > span,\ndiv.citation > span\n float: left\n font-weight: 500\n padding-right: 0.25rem\n\naside.footnote > *:not(span),\ndiv.citation > p\n margin-left: 2rem\n","//\n// Figures\n//\nimg\n box-sizing: border-box\n max-width: 100%\n height: auto\n\narticle\n figure, .figure\n border-radius: 0.2rem\n\n margin: 0\n :last-child\n margin-bottom: 0\n\n .align-left\n float: left\n clear: left\n margin: 0 1rem 1rem\n\n .align-right\n float: right\n clear: right\n margin: 0 1rem 1rem\n\n .align-default,\n .align-center\n display: block\n text-align: center\n margin-left: auto\n margin-right: auto\n\n // WELL, table needs to be stylised like a table.\n table.align-default\n display: table\n text-align: initial\n",".genindex-jumpbox, .domainindex-jumpbox\n border-top: 1px solid var(--color-background-border)\n border-bottom: 1px solid var(--color-background-border)\n padding: 0.25rem\n\n.genindex-section, .domainindex-section\n h2\n margin-top: 0.75rem\n margin-bottom: 0.5rem\n ul\n margin-top: 0\n margin-bottom: 0\n","ul,\nol\n padding-left: 1.2rem\n\n // Space lists out like paragraphs\n margin-top: 1rem\n margin-bottom: 1rem\n // reduce margins within li.\n li\n > p:first-child\n margin-top: 0.25rem\n margin-bottom: 0.25rem\n\n > p:last-child\n margin-top: 0.25rem\n\n > ul,\n > ol\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n\nol\n &.arabic\n list-style: decimal\n &.loweralpha\n list-style: lower-alpha\n &.upperalpha\n list-style: upper-alpha\n &.lowerroman\n list-style: lower-roman\n &.upperroman\n list-style: upper-roman\n\n// Don't space lists out when they're \"simple\" or in a `.. toctree::`\n.simple,\n.toctree-wrapper\n li\n > ul,\n > ol\n margin-top: 0\n margin-bottom: 0\n\n// Definition Lists\n.field-list,\n.option-list,\ndl:not([class]),\ndl.simple,\ndl.footnote,\ndl.glossary\n dt\n font-weight: 500\n margin-top: 0.25rem\n + dt\n margin-top: 0\n\n .classifier::before\n content: \":\"\n margin-left: 0.2rem\n margin-right: 0.2rem\n\n dd\n > p:first-child,\n ul\n margin-top: 0.125rem\n\n ul\n margin-bottom: 0.125rem\n",".math-wrapper\n width: 100%\n overflow-x: auto\n\ndiv.math\n position: relative\n text-align: center\n\n .headerlink,\n &:focus .headerlink\n display: none\n\n &:hover .headerlink\n display: inline-block\n\n span.eqno\n position: absolute\n right: 0.5rem\n top: 50%\n transform: translate(0, -50%)\n z-index: 1\n","// Abbreviations\nabbr[title]\n cursor: help\n\n// \"Problematic\" content, as identified by Sphinx\n.problematic\n color: var(--color-problematic)\n\n// Keyboard / Mouse \"instructions\"\nkbd:not(.compound)\n margin: 0 0.2rem\n padding: 0 0.2rem\n border-radius: 0.2rem\n border: 1px solid var(--color-foreground-border)\n color: var(--color-foreground-primary)\n vertical-align: text-bottom\n\n font-size: var(--font-size--small--3)\n display: inline-block\n\n box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.2), inset 0 0 0 0.125rem var(--color-background-primary)\n\n background-color: var(--color-background-secondary)\n\n// Blockquote\nblockquote\n border-left: 4px solid var(--color-background-border)\n background: var(--color-background-secondary)\n\n margin-left: 0\n margin-right: 0\n padding: 0.5rem 1rem\n\n .attribution\n font-weight: 600\n text-align: right\n\n &.pull-quote,\n &.highlights\n font-size: 1.25em\n\n &.epigraph,\n &.pull-quote\n border-left-width: 0\n border-radius: 0.5rem\n\n &.highlights\n border-left-width: 0\n background: transparent\n\n// Center align embedded-in-text images\np .reference img\n vertical-align: middle\n","p.rubric\n line-height: 1.25\n font-weight: bold\n font-size: 1.125em\n\n // For Numpy-style documentation that's got rubrics within it.\n // https://github.com/pradyunsg/furo/discussions/505\n dd &\n line-height: inherit\n font-weight: inherit\n\n font-size: var(--font-size--small)\n text-transform: uppercase\n","article .sidebar\n float: right\n clear: right\n width: 30%\n\n margin-left: 1rem\n margin-right: 0\n\n border-radius: 0.2rem\n background-color: var(--color-background-secondary)\n border: var(--color-background-border) 1px solid\n\n > *\n padding-left: 1rem\n padding-right: 1rem\n\n > ul, > ol // lists need additional padding, because bullets.\n padding-left: 2.2rem\n\n .sidebar-title\n margin: 0\n padding: 0.5rem 1rem\n border-bottom: var(--color-background-border) 1px solid\n\n font-weight: 500\n\n// TODO: subtitle\n// TODO: dedicated variables?\n","[role=main] .table-wrapper.container\n width: 100%\n overflow-x: auto\n margin-top: 1rem\n margin-bottom: 0.5rem\n padding: 0.2rem 0.2rem 0.75rem\n\ntable.docutils\n border-radius: 0.2rem\n border-spacing: 0\n border-collapse: collapse\n\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n th\n background: var(--color-table-header-background)\n\n td,\n th\n // Space things out properly\n padding: 0 0.25rem\n\n // Get the borders looking just-right.\n border-left: 1px solid var(--color-table-border)\n border-right: 1px solid var(--color-table-border)\n border-bottom: 1px solid var(--color-table-border)\n\n p\n margin: 0.25rem\n\n &:first-child\n border-left: none\n &:last-child\n border-right: none\n\n // MyST-parser tables set these classes for control of column alignment\n &.text-left\n text-align: left\n &.text-right\n text-align: right\n &.text-center\n text-align: center\n","@use \"../variables\" as *\n\n:target\n scroll-margin-top: 2.5rem\n\n@media (max-width: $full-width - $sidebar-width)\n :target\n scroll-margin-top: calc(2.5rem + var(--header-height))\n\n // When a heading is selected\n section > span:target\n scroll-margin-top: calc(2.8rem + var(--header-height))\n\n// Permalinks\n.headerlink\n font-weight: 100\n user-select: none\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\ndl dt,\np.caption,\nfigcaption p,\ntable > caption,\n.code-block-caption\n > .headerlink\n margin-left: 0.5rem\n visibility: hidden\n &:hover > .headerlink\n visibility: visible\n\n // Don't change to link-like, if someone adds the contents directive.\n > .toc-backref\n color: inherit\n text-decoration-line: none\n\n// Figure and table captions are special.\nfigure:hover > figcaption > p > .headerlink,\ntable:hover > caption > .headerlink\n visibility: visible\n\n:target >, // Regular section[id] style anchors\nspan:target ~ // Non-regular span[id] style \"extra\" anchors\n h1,\n h2,\n h3,\n h4,\n h5,\n h6\n &:nth-of-type(1)\n background-color: var(--color-highlight-on-target)\n // .headerlink\n // visibility: visible\n code.literal\n background-color: transparent\n\ntable:target > caption,\nfigure:target\n background-color: var(--color-highlight-on-target)\n\n// Inline page contents\n.this-will-duplicate-information-and-it-is-still-useful-here li :target\n background-color: var(--color-highlight-on-target)\n\n// Code block permalinks\n.literal-block-wrapper:target .code-block-caption\n background-color: var(--color-highlight-on-target)\n\n// When a definition list item is selected\n//\n// There isn't really an alternative to !important here, due to the\n// high-specificity of API documentation's selector.\ndt:target\n background-color: var(--color-highlight-on-target) !important\n\n// When a footnote reference is selected\n.footnote > dt:target + dd,\n.footnote-reference:target\n background-color: var(--color-highlight-on-target)\n",".guilabel\n background-color: var(--color-guilabel-background)\n border: 1px solid var(--color-guilabel-border)\n color: var(--color-guilabel-text)\n\n padding: 0 0.3em\n border-radius: 0.5em\n font-size: 0.9em\n","// This file contains the styles used for stylizing the footer that's shown\n// below the content.\n@use \"../variables\" as *\n\nfooter\n font-size: var(--font-size--small)\n display: flex\n flex-direction: column\n\n margin-top: 2rem\n\n// Bottom of page information\n.bottom-of-page\n display: flex\n align-items: center\n justify-content: space-between\n\n margin-top: 1rem\n padding-top: 1rem\n padding-bottom: 1rem\n\n color: var(--color-foreground-secondary)\n border-top: 1px solid var(--color-background-border)\n\n line-height: 1.5\n\n @media (max-width: $content-width)\n text-align: center\n flex-direction: column-reverse\n gap: 0.25rem\n\n .left-details\n font-size: var(--font-size--small)\n\n .right-details\n display: flex\n flex-direction: column\n gap: 0.25rem\n text-align: right\n\n .icons\n display: flex\n justify-content: flex-end\n gap: 0.25rem\n font-size: 1rem\n\n a\n text-decoration: none\n\n svg,\n img\n font-size: 1.125rem\n height: 1em\n width: 1em\n\n// Next/Prev page information\n.related-pages\n a\n display: flex\n align-items: center\n\n text-decoration: none\n &:hover .page-info .title\n text-decoration: underline\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n svg.furo-related-icon,\n svg.furo-related-icon > use\n flex-shrink: 0\n\n color: var(--color-foreground-border)\n\n width: 0.75rem\n height: 0.75rem\n margin: 0 0.5rem\n\n &.next-page\n max-width: 50%\n\n float: right\n clear: right\n text-align: right\n\n &.prev-page\n max-width: 50%\n\n float: left\n clear: left\n\n svg\n transform: rotate(180deg)\n\n.page-info\n display: flex\n flex-direction: column\n overflow-wrap: anywhere\n\n .next-page &\n align-items: flex-end\n\n .context\n display: flex\n align-items: center\n\n padding-bottom: 0.1rem\n\n color: var(--color-foreground-muted)\n font-size: var(--font-size--small)\n text-decoration: none\n","// This file contains the styles for the contents of the left sidebar, which\n// contains the navigation tree, logo, search etc.\n\n////////////////////////////////////////////////////////////////////////////////\n// Brand on top of the scrollable tree.\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-brand\n display: flex\n flex-direction: column\n flex-shrink: 0\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n text-decoration: none\n\n.sidebar-brand-text\n color: var(--color-sidebar-brand-text)\n overflow-wrap: break-word\n margin: var(--sidebar-item-spacing-vertical) 0\n font-size: 1.5rem\n\n.sidebar-logo-container\n margin: var(--sidebar-item-spacing-vertical) 0\n\n.sidebar-logo\n margin: 0 auto\n display: block\n max-width: 100%\n\n////////////////////////////////////////////////////////////////////////////////\n// Search\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-search-container\n display: flex\n align-items: center\n margin-top: var(--sidebar-search-space-above)\n\n position: relative\n\n background: var(--color-sidebar-search-background)\n &:hover,\n &:focus-within\n background: var(--color-sidebar-search-background--focus)\n\n &::before\n content: \"\"\n position: absolute\n left: var(--sidebar-item-spacing-horizontal)\n width: var(--sidebar-search-icon-size)\n height: var(--sidebar-search-icon-size)\n\n background-color: var(--color-sidebar-search-icon)\n mask-image: var(--icon-search)\n\n.sidebar-search\n box-sizing: border-box\n\n border: none\n border-top: 1px solid var(--color-sidebar-search-border)\n border-bottom: 1px solid var(--color-sidebar-search-border)\n\n padding-top: var(--sidebar-search-input-spacing-vertical)\n padding-bottom: var(--sidebar-search-input-spacing-vertical)\n padding-right: var(--sidebar-search-input-spacing-horizontal)\n padding-left: calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size))\n\n width: 100%\n\n color: var(--color-sidebar-search-foreground)\n background: transparent\n z-index: 10\n\n &:focus\n outline: none\n\n &::placeholder\n font-size: var(--sidebar-search-input-font-size)\n\n//\n// Hide Search Matches link\n//\n#searchbox .highlight-link\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0\n margin: 0\n text-align: center\n\n a\n color: var(--color-sidebar-search-icon)\n font-size: var(--font-size--small--2)\n\n////////////////////////////////////////////////////////////////////////////////\n// Structure/Skeleton of the navigation tree (left)\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-tree\n font-size: var(--sidebar-item-font-size)\n margin-top: var(--sidebar-tree-space-above)\n margin-bottom: var(--sidebar-item-spacing-vertical)\n\n ul\n padding: 0\n margin-top: 0\n margin-bottom: 0\n\n display: flex\n flex-direction: column\n\n list-style: none\n\n li\n position: relative\n margin: 0\n\n > ul\n margin-left: var(--sidebar-item-spacing-horizontal)\n\n .icon\n color: var(--color-sidebar-link-text)\n\n .reference\n box-sizing: border-box\n color: var(--color-sidebar-link-text)\n\n // Fill the parent.\n display: inline-block\n line-height: var(--sidebar-item-line-height)\n text-decoration: none\n\n // Don't allow long words to cause wrapping.\n overflow-wrap: anywhere\n\n height: 100%\n width: 100%\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n &:hover\n color: var(--color-sidebar-link-text)\n background: var(--color-sidebar-item-background--hover)\n\n // Add a nice little \"external-link\" arrow here.\n &.external::after\n content: url('data:image/svg+xml, ')\n margin: 0 0.25rem\n vertical-align: middle\n color: var(--color-sidebar-link-text)\n\n // Make the current page reference bold.\n .current-page > .reference\n font-weight: bold\n\n label\n position: absolute\n top: 0\n right: 0\n height: var(--sidebar-item-height)\n width: var(--sidebar-expander-width)\n\n cursor: pointer\n user-select: none\n\n display: flex\n justify-content: center\n align-items: center\n\n .caption, :not(.caption) > .caption-text\n font-size: var(--sidebar-caption-font-size)\n color: var(--color-sidebar-caption-text)\n\n font-weight: bold\n text-transform: uppercase\n\n margin: var(--sidebar-caption-space-above) 0 0 0\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n // If it has children, add a bit more padding to wrap the content to avoid\n // overlapping with the \n li.has-children\n > .reference\n padding-right: var(--sidebar-expander-width)\n\n // Colorize the top-level list items and icon.\n .toctree-l1\n & > .reference,\n & > label .icon\n color: var(--color-sidebar-link-text--top-level)\n\n // Color changes on hover\n label\n background: var(--color-sidebar-item-expander-background)\n &:hover\n background: var(--color-sidebar-item-expander-background--hover)\n\n .current > .reference\n background: var(--color-sidebar-item-background--current)\n &:hover\n background: var(--color-sidebar-item-background--hover)\n\n.toctree-checkbox\n position: absolute\n display: none\n\n////////////////////////////////////////////////////////////////////////////////\n// Togglable expand/collapse\n////////////////////////////////////////////////////////////////////////////////\n.toctree-checkbox\n ~ ul\n display: none\n\n ~ label .icon svg\n transform: rotate(90deg)\n\n.toctree-checkbox:checked\n ~ ul\n display: block\n\n ~ label .icon svg\n transform: rotate(-90deg)\n","// This file contains the styles for the contents of the right sidebar, which\n// contains the table of contents for the current page.\n.toc-title-container\n padding: var(--toc-title-padding)\n padding-top: var(--toc-spacing-vertical)\n\n.toc-title\n color: var(--color-toc-title-text)\n font-size: var(--toc-title-font-size)\n padding-left: var(--toc-spacing-horizontal)\n text-transform: uppercase\n\n// If the ToC is not present, hide these elements coz they're not relevant.\n.no-toc\n display: none\n\n.toc-tree-container\n padding-bottom: var(--toc-spacing-vertical)\n\n.toc-tree\n font-size: var(--toc-font-size)\n line-height: 1.3\n border-left: 1px solid var(--color-background-border)\n\n padding-left: calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))\n\n // Hide the first \"top level\" bullet.\n > ul > li:first-child\n padding-top: 0\n & > ul\n padding-left: 0\n & > a\n display: none\n\n ul\n list-style-type: none\n margin-top: 0\n margin-bottom: 0\n padding-left: var(--toc-item-spacing-horizontal)\n li\n padding-top: var(--toc-item-spacing-vertical)\n\n &.scroll-current > .reference\n color: var(--color-toc-item-text--active)\n font-weight: bold\n\n a.reference\n color: var(--color-toc-item-text)\n text-decoration: none\n overflow-wrap: anywhere\n\n.toc-scroll\n max-height: 100vh\n overflow-y: scroll\n\n// Be very annoying when someone includes the table of contents\n.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here)\n color: var(--color-problematic)\n background: rgba(255, 0, 0, 0.25)\n &::before\n content: \"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch.\"\n","// Shameful hacks, to work around bugs.\n\n// MyST parser doesn't correctly generate classes, to align table contents.\n// https://github.com/executablebooks/MyST-Parser/issues/412\n.text-align\\:left > p\n text-align: left\n\n.text-align\\:center > p\n text-align: center\n\n.text-align\\:right > p\n text-align: right\n"],"names":[],"sourceRoot":""}
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/config.html b/SRV/docs/_build/html/api/config.html
new file mode 100644
index 0000000..3b3d977
--- /dev/null
+++ b/SRV/docs/_build/html/api/config.html
@@ -0,0 +1,349 @@
+
+
+
+
+
+
+
+
+ Конфигурация - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/config/cors.html b/SRV/docs/_build/html/api/config/cors.html
new file mode 100644
index 0000000..866319c
--- /dev/null
+++ b/SRV/docs/_build/html/api/config/cors.html
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+ config.cors - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+config.cors
+Конфигурация CORS для FastAPI-приложения GONETS.
+Модуль инкапсулирует добавление middleware CORSMiddleware со списком
+разрешённых источников (origins), характерных для локальной разработки и
+типовых портов фронтенда (например, Vite/React на 5173/5174, dev-серверы).
+
+Использование: cors_config = CORSMiddlewareConfig(app)
+cors_config.add_cors_middleware()
+
+Поведение:
+Разрешены все методы (allow_methods=[“*”] ) и заголовки (allow_headers=[“*”] ).
+Разрешены креденшлы (allow_credentials=True ) для работы с куками (локаль и др.).
+При необходимости список origins можно расширить или параметризовать
+на основе конфигурационного файла окружения.
+
+
+
+
+
+class config.cors. CORSMiddlewareConfig ( app : FastAPI ) [source]
+Bases: object
+Класс для настройки CORS политики в FastAPI
+
+
+add_cors_middleware ( ) → None [source]
+Добавляем CORS в приложение FastAPI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/config/init_config.html b/SRV/docs/_build/html/api/config/init_config.html
new file mode 100644
index 0000000..10017c8
--- /dev/null
+++ b/SRV/docs/_build/html/api/config/init_config.html
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+
+
+ config.init_config - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/config/ports.html b/SRV/docs/_build/html/api/config/ports.html
new file mode 100644
index 0000000..6f52956
--- /dev/null
+++ b/SRV/docs/_build/html/api/config/ports.html
@@ -0,0 +1,380 @@
+
+
+
+
+
+
+
+
+ config.ports - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+config.ports
+
+
+class config.ports. PortConfig ( custom_ports : Dict [ int , str ] = None ) [source]
+Bases: object
+Конфигурация известных портов для сетевого анализа.
+
+Назначение: Предоставляет сопоставление распространённых портов с человекочитаемыми
+названиями сервисов для отображения в UI и логах. Поддерживает расширение
+пользовательским словарём.
+
+
+
+
+get_port_name ( port : int ) → str [source]
+Возвращает название сервиса для указанного порта.
+
+Parameters:
+port – Номер порта.
+
+Returns:
+Название сервиса или строка вида ‘Port-<port>’.
+
+
+
+
+
+
+get_ports ( ) → List [ int ] [source]
+Возвращает список всех номеров портов.
+
+Returns:
+Список номеров портов.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/core.html b/SRV/docs/_build/html/api/core.html
new file mode 100644
index 0000000..c28ad78
--- /dev/null
+++ b/SRV/docs/_build/html/api/core.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+ Core - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/core/cli_executor.html b/SRV/docs/_build/html/api/core/cli_executor.html
new file mode 100644
index 0000000..454a5f3
--- /dev/null
+++ b/SRV/docs/_build/html/api/core/cli_executor.html
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+ core.cli_executor - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+core.cli_executor
+
+
+class core.cli_executor. CLIExecutor [source]
+Bases: object
+Утилита для безопасного выполнения CLI-команд.
+
+Назначение: Инкапсулирует вызов subprocess.run и приводит результат к удобному
+кортежу (код возврата, stdout) без генерации исключений при ошибках.
+
+
+
+
+static run ( command : list ) → Tuple [ int , str ] [source]
+Выполняет команду в системе и возвращает код и вывод.
+
+Parameters:
+command (list ) – Список аргументов команды для выполнения.
+
+Returns:
+Код возврата и вывод команды.
+
+Return type:
+tuple
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints.html b/SRV/docs/_build/html/api/endpoints.html
new file mode 100644
index 0000000..92f567e
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints.html
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+
+
+ API эндпоинты - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/auth.html b/SRV/docs/_build/html/api/endpoints/auth.html
new file mode 100644
index 0000000..2bc2c38
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/auth.html
@@ -0,0 +1,476 @@
+
+
+
+
+
+
+
+
+ api.v1.auth - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.auth
+
+
+async api.v1.auth. check_users_file ( translations : Translations = Depends(translations_dependency) ) [source]
+Проверяет, существует ли файл users.json и содержит ли он пользователей.
+Возвращает информацию о состоянии файла и наличии пользователей.
+
+Returns:
+
+Словарь с ключами:
+”exists” (bool): Существует ли файл.
+”has_users” (bool): Содержит ли файл пользователей.
+”message” (str): Сообщение с описанием результата.
+
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если произошла ошибка при проверке файла (status_code=500).
+
+
+
+
+
+
+async api.v1.auth. get_current_user ( request : Request , translations : Translations = Depends(translations_dependency) ) [source]
+Возвращает данные текущего пользователя и токен.
+Проверяет активную сессию по client_id и возвращает информацию о пользователе
+и токен для дальнейшего использования.
+
+Parameters:
+
+
+Returns:
+
+Словарь с ключами “user” (UserResponse), “access_token” (str) и ”token_type” (str, всегда “bearer”).
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если сессия не найдена или истекла (status_code=401) или
+ произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+async api.v1.auth. login ( user : UserLogin , request : Request , translations : Translations = Depends(translations_dependency) ) [source]
+Авторизует пользователя и создаёт сессию.
+Проверяет учетные данные пользователя и возвращает токен и session_id для
+последующей авторизации.
+
+Parameters:
+
+user (UserLogin ) – Объект с данными для входа (username, password).
+request (Request ) – Объект запроса для получения client_id (IP-адрес клиента).
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Объект с access_token и session_id для авторизации.
+
+Return type:
+Token
+
+Raises:
+HTTPException – Если учетные данные неверны (status_code=401) или произошла
+ внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+async api.v1.auth. logout ( request : Request , translations : Translations = Depends(translations_dependency) ) [source]
+Выход из системы, удаление сессии.
+Удаляет активную сессию пользователя по client_id.
+
+Parameters:
+
+
+Returns:
+Пустой словарь или сообщение об успешном выходе.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если сессия не найдена (status_code=401) или произошла
+ внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+async api.v1.auth. register ( user : UserCreate , translations : Translations = Depends(translations_dependency) ) [source]
+Регистрирует нового пользователя.
+Создаёт нового пользователя на основе предоставленных данных и возвращает
+информацию о зарегистрированном пользователе.
+
+Parameters:
+
+user (UserCreate ) – Объект с данными нового пользователя (username, password, is_admin).
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Объект с данными зарегистрированного пользователя (id, username, is_admin).
+
+Return type:
+UserResponse
+
+Raises:
+HTTPException – Если пользователь уже существует (status_code=400) или произошла
+ внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/ip.html b/SRV/docs/_build/html/api/endpoints/ip.html
new file mode 100644
index 0000000..f6e1077
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/ip.html
@@ -0,0 +1,443 @@
+
+
+
+
+
+
+
+
+ api.v1.ip - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.ip
+
+
+async api.v1.ip. apply_ip_config ( data : Dict [ str , Any ] = Body(PydanticUndefined) , translations : Translations = Depends(translations_dependency) ) [source]
+Применяет проверенные IP-настройки к указанному сетевому профилю.
+Обновляет настройки соединения через NetworkManager, отключает и
+переподключает устройство к сети.
+
+Parameters:
+
+data (Dict [ str , Any ] ) – Словарь с данными конфигурации, включая
+“connection_id” и “ipv4_updates” с обновлениями IP-настроек.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+
+Словарь с ключами:
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если подключение не найдено (status_code=404) или
+ произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+async api.v1.ip. check_ip_config ( data : Dict [ str , Any ] = Body(PydanticUndefined) , translations : Translations = Depends(translations_dependency) ) [source]
+Проверяет доступность IP-адреса перед его применением.
+Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
+и возвращает результат проверки.
+
+Parameters:
+
+data (Dict [ str , Any ] ) – Словарь с данными конфигурации, включая
+“ipv4_updates” с полем “ipv4.address-data” для нового IP-адреса.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+
+Словарь с ключами:
+
+
+
+
+Return type:
+Dict[str, Any]
+
+Raises:
+HTTPException – Если произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+async api.v1.ip. save_ip_config ( data : Dict [ str , Any ] = Body(PydanticUndefined) , translations : Translations = Depends(translations_dependency) ) [source]
+Применяет проверенные IP-настройки для отката сетевого профиля.
+Используется для сохранения и отката настроек сетевого профиля к предыдущему
+состоянию через NetworkManager.
+
+Parameters:
+
+data (Dict [ str , Any ] ) – Словарь с данными конфигурации, включая
+“connection_id” и “ipv4_updates” с обновлениями IP-настроек.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+
+Словарь с ключами:
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если подключение не найдено (status_code=404) или
+ произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/network.html b/SRV/docs/_build/html/api/endpoints/network.html
new file mode 100644
index 0000000..d4a7267
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/network.html
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+
+
+ api.v1.network - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.network
+
+
+class api.v1.network. ContainerPositionManager [source]
+Bases: object
+Класс для управления позициями и состоянием контейнеров.
+Хранит и обновляет координаты (x, y) и состояние (isOpen) для контейнеров
+активации и деактивации.
+
+
+get_position_and_state ( container_name : str , translations : Translations = Depends(translations_dependency) ) → Tuple [ int , int , bool ] [source]
+Получает текущие координаты и состояние указанного контейнера.
+
+Parameters:
+
+
+Returns:
+Кортеж с координатами (x, y) и состоянием (isOpen).
+
+Return type:
+Tuple[int, int, bool]
+
+Raises:
+ValueError – Если указан неверный идентификатор контейнера.
+
+
+
+
+
+
+update_position ( container_id : int , x : int , y : int , isOpen : bool ) → None [source]
+Обновляет позицию и состояние контейнера по его идентификатору.
+
+Parameters:
+
+container_id (int ) – Идентификатор контейнера (1 для “activation”, 2 для “deactivation”).
+x (int ) – Новая координата X.
+y (int ) – Новая координата Y.
+isOpen (bool ) – Новое состояние контейнера (открыт/закрыт).
+
+
+Raises:
+ValueError – Если container_id не соответствует ни одному контейнеру.
+
+
+
+
+
+
+
+
+async api.v1.network. get_hostname ( translations : Translations = Depends(translations_dependency) ) [source]
+Возвращает имя хоста системы.
+Выполняет запрос к сервису для получения имени хоста.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+Словарь с ключом “hostname” и значением имени хоста.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если произошла ошибка при получении имени хоста (status_code=500).
+
+
+
+
+
+
+async api.v1.network. get_network_data ( translations : Translations = Depends(translations_dependency) ) → Dict [ str , Dict ] [source]
+Возвращает текущие сетевые данные в виде словаря.
+Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
+и возвращает их в формате Activation и Deactivation с учётом позиций и состояний
+контейнеров.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+
+Словарь с ключами “activation” и “deactivation”, содержащими данные о соответствующих контейнерах.
+
+
+
+
+Return type:
+Dict[str, Dict]
+
+Raises:
+HTTPException – Если произошла ошибка при получении данных (status_code=500).
+
+
+
+
+
+
+async api.v1.network. update_container_position ( data : Dict [ str , int | bool ] = Body(PydanticUndefined) , translations : Translations = Depends(translations_dependency) ) [source]
+Обновляет позицию и состояние контейнера.
+Принимает данные о новом положении и состоянии контейнера и обновляет их
+в менеджере позиций.
+
+Parameters:
+
+data (Dict [ str , Union [ int , bool ] ] ) – Словарь с ключами:
+- “container_id” (int): Идентификатор контейнера (1 или 2).
+- “container_x” (int): Новая координата X.
+- “container_y” (int): Новая координата Y.
+- “isOpen” (bool): Новое состояние контейнера (открыт/закрыт).
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+
+Словарь с ключами:
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если отсутствуют обязательные поля (status_code=400) или
+ типы данных неверны.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/settings.html b/SRV/docs/_build/html/api/endpoints/settings.html
new file mode 100644
index 0000000..5897f09
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/settings.html
@@ -0,0 +1,497 @@
+
+
+
+
+
+
+
+
+ api.v1.settings - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.settings
+
+
+api.v1.settings. clear_all_data ( translations : Translations = Depends(translations_dependency) ) [source]
+Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
+Удаляет все записи из devices.json и wifi.json.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+Словарь с сообщением об успешной очистке.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если произошла ошибка очистки (status_code=500).
+
+
+
+
+
+
+api.v1.settings. clear_subnet ( subnet : str | None = None , translations : Translations = Depends(translations_dependency) ) [source]
+Очищает данные узлов подсети, сохранённые в devices.json.
+Удаляет все записи об узлах подсети, оставляя файл пустым.
+
+Parameters:
+
+subnet (Optional [ str ] ) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+Если None, очищает все подсети.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешной очистке.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если произошла ошибка очистки (status_code=500).
+
+
+
+
+
+
+api.v1.settings. clear_wifi ( translations : Translations = Depends(translations_dependency) ) [source]
+Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
+Удаляет все записи о точках доступа, оставляя файл пустым.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+Словарь с сообщением об успешной очистке.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если произошла ошибка очистки (status_code=500).
+
+
+
+
+
+
+async api.v1.settings. export_devices ( request : SubnetScanRequest = Body(PydanticUndefined) , translations : Translations = Depends(translations_dependency) ) [source]
+Экспортирует файл с данными устройств в формате JSON для указанной подсети.
+
+Parameters:
+
+
+Returns:
+Файл <subnet>.json для скачивания.
+
+Return type:
+FileResponse
+
+Raises:
+HTTPException – Если файл не найден (status_code=404).
+
+
+
+
+
+
+async api.v1.settings. export_wifi ( translations : Translations = Depends(translations_dependency) ) [source]
+Экспортирует файл с данными устройств в формате JSON.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+Файл wifi.json для скачивания.
+
+Return type:
+FileResponse
+
+Raises:
+HTTPException – Если файл не найден (status_code=404).
+
+
+
+
+
+
+async api.v1.settings. get_system_language ( request : Request , translations : Translations = Depends(translations_dependency) ) [source]
+Выводит текущий язык, установленный в системе на основе locale.
+Выполняет команду ‘locale’ в системе и извлекает параметры LANG
+
+Parameters:
+
+
+Returns:
+Словарь с текущим языком системы.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если не удалось извлечь системный язык (status_code=500).
+
+
+
+
+
+
+api.v1.settings. set_language ( locale : str , response : Response , translations : Translations = Depends(translations_dependency) ) [source]
+Устанавливает куку locale для смены языка пользователя.
+
+Parameters:
+
+locale (str ) – Используемый язык.
+response (Response ) – Запрос.
+translations – Объект переводов текущего языка.
+
+
+Returns:
+Сообщение об успешной установке языка.
+
+Return type:
+dict
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/subnet.html b/SRV/docs/_build/html/api/endpoints/subnet.html
new file mode 100644
index 0000000..a3db6a0
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/subnet.html
@@ -0,0 +1,625 @@
+
+
+
+
+
+
+
+
+ api.v1.subnet - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.subnet
+
+
+api.v1.subnet. add_note ( mac : str , request : NoteRequest , subnet_service : SubnetService = Depends(get_subnet_service) , translations : Translations = Depends(translations_dependency) ) [source]
+Добавляет заметку для устройства по MAC-адресу.
+Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
+
+Parameters:
+
+mac (str ) – MAC-адрес устройства.
+request (NoteRequest ) – Объект с полем “note” для текста заметки.
+subnet_service – Сервис сканирования подсети.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешном добавлении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если устройство не найдено (status_code=404), заметка уже
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. add_wifi_note ( ssid : str , request : NoteRequest , translations : Translations = Depends(translations_dependency) ) [source]
+Добавляет заметку для сети Wi-Fi по SSID.
+Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
+
+Parameters:
+
+ssid (str ) – Идентификатор Wi-Fi сети.
+request (NoteRequest ) – Объект с полем “note” для текста заметки.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешном добавлении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если сеть не найдена (status_code=404), заметка уже
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. connect_to_wifi ( request : WifiConnectRequest , translations : Translations = Depends(translations_dependency) ) [source]
+Подключается к Wi-Fi сети по SSID и паролю.
+Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
+учетных данных.
+
+Parameters:
+
+
+Returns:
+Словарь с сообщением об успешном подключении.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если подключение не удалось (status_code=400).
+
+
+
+
+
+
+api.v1.subnet. delete_note ( mac : str , subnet_service : SubnetService = Depends(get_subnet_service) , translations : Translations = Depends(translations_dependency) ) [source]
+Удаляет заметку для устройства по MAC-адресу.
+Удаляет привязанную заметку для указанного устройства.
+
+Parameters:
+
+mac (str ) – MAC-адрес устройства.
+subnet_service – Сервис сканирования подсети.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешном удалении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если устройство не найдено (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. delete_wifi_note ( ssid : str , translations : Translations = Depends(translations_dependency) ) [source]
+Удаляет заметку для сети Wi-Fi по SSID.
+Удаляет привязанную заметку для указанной Wi-Fi сети.
+
+Parameters:
+
+
+Returns:
+Словарь с сообщением об успешном удалении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если сеть не найдена (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. disconnect_from_wifi ( request : WifiDisconnectRequest , translations : Translations = Depends(translations_dependency) ) [source]
+Отключается от Wi-Fi сети по указанному SSID.
+Выполняет отключение от указанной Wi-Fi сети.
+
+Parameters:
+
+
+Returns:
+Словарь с сообщением об успешном отключении.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если отключение не удалось (status_code=400).
+
+
+
+
+
+
+api.v1.subnet. get_wifi_status ( translations : Translations = Depends(translations_dependency) ) [source]
+Проверяет статус текущего подключения к Wi-Fi.
+Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Returns:
+
+Словарь с ключами:
+”status” (str): Состояние (“connected” или другое).
+”ssid” (str, optional): SSID текущей сети, если подключено.
+
+
+
+
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если проверка статуса не удалась (status_code=400).
+
+
+
+
+
+
+api.v1.subnet. scan_subnet ( request : SubnetScanRequest = Body(PydanticUndefined) , subnet_service : SubnetService = Depends(get_subnet_service) , translations : Translations = Depends(translations_dependency) ) [source]
+Сканирует локальную подсеть и возвращает данные об устройствах.
+Выполняет сканирование указанной подсети для обнаружения активных устройств.
+
+Parameters:
+
+request (SubnetScanRequest ) – Объект с полем subnet.
+subnet_service – Сервис сканирования подсети.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Объект с результатами сканирования (устройства и их данные).
+
+Return type:
+SubnetScanResult
+
+Raises:
+HTTPException – Если произошла ошибка сканирования (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. scan_wifi ( timeout : int = 30 , translations : Translations = Depends(translations_dependency) ) [source]
+Сканирует Wi-Fi точки доступа и возвращает данные о них.
+Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
+
+Parameters:
+
+timeout (int , optional ) – Таймаут сканирования в секундах. По умолчанию 30.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Объект с результатами сканирования (список Wi-Fi сетей).
+
+Return type:
+WifiScanResult
+
+Raises:
+HTTPException – Если произошла ошибка сканирования Wi-Fi (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. update_note ( mac : str , request : NoteRequest , subnet_service : SubnetService = Depends(get_subnet_service) , translations : Translations = Depends(translations_dependency) ) [source]
+Обновляет заметку для устройства по MAC-адресу.
+Изменяет существующую заметку, привязанную к устройству.
+
+Parameters:
+
+mac (str ) – MAC-адрес устройства.
+request (NoteRequest ) – Объект с полем “note” для нового текста заметки.
+subnet_service – Сервис сканирования подсети.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешном обновлении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если устройство не найдено (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+api.v1.subnet. update_wifi_note ( ssid : str , request : NoteRequest , translations : Translations = Depends(translations_dependency) ) [source]
+Обновляет заметку для сети Wi-Fi по SSID.
+Изменяет существующую заметку, привязанную к Wi-Fi сети.
+
+Parameters:
+
+ssid (str ) – Идентификатор Wi-Fi сети.
+request (NoteRequest ) – Объект с полем “note” для нового текста заметки.
+translations – Объект переводов для текущего языка.
+
+
+Returns:
+Словарь с сообщением об успешном обновлении заметки.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если сеть не найдена (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/ufw.html b/SRV/docs/_build/html/api/endpoints/ufw.html
new file mode 100644
index 0000000..609d88a
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/ufw.html
@@ -0,0 +1,505 @@
+
+
+
+
+
+
+
+
+ api.v1.ufw - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.ufw
+
+
+class api.v1.ufw. RuleNumbers ( * , numbers : List [ int ] ) [source]
+Bases: BaseModel
+Модель запроса на удаление UFW правил.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+numbers : List [ int ]
+
+
+
+
+
+
+async api.v1.ufw. add_ufw_in_rules ( rules : List [ UFWRule ] ) [source]
+Добавляет входящие (in) правила UFW.
+
+Parameters:
+rules (List [ UFWRule ] ) – Список правил, определяющих направления,
+порты/диапазоны/протоколы и действия (allow/deny/reject).
+
+Returns:
+Итог выполнения с количеством успешно добавленных правил и деталями
+по каждому правилу.
+
+Return type:
+dict
+
+Raises:
+
+
+
+
+
+
+
+async api.v1.ufw. add_ufw_out_rules ( rules : List [ UFWRule ] ) [source]
+Добавляет исходящие (out) правила UFW.
+
+Parameters:
+rules (List [ UFWRule ] ) – Список правил исходящего трафика для добавления.
+
+Returns:
+Объект статуса с сообщением об успешном добавлении.
+
+Return type:
+dict
+
+Raises:
+
+
+
+
+
+
+
+async api.v1.ufw. delete_ufw_rules ( rule_numbers : RuleNumbers ) [source]
+Удаляет правила UFW по их номерам.
+
+Parameters:
+rule_numbers (RuleNumbers ) – Номера правил, подлежащих удалению (как в
+выводе ufw status numbered ).
+
+Returns:
+Информация об общем количестве удаляемых правил, числе успешных и
+возможной ошибке.
+
+Return type:
+dict
+
+Raises:
+HTTPException – При ошибках удаления (status_code=500).
+
+
+
+
+
+
+async api.v1.ufw. disable_ufw ( ) [source]
+Выключает UFW.
+
+Returns:
+Словарь с ключами status и message при успешном выполнении.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если выключение завершилось неуспешно (status_code=500).
+
+
+
+
+
+
+async api.v1.ufw. enable_ufw ( ) [source]
+Включает UFW.
+
+Returns:
+Словарь с ключами status и message при успешном выполнении.
+
+Return type:
+dict
+
+Raises:
+HTTPException – Если включение завершилось неуспешно (status_code=500).
+
+
+
+
+
+
+async api.v1.ufw. get_ufw_rules ( ) [source]
+Возвращает все правила UFW.
+Осуществляет обращение к сервису UFWService для чтения текущего списка
+правил брандмауэра и возвращает результат в сериализованном виде для клиента.
+
+Returns:
+Структура с коллекцией правил и сопутствующей
+информацией о состоянии.
+
+Return type:
+UFWRulesResponse
+
+Raises:
+HTTPException – В случае ошибок взаимодействия с UFW (status_code=500).
+
+
+
+
+
+
+async api.v1.ufw. get_ufw_status ( ) [source]
+Возвращает текущий статус UFW.
+
+Returns:
+Объект со сведениями о включенности UFW и деталями статуса.
+
+Return type:
+UFWStatus
+
+Raises:
+HTTPException – Если не удалось получить статус (status_code=500).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/endpoints/websocket.html b/SRV/docs/_build/html/api/endpoints/websocket.html
new file mode 100644
index 0000000..cbd6fc1
--- /dev/null
+++ b/SRV/docs/_build/html/api/endpoints/websocket.html
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
+ api.v1.websocket - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+api.v1.websocket
+
+
+class api.v1.websocket. WebSocketManager [source]
+Bases: object
+Класс для управления подключениями к WebSocket-серверу.
+Обрабатывает подключение, отключение и трансляцию сообщений для клиентов,
+подключённых через WebSocket.
+
+
+async broadcast ( message : dict ) [source]
+Транслирует сообщения всем подключённым клиентам.
+Отправляет JSON-данные всем активным соединениям, обрабатывая
+отключения клиентов.
+
+Parameters:
+message (dict ) – Сообщение для трансляции в формате JSON.
+
+Raises:
+
+
+
+
+
+
+
+async connect ( websocket : WebSocket ) [source]
+Подключает клиента к WebSocket-серверу.
+Принимает подключение и добавляет WebSocket в список активных соединений.
+
+Parameters:
+websocket (WebSocket ) – Объект WebSocket для подключения.
+
+Raises:
+Exception – Если произошла ошибка при принятии подключения.
+
+
+
+
+
+
+disconnect ( websocket : WebSocket ) [source]
+Отключает клиента от WebSocket-серверу.
+Удаляет WebSocket из списка активных соединений.
+
+Parameters:
+websocket (WebSocket ) – Объект WebSocket для отключения.
+
+
+
+
+
+
+
+
+async api.v1.websocket. broadcast_data ( translations : Translations = Depends(translations_dependency) ) [source]
+Таск для периодической трансляции сетевых данных.
+Выполняет сканирование сети и отправляет данные всем подключённым клиентам
+с интервалом в 2 секунды.
+
+Parameters:
+translations – Объект переводов для текущего языка.
+
+Raises:
+Exception – Если произошла ошибка при получении данных или трансляции.
+
+
+
+
+
+
+async api.v1.websocket. startup_event ( ) [source]
+Запуск таска для трансляции сетевых данных при старте приложения.
+Создаёт асинхронную задачу для периодической трансляции данных.
+
+Raises:
+Exception – Если произошла ошибка при запуске таска.
+
+
+
+
+
+
+async api.v1.websocket. websocket_endpoint ( websocket : WebSocket , translations : Translations = Depends(translations_dependency) ) [source]
+Обработчик WebSocket-соединения.
+Управляет соединением клиента, принимает данные для обновления позиций
+контейнеров и транслирует сетевые данные.
+
+Parameters:
+
+
+Raises:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models.html b/SRV/docs/_build/html/api/models.html
new file mode 100644
index 0000000..c3b9afa
--- /dev/null
+++ b/SRV/docs/_build/html/api/models.html
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+ Модели - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/auth.html b/SRV/docs/_build/html/api/models/auth.html
new file mode 100644
index 0000000..fe79b3e
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/auth.html
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+
+
+ models.auth - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.auth
+
+
+class models.auth. Token ( * , access_token : str , token_type : str , session_id : str | None = None , user : UserResponse | None = None ) [source]
+Bases: BaseModel
+Модель JWT-токена.
+Атрибуты:
+
+access_token (str): Значение токена (в формате JWT).
+token_type (str): Тип токена (обычно bearer ).
+session_id (Optional[str]): Идентификатор сессии.
+user (Optional[UserResponse]): Данные пользователя, если включены в ответ.
+
+
+
+access_token : str
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+session_id : str | None
+
+
+
+
+token_type : str
+
+
+
+
+user : UserResponse | None
+
+
+
+
+
+
+class models.auth. UserCreate ( * , username : str , password : str , is_admin : bool = False ) [source]
+Bases: BaseModel
+Модель созданного пользователя.
+Атрибуты:
+
+username (str): Имя пользователя.
+password (str): Пароль пользователя.
+is_admin (bool): Флаг административного доступа.
+
+
+
+is_admin : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+password : str
+
+
+
+
+username : str
+
+
+
+
+
+
+class models.auth. UserLogin ( * , username : str , password : str ) [source]
+Bases: BaseModel
+Модель авторизации пользователя.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+password : str
+
+
+
+
+username : str
+
+
+
+
+
+
+class models.auth. UserResponse ( * , id : int , username : str , is_admin : bool ) [source]
+Bases: BaseModel
+Модель, используемая при возврате сведений о пользователе.
+Атрибуты:
+
+id (int): Идентификатор пользователя.
+username (str): Имя пользователя.
+is_admin (bool): Флаг административного доступа.
+
+
+
+id : int
+
+
+
+
+is_admin : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+username : str
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/network.html b/SRV/docs/_build/html/api/models/network.html
new file mode 100644
index 0000000..75fea31
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/network.html
@@ -0,0 +1,744 @@
+
+
+
+
+
+
+
+
+ models.network - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.network
+
+
+class models.network. Activation ( * , id : int , interfaces : List [ NetworkInterface | WiFiDevice ] , container_x : int , container_y : int , isOpen : bool = True ) [source]
+Bases: BaseModel
+Модель узла активных интерфейсов.
+Атрибуты:
+
+id (int): Идентификатор ноды.
+interfaces (List[Union[NetworkInterface, WiFiDevice]]): Активные интерфейсы.
+container_x (int): Координата X на холсте.
+container_y (int): Координата Y на холсте.
+isOpen (bool): Флаг отображения узла на холсте.
+
+
+
+container_x : int
+
+
+
+
+container_y : int
+
+
+
+
+id : int
+
+
+
+
+interfaces : List [ NetworkInterface | WiFiDevice ]
+
+
+
+
+isOpen : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+class models.network. Deactivation ( * , id : int , interfaces : List [ NetworkInterface | WiFiDevice ] , container_x : int , container_y : int , isOpen : bool = True ) [source]
+Bases: BaseModel
+Модель узла отключённых интерфейсов.
+Атрибуты:
+
+id (int): Идентификатор ноды.
+interfaces (List[Union[NetworkInterface, WiFiDevice]]): Список отключённых интерфейсов.
+container_x (int): Координата X на холсте.
+container_y (int): Координата Y на холсте.
+isOpen (bool): Флаг отображения узла на холсте.
+
+
+
+container_x : int
+
+
+
+
+container_y : int
+
+
+
+
+id : int
+
+
+
+
+interfaces : List [ NetworkInterface | WiFiDevice ]
+
+
+
+
+isOpen : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+class models.network. IPConfig ( * , configurations : List [ IndividualIPConfig ] ) [source]
+Bases: BaseModel
+Групповая конфигурация IP-адресов.
+Атрибуты:
+
+
+
+configurations : List [ IndividualIPConfig ]
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+class models.network. IPv4Route ( * , destination : str , prefix : int , next_hop : str , metric : int ) [source]
+Bases: BaseModel
+Модель маршрута IPv4.
+Атрибуты:
+
+destination (str): IP-адрес назначения.
+prefix (int): Префикс подсети в формате CIDR.
+next_hop (str): Адрес следующего узла (маршрутизатор).
+metric (int): Метрика маршрута.
+
+
+
+destination : str
+
+
+
+
+metric : int
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+next_hop : str
+
+
+
+
+prefix : int
+
+
+
+
+
+
+class models.network. IPv6Route ( * , destination : str , prefix : int , next_hop : str , metric : int ) [source]
+Bases: BaseModel
+Модель маршрута IPv6.
+Атрибуты:
+
+destination (str): IP-адрес назначения.
+prefix (int): Префикс подсети в формате CIDR.
+next_hop (str): Адрес следующего узла.
+metric (int): Метрика маршрута.
+
+
+
+destination : str
+
+
+
+
+metric : int
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+next_hop : str
+
+
+
+
+prefix : int
+
+
+
+
+
+
+class models.network. IndividualIPConfig ( * , address : str , routes : List [ IPv4Route | IPv6Route ] , method : str | None , gateway : str , dns : List [ str ] ) [source]
+Bases: BaseModel
+Модель индивидуальной конфигурации IP-адреса.
+Атрибуты:
+
+address (str): IP-адрес интерфейса.
+routes (List[Union[IPv4Route, IPv6Route]]): Список маршрутов.
+method (Optional[str]): Метод назначения IP.
+gateway (str): Шлюз по умолчанию.
+dns (List[str]): Список DNS-серверов.
+
+
+
+address : str
+
+
+
+
+dns : List [ str ]
+
+
+
+
+gateway : str
+
+
+
+
+method : str | None
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+routes : List [ IPv4Route | IPv6Route ]
+
+
+
+
+
+
+class models.network. NetworkInterface ( * , name : str , type : str , mac_address : str , device_state : str , active_connection : str , device_mtu : str , ipv4 : IPConfig , ipv6 : IPConfig , container_x : int = 0 , container_y : int = 0 ) [source]
+Bases: BaseModel
+Модель базового сетевого интерфейса.
+Атрибуты:
+
+name (str): Название интерфейса.
+type (str): Тип устройства.
+mac_address (str): MAC-адрес.
+device_state (str): Текущее состояние.
+active_connection (str): Название активного соединения.
+device_mtu (str): MTU устройства.
+ipv4 (IPConfig): IPv4-конфигурация.
+ipv6 (IPConfig): IPv6-конфигурация.
+container_x (int): Положение по оси X на холсте.
+container_y (int): Положение по оси Y на холсте.
+
+
+
+active_connection : str
+
+
+
+
+container_x : int
+
+
+
+
+container_y : int
+
+
+
+
+device_mtu : str
+
+
+
+
+device_state : str
+
+
+
+
+ipv4 : IPConfig
+
+
+
+
+ipv6 : IPConfig
+
+
+
+
+mac_address : str
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+name : str
+
+
+
+
+type : str
+
+
+
+
+
+
+class models.network. NetworkInterfacesContainer ( * , interfaces : List [ NetworkInterface | WiFiDevice ] , container_x : int , container_y : int ) [source]
+Bases: BaseModel
+Контейнер для набора сетевых интерфейсов.
+Атрибуты:
+
+interfaces (List[Union[NetworkInterface, WiFiDevice]]): Список интерфейсов.
+container_x (int): Положение по оси X.
+container_y (int): Положение по оси Y.
+
+
+
+container_x : int
+
+
+
+
+container_y : int
+
+
+
+
+interfaces : List [ NetworkInterface | WiFiDevice ]
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+class models.network. WiFiDevice ( * , name : str , type : str , mac_address : str , device_state : str , active_connection : str , device_mtu : str , ipv4 : IPConfig , ipv6 : IPConfig , container_x : int = 0 , container_y : int = 0 , ssid : str | None = None , bssid : str | None = None , bitrate : int | None = None , strength : int | None = None , frequency : float | None = None , mode : str | int = None ) [source]
+Bases: NetworkInterface
+Расширенная модель Wi-Fi устройства.
+Атрибуты:
+
+ssid (Optional[str]): Название Wi-Fi сети.
+bssid (Optional[str]): MAC-адрес точки доступа.
+bitrate (Optional[int]): Скорость соединения.
+strength (Optional[int]): Уровень сигнала.
+frequency (Optional[float]): Рабочая частота.
+mode (Union[str, int]): Режим работы.
+
+
+
+bitrate : int | None
+
+
+
+
+bssid : str | None
+
+
+
+
+frequency : float | None
+
+
+
+
+mode : str | int
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+ssid : str | None
+
+
+
+
+strength : int | None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/request.html b/SRV/docs/_build/html/api/models/request.html
new file mode 100644
index 0000000..e2c8e58
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/request.html
@@ -0,0 +1,414 @@
+
+
+
+
+
+
+
+
+ models.request - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.request
+
+
+class models.request. NoteRequest ( * , note : str ) [source]
+Bases: BaseModel
+Модель запроса на добавление заметки к устройству.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+note : str
+
+
+
+
+
+
+class models.request. WifiConnectRequest ( * , ssid : str , password : str ) [source]
+Bases: BaseModel
+Модель запроса на подключение к Wi-Fi.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+password : str
+
+
+
+
+ssid : str
+
+
+
+
+
+
+class models.request. WifiDisconnectRequest ( * , ssid : str ) [source]
+Bases: BaseModel
+Модель запроса на отключение от Wi-Fi.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+ssid : str
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/subnet.html b/SRV/docs/_build/html/api/models/subnet.html
new file mode 100644
index 0000000..4a88341
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/subnet.html
@@ -0,0 +1,456 @@
+
+
+
+
+
+
+
+
+ models.subnet - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.subnet
+
+
+class models.subnet. Device ( * , ip : str , mac : str | None = None , hostname : str | None = None , vendor : str | None = None , open_ports : List [ str ] = [] , status : str , last_seen : str | None = None , note : str | None = None ) [source]
+Bases: BaseModel
+Модель устройства, обнаруженного в подсети.
+Атрибуты:
+
+ip (str): IP-адрес устройства.
+mac (Optional[str]): MAC-адрес устройства.
+hostname (Optional[str]): Имя хоста.
+vendor (Optional[str]): Производитель оборудования.
+open_ports (List[str]): Открытые порты.
+status (str): Состояние устройства (online/offline).
+last_seen (Optional[str]): Время последнего обнаружения.
+note (Optional[str]): Пользовательская заметка.
+
+
+
+hostname : str | None
+
+
+
+
+ip : str
+
+
+
+
+last_seen : str | None
+
+
+
+
+mac : str | None
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+note : str | None
+
+
+
+
+open_ports : List [ str ]
+
+
+
+
+status : str
+
+
+
+
+vendor : str | None
+
+
+
+
+
+
+class models.subnet. SubnetScanRequest ( * , subnet : str | None = None ) [source]
+Bases: BaseModel
+Параметры запроса на сканирование подсети.
+Атрибуты:
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+subnet : str | None
+
+
+
+
+
+
+class models.subnet. SubnetScanResult ( * , devices : List [ Device ] = [] , error : str | None = None ) [source]
+Bases: BaseModel
+Результат сканирования подсети.
+Атрибуты:
+
+
+
+devices : List [ Device ]
+
+
+
+
+error : str | None
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/ufw.html b/SRV/docs/_build/html/api/models/ufw.html
new file mode 100644
index 0000000..ab10a66
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/ufw.html
@@ -0,0 +1,662 @@
+
+
+
+
+
+
+
+
+ models.ufw - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.ufw
+
+
+class models.ufw. Action ( * values ) [source]
+Bases: str , Enum
+Enum для действий UFW.
+
+
+ALLOW
+Разрешить трафик
+
+
+
+
+DENY
+Запретить трафик без уведомления
+
+
+
+
+REJECT
+Запретить трафик с уведомлением отправителю
+
+
+
+
+ALLOW = 'allow'
+
+
+
+
+DENY = 'deny'
+
+
+
+
+REJECT = 'reject'
+
+
+
+
+
+
+class models.ufw. Direction ( * values ) [source]
+Bases: str , Enum
+Enum для направления трафика UFW.
+
+
+IN
+Входящий трафик
+
+
+
+
+OUT
+Исходящий трафик
+
+
+
+
+IN = 'in'
+
+
+
+
+OUT = 'out'
+
+
+
+
+
+
+class models.ufw. Protocol ( * values ) [source]
+Bases: str , Enum
+Enum для сетевых протоколов UFW.
+
+
+BOTH = 'both'
+
+
+
+
+TCP = 'tcp'
+
+
+
+
+UDP = 'udp'
+
+
+
+
+property protocols : List [ str ]
+Возвращает список протоколов для использования в команде UFW.
+
+
+
+
+
+
+class models.ufw. UFWRule ( * , action : Action , direction : Direction , from_ip : str , to_ip : str , port : int | str , protocol : Protocol , comment : str | None = None ) [source]
+Bases: BaseModel
+Модель правила UFW
+
+
+action : Action
+
+
+
+
+comment : str | None
+
+
+
+
+direction : Direction
+
+
+
+
+from_ip : str
+
+
+
+
+classmethod from_ufw_output ( line : str ) → UFWRule [source]
+Создает экземпляр UFWRule из строки вывода команды ‘ufw status numbered’.
+
+Parameters:
+line – Строка вывода UFW (например, “[1] 80/tcp ALLOW IN 192.168.1.0/24 # Web Server”)
+
+Returns:
+Новый экземпляр правила
+
+Return type:
+UFWRule
+
+
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+port : int | str
+
+
+
+
+protocol : Protocol
+
+
+
+
+to_ip : str
+
+
+
+
+to_ufw_command ( ) → str [source]
+Преобразует правило в команду UFW.
+
+
+
+
+classmethod validate_action ( v ) [source]
+Приводит значение action к нижнему регистру перед валидацией
+
+
+
+
+classmethod validate_ip ( v ) [source]
+Проверяет корректность IP адреса
+
+
+
+
+classmethod validate_ip_version_compatibility ( v , values ) [source]
+Проверяет совместимость версий IP адресов
+
+
+
+
+classmethod validate_port ( v ) [source]
+Проверяет корректность порта или диапазона портов.
+
+Parameters:
+v – Порт (число) или диапазон портов (строка вида “3000:3999”)
+
+Returns:
+Валидированное значение порта
+
+Return type:
+Union[int, str]
+
+Raises:
+ValueError – Если порт или диапазон портов некорректен
+
+
+
+
+
+
+classmethod validate_protocol ( v ) [source]
+Приводит значение protocol к нижнему регистру перед валидацией
+
+
+
+
+
+
+class models.ufw. UFWRulesResponse ( * , enabled: bool , rules: ~typing.List[~models.ufw.UFWRule] = <factory> ) [source]
+Bases: BaseModel
+Модель для представления списка правил UFW.
+
+
+enabled
+Включен ли UFW
+
+Type:
+bool
+
+
+
+
+
+
+rules
+Список правил UFW
+
+Type:
+List[models.ufw.UFWRule]
+
+
+
+
+
+
+total_rules
+Общее количество правил
+
+
+
+
+enabled : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+rules : List [ UFWRule ]
+
+
+
+
+property total_rules : int
+Возвращает общее количество правил.
+
+
+
+
+
+
+class models.ufw. UFWStatus ( * , enabled : bool , message : str ) [source]
+Bases: BaseModel
+Модель для представления статуса UFW.
+
+
+enabled
+Включен ли UFW
+
+Type:
+bool
+
+
+
+
+
+
+message
+Сообщение о статусе
+
+Type:
+str
+
+
+
+
+
+
+last_checked
+Время последней проверки статуса
+
+
+
+
+enabled : bool
+
+
+
+
+message : str
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/models/wifi.html b/SRV/docs/_build/html/api/models/wifi.html
new file mode 100644
index 0000000..370c6c9
--- /dev/null
+++ b/SRV/docs/_build/html/api/models/wifi.html
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+ models.wifi - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+models.wifi
+
+
+class models.wifi. WifiAccessPoint ( * , ssid : str | None = None , bssid : str , channel : int | None = None , signal : int | None = None , encryption : str | None = None , frequency : str | None = None , vendor : str | None = None , hidden : bool = False , note : str | None = None ) [source]
+Bases: BaseModel
+Модель Wi-Fi точки доступа.
+Атрибуты:
+
+ssid (Optional[str]): Название сети (может быть скрытым).
+bssid (str): Уникальный идентификатор точки доступа.
+channel (Optional[int]): Номер канала.
+signal (Optional[int]): Уровень сигнала в dBm.
+encryption (Optional[str]): Тип шифрования (WPA/WPA2/WPA3/открытая).
+frequency (Optional[str]): Диапазон частот (2.4/5 ГГц).
+vendor (Optional[str]): Производитель оборудования.
+hidden (bool): Флаг скрытого SSID.
+note (Optional[str]): Пользовательская заметка.
+
+
+
+bssid : str
+
+
+
+
+channel : int | None
+
+
+
+
+encryption : str | None
+
+
+
+
+frequency : str | None
+
+
+
+
+hidden : bool
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+note : str | None
+
+
+
+
+signal : int | None
+
+
+
+
+ssid : str | None
+
+
+
+
+vendor : str | None
+
+
+
+
+
+
+class models.wifi. WifiScanResult ( * , access_points : List [ WifiAccessPoint ] ) [source]
+Bases: BaseModel
+Результат сканирования Wi-Fi.
+Атрибуты:
+
+
+
+access_points : List [ WifiAccessPoint ]
+
+
+
+
+model_config : ClassVar [ ConfigDict ] = {}
+Configuration for the model, should be a dictionary conforming to [ConfigDict ][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services.html b/SRV/docs/_build/html/api/services.html
new file mode 100644
index 0000000..4fef3f5
--- /dev/null
+++ b/SRV/docs/_build/html/api/services.html
@@ -0,0 +1,356 @@
+
+
+
+
+
+
+
+
+ Сервисы - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/arp_service.html b/SRV/docs/_build/html/api/services/arp_service.html
new file mode 100644
index 0000000..743cb27
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/arp_service.html
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+ ARP Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ARP Service
+Сервис низкоуровневого ARP-сканирования локальной сети.
+Назначение:
+
+Обнаружение активных узлов в пределах сетей хоста путём широковещательных ARP-запросов от имени выбранного интерфейса.
+Помощь сервисам более высокого уровня (например, IPService) при проверке занятости адресов перед применением конфигураций.
+
+Особенности реализации:
+
+Определяет подключённые сети интерфейса через netifaces и строит CIDR для сканирования.
+Формирует ARP-пакеты с помощью scapy (Ether/ARP) и отправляет их с таймаутом.
+Добавляет локальный адрес интерфейса в результирующий список клиентов.
+
+Требования/ограничения:
+
+
+
+class services.arp_service. NetworkScanner ( interface = None , translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Инициализация сетевого сканера.
+Предоставляет методы для сканирования сети с использованием ARP-запросов и
+вывода информации о клиентах.
+
+
+print_clients ( clients ) [source]
+Выводит список адресов клиентов в формате таблицы.
+
+Parameters:
+clients (List [ dict ] ) – Список словарей с ключами “ip” и “mac”,
+представляющих клиентов.
+
+
+Выводит IP-адреса и MAC-адреса в отсортированном по IP порядке.
+
+
+
+
+scan_network ( attempts = 3 , timeout = 2 ) [source]
+Сканирует сеть для обнаружения активных клиентов.
+
+Parameters:
+
+attempts (int , optional ) – Количество попыток отправки ARP-запросов.
+По умолчанию 3.
+timeout (int , optional ) – Таймаут ожидания ответа в секундах.
+По умолчанию 2.
+
+
+Returns:
+
+Список словарей с ключами “ip” и “mac”, представляющих обнаруженных клиентов, включая локальные IP-адреса.
+
+
+
+
+Return type:
+List[dict]
+
+
+
+
Note
+
Учитывает локальные IP-адреса интерфейса и удаляет дубликаты.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/auth_service.html b/SRV/docs/_build/html/api/services/auth_service.html
new file mode 100644
index 0000000..635ba00
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/auth_service.html
@@ -0,0 +1,391 @@
+
+
+
+
+
+
+
+
+ Auth Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Auth Service
+
+
+class services.auth_service. AuthService ( translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Сервис для регистрации, авторизации и управления сессиями
+
+
+create_access_token ( data : dict ) → str [source]
+Создаёт токен
+
+
+
+
+get_current_user ( token : str = Depends(OAuth2PasswordBearer) ) → UserResponse [source]
+Получает текущего пользователя по токену
+
+
+
+
+get_user_by_session ( client_id : str ) → dict [source]
+Получает пользователя и токен по client_id
+
+
+
+
+login ( user : UserLogin , client_id : str ) → dict [source]
+Авторизует пользователя, создаёт сессию и возвращает токен
+
+
+
+
+logout ( client_id : str ) [source]
+Выполняет выход из системы, удаляя последнюю сессию для client_id
+
+
+
+
+register ( user : UserCreate ) → UserResponse [source]
+Регистрирует нового пользователя
+
+
+
+
+verify_token ( token : str ) → dict | None [source]
+Проверяет токен
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/connect_service.html b/SRV/docs/_build/html/api/services/connect_service.html
new file mode 100644
index 0000000..1faf2c9
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/connect_service.html
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+ Connect Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Connect Service
+Сервис управления подключениями Wi-Fi через nmcli .
+Назначение:
+
+Поведение:
+
+Автоматически определяет активный Wi-Fi-интерфейс.
+Валидирует наличие SSID в листинге перед подключением и интерпретирует частые ошибки nmcli (неверный пароль, отсутствующая сеть и т.п.).
+
+Ограничения:
+
+
+
+class services.connect_service. WifiConnectionService ( translations : Translations | None = None ) [source]
+Bases: object
+Сервис для управления подключением к Wi-Fi сетям с использованием nmcli.
+Предоставляет методы для подключения, отключения и проверки статуса Wi-Fi соединений.
+
+
+connect ( ssid : str , password : str ) → Tuple [ bool , str | None ] [source]
+Подключается к Wi-Fi сети по SSID и паролю.
+
+Parameters:
+
+
+Returns:
+
+Кортеж (успех, сообщение об ошибке).
+
+
+
+
+Return type:
+Tuple[bool, Optional[str]]
+
+Raises:
+
+
+
+
+
+
+
+disconnect ( ssid : str ) → Tuple [ bool , str | None ] [source]
+Отключается от Wi-Fi сети по указанному SSID.
+
+Parameters:
+ssid (str ) – Идентификатор сети Wi-Fi для отключения.
+
+Returns:
+
+Кортеж (успех, сообщение об ошибке).
+Успех: (True, None или сообщение, если подключение не найдено).
+Ошибка: (False, сообщение об ошибке).
+
+
+
+
+
+Return type:
+Tuple[bool, Optional[str]]
+
+Raises:
+
+
+
+
+
+
+
+get_status ( ) → Tuple [ bool , str | None ] [source]
+Проверяет статус текущего подключения к Wi-Fi.
+
+Returns:
+
+Кортеж (успех, SSID или сообщение об ошибке).
+Успех: (True, SSID текущей сети).
+Ошибка или отсутствие подключения: (False, сообщение об ошибке).
+
+
+
+
+
+Return type:
+Tuple[bool, Optional[str]]
+
+Raises:
+Exception – Если произошла ошибка при выполнении команды nmcli.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/firewalld_service.html b/SRV/docs/_build/html/api/services/firewalld_service.html
new file mode 100644
index 0000000..aef820d
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/firewalld_service.html
@@ -0,0 +1,442 @@
+
+
+
+
+
+
+
+
+ Firewalld Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Firewalld Service
+Сервис для управления firewalld через D-Bus.
+Назначение:
+
+Обёртка над интерфейсами org.fedoraproject.FirewallD1 для выполнения типовых операций по зонам: просмотр/изменение дефолтной зоны, добавление и удаление сервисов, портов, источников, перезагрузка конфигурации.
+
+Примечания:
+
+Используется pydbus.SystemBus для доступа к системной шине.
+Методы генерируют дружелюбные исключения при ошибках, содержащие контекст операции.
+
+
+
+class services.firewalld_service. FirewalldService [source]
+Bases: object
+
+
+add_port ( zone : str , port : int , protocol : str , timeout : int | None = 0 ) → None [source]
+Add a port to a zone.
+
+
+
+
+add_service ( zone : str , service : str , timeout : int | None = 0 ) → None [source]
+Add a service to a zone.
+
+
+
+
+add_source ( zone : str , source : str ) → None [source]
+Add a source to a zone.
+
+
+
+
+get_default_zone ( ) → str [source]
+Get the default zone.
+
+
+
+
+get_ports ( zone : str ) → List [ str ] [source]
+Get list of ports in a zone.
+
+
+
+
+get_services ( zone : str ) → List [ str ] [source]
+Get list of services in a zone.
+
+
+
+
+get_sources ( zone : str ) → List [ str ] [source]
+Get list of sources in a zone.
+
+
+
+
+get_zone_settings ( zone : str ) → Dict [source]
+Get all settings for a zone.
+
+
+
+
+get_zones ( ) → List [ str ] [source]
+Get list of all zones.
+
+
+
+
+reload ( ) → None [source]
+Reload firewall rules.
+
+
+
+
+remove_port ( zone : str , port : int , protocol : str ) → None [source]
+Remove a port from a zone.
+
+
+
+
+remove_service ( zone : str , service : str ) → None [source]
+Remove a service from a zone.
+
+
+
+
+remove_source ( zone : str , source : str ) → None [source]
+Remove a source from a zone.
+
+
+
+
+set_default_zone ( zone : str ) → None [source]
+Set the default zone.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/ip_service.html b/SRV/docs/_build/html/api/services/ip_service.html
new file mode 100644
index 0000000..a73a22c
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/ip_service.html
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+ IP Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+IP Service
+Сервис проверки и применения IP-настроек через NetworkManager (D-Bus).
+Назначение:
+
+Перед применением конфигурации проверяет занятость IP адреса, сканируя сегмент сети средствами ARP (через NetworkScanner ).
+Применяет изменения профиля подключения через sdbus_async.networkmanager и выполняет переподключение интерфейса.
+
+Требования/ограничения:
+
+
+
+class services.ip_service. IPService [source]
+Bases: object
+Сервис проверки доступности IP-адреса и применения настроек сети.
+Предоставляет асинхронные методы для проверки занятости IP-адреса с использованием
+ARP-сканирования и применения настроек через NetworkManager.
+
+
+async apply_ip_config ( args : Dict [ str , Any ] , translations : Translations = Depends(translations_dependency) ) → None [source]
+Применяет проверенные настройки IP к указанному соединению.
+Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
+отключает и переподключает устройство к сети.
+
+Parameters:
+
+args (Dict [ str , Any ] ) – Словарь с данными, включая:
+- “connection_id” (str): Идентификатор соединения.
+- “ipv4_updates” (Dict): Обновления настроек IPv4.
+translations (Translations ) – Объект переводов.
+
+
+Raises:
+
+
+
+
+
Note
+
Требуются права для изменения настроек через NetworkManager.
+
+
+
+
+
+async check_ip_config ( args : Dict [ str , Any ] , translations : Translations = Depends(translations_dependency) ) → Dict [ str , Any ] [source]
+Проверяет доступность IP-адреса перед его применением.
+Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
+Перезапускает шину D-Bus перед выполнением.
+
+Parameters:
+
+args (Dict [ str , Any ] ) – Словарь с данными, включая “ipv4_updates”
+с полем “ipv4.address-data” для нового IP-адреса.
+translations (Translations ) – Объект переводов.
+
+
+Returns:
+
+Словарь с ключами:
+
+
+
+
+Return type:
+Dict[str, Any]
+
+Raises:
+Exception – Если произошла ошибка при сканировании ARP.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/network_service.html b/SRV/docs/_build/html/api/services/network_service.html
new file mode 100644
index 0000000..371868d
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/network_service.html
@@ -0,0 +1,634 @@
+
+
+
+
+
+
+
+
+ Network Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Network Service
+Сервис чтения сетевого состояния через NetworkManager (D-Bus).
+Назначение:
+
+Предоставляет асинхронные методы для сборки унифицированных моделей сетевых интерфейсов (Ethernet/Wi-Fi), их IP-конфигураций, а также получения имени хоста. Используется API-слоем для визуализации и управления.
+
+Особенности:
+
+Фильтрация loopback и явно неактивных интерфейсов.
+Адаптация данных D-Bus в pydantic-модели (NetworkInterface , WiFiDevice , NetworkInterfacesContainer ).
+
+
+
+class services.network_service. NetworkService ( translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Сервис для взаимодействия с NetworkManager через D-Bus.
+Предоставляет асинхронные методы для получения информации о сетевых интерфейсах,
+их конфигурациях IP, Wi-Fi и состояниях.
+
+
+async get_activated_interfaces ( initial_positions : Dict [ str , Tuple [ int , int ] ] = None ) → List [ NetworkInterface ] [source]
+Получает список активных сетевых интерфейсов.
+
+Parameters:
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+
+Returns:
+Список активных интерфейсов (state == “100”).
+
+Return type:
+List[NetworkInterface]
+
+
+
+
+
+
+async get_activated_interfaces_container ( initial_positions : Dict [ str , Tuple [ int , int ] ] = None , container_x : int = 0 , container_y : int = 0 ) → NetworkInterfacesContainer [source]
+Получает объект, содержащий список активных интерфейсов и координаты контейнера.
+
+Parameters:
+
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+container_x (int , optional ) – Координата X контейнера. По умолчанию 0.
+container_y (int , optional ) – Координата Y контейнера. По умолчанию 0.
+
+
+Returns:
+Объект с активными интерфейсами и координатами.
+
+Return type:
+NetworkInterfacesContainer
+
+
+
+
+
+
+async get_connection_name ( device : sdbus_async.networkmanager.NetworkDeviceGeneric ) → str [source]
+Извлекает имя соединения (GENERAL.CONNECTION) для устройства.
+
+Parameters:
+device (NetworkDeviceGeneric ) – Объект устройства NetworkManager.
+
+Returns:
+Имя соединения или “unknown” в случае ошибки.
+
+Return type:
+str
+
+Raises:
+Exception – Если произошла ошибка при получении настроек соединения.
+
+
+
+
+
+
+async get_deactivated_interfaces ( initial_positions : Dict [ str , Tuple [ int , int ] ] = None ) → List [ NetworkInterface ] [source]
+Получает список неактивных сетевых интерфейсов.
+
+Parameters:
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+
+Returns:
+Список интерфейсов с state != “100”.
+
+Return type:
+List[NetworkInterface]
+
+
+
+
+
+
+async get_deactivated_interfaces_container ( initial_positions : Dict [ str , Tuple [ int , int ] ] = None , container_x : int = 0 , container_y : int = 0 ) → NetworkInterfacesContainer [source]
+Получает объект, содержащий список неактивных интерфейсов и координаты контейнера.
+
+Parameters:
+
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+container_x (int , optional ) – Координата X контейнера. По умолчанию 0.
+container_y (int , optional ) – Координата Y контейнера. По умолчанию 0.
+
+
+Returns:
+Объект с неактивными интерфейсами и координатами.
+
+Return type:
+NetworkInterfacesContainer
+
+
+
+
+
+
+async get_hostname ( ) → Dict [ str , str ] [source]
+Возвращает имя хоста из команды hostname в формате JSON.
+
+Returns:
+Словарь с ключом “hostname” и значением имени хоста.
+
+Return type:
+Dict[str, str]
+
+Raises:
+RuntimeError – Если произошла ошибка при выполнении команды hostname
+ или обработке вывода.
+
+
+
+
+
+
+async get_interfaces_by_state ( state : str , initial_positions : Dict [ str , Tuple [ int , int ] ] = None ) → List [ NetworkInterface ] [source]
+Получает список интерфейсов по указанному состоянию.
+
+Parameters:
+
+state (str ) – Состояние интерфейса (например, “100” для активных).
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+
+
+Returns:
+Список интерфейсов с указанным состоянием.
+
+Return type:
+List[NetworkInterface]
+
+
+
+
+
+
+async get_interfaces_container ( state : str | None = None , initial_positions : Dict [ str , Tuple [ int , int ] ] = None , container_x : int = 0 , container_y : int = 0 ) → NetworkInterfacesContainer [source]
+Получает объект, содержащий список интерфейсов и координаты контейнера.
+
+Parameters:
+
+state (Optional [ str ] , optional ) – Состояние интерфейсов (например, “100”).
+По умолчанию None (все интерфейсы).
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None.
+container_x (int , optional ) – Координата X контейнера. По умолчанию 0.
+container_y (int , optional ) – Координата Y контейнера. По умолчанию 0.
+
+
+Returns:
+Объект с интерфейсами и координатами контейнера.
+
+Return type:
+NetworkInterfacesContainer
+
+
+
+
+
+
+async get_ip_config ( device : sdbus_async.networkmanager.NetworkDeviceGeneric , ip_version : str ) → List [ IndividualIPConfig ] [source]
+Получает конфигурацию IP для устройства.
+
+Parameters:
+
+
+Returns:
+Список конфигураций IP, включая адреса и маршруты.
+
+Return type:
+List[IndividualIPConfig]
+
+Raises:
+Exception – Если произошла ошибка при получении данных IP.
+
+
+
+
+
+
+async get_ip_method ( device : sdbus_async.networkmanager.NetworkDeviceGeneric , ip_type : str ) → str [source]
+Получает метод IP (например, “auto”, “manual”) для указанного устройства.
+
+Parameters:
+
+device (NetworkDeviceGeneric ) – Объект устройства NetworkManager.
+ip_type (str ) – Тип IP (например, “ipv4” или “ipv6”).
+
+
+Returns:
+Метод IP или “unknown” в случае ошибки.
+
+Return type:
+str
+
+Raises:
+Exception – Если произошла ошибка при получении настроек соединения.
+
+
+
+
+
+
+async get_network_interfaces ( initial_positions : Dict [ str , Tuple [ int , int ] ] = None ) → List [ NetworkInterface | WiFiDevice ] [source]
+Получает список сетевых интерфейсов.
+
+Parameters:
+initial_positions (Dict [ str , Tuple [ int , int ] ] , optional ) – Словарь начальных
+координат (x, y) для интерфейсов. По умолчанию None (используются (100, 100)).
+
+Returns:
+
+Список объектов интерфейсов, включая Wi-Fi устройства.
+
+
+
+
+Return type:
+List[Union[NetworkInterface, WiFiDevice]]
+
+
+Исключает интерфейсы типа LOOPBACK и неактивные состояния (state == “10”).
+
+
+
+
+async get_network_interfaces_container ( initial_positions : Dict [ str , Tuple [ int , int ] ] | None = None ) → NetworkInterfacesContainer [source]
+Получает контейнер с сетевыми интерфейсами и координатами контейнера.
+
+Parameters:
+initial_positions (Optional [ Dict [ str , Tuple [ int , int ] ] ] , optional ) – Словарь начальных координат (x, y) для интерфейсов. По умолчанию None.
+
+Returns:
+Объект с интерфейсами и координатами (0, 0).
+
+Return type:
+NetworkInterfacesContainer
+
+
+
+
+
+
+async get_wifi_info ( wifi_device : sdbus_async.networkmanager.NetworkDeviceWireless ) → Dict [ str , str | int | None ] [source]
+Получает информацию о Wi-Fi устройстве.
+
+Parameters:
+wifi_device (NetworkDeviceWireless ) – Объект Wi-Fi устройства NetworkManager.
+
+Returns:
+
+Словарь с данными Wi-Fi (SSID, BSSID, bitrate, strength, frequency, mode), где значения могут быть None.
+
+
+
+
+Return type:
+Dict[str, Optional[Union[str, int]]]
+
+Raises:
+Exception – Если произошла ошибка при получении данных точки доступа.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/settings_service.html b/SRV/docs/_build/html/api/services/settings_service.html
new file mode 100644
index 0000000..19a2f1f
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/settings_service.html
@@ -0,0 +1,404 @@
+
+
+
+
+
+
+
+
+ Settings Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Settings Service
+
+
+class services.settings_service. SettingsService ( translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Сервис для управления настройками и очистки данных сети.
+
+
+clear_all_data ( ) → bool [source]
+Очищает данные узлов всех подсетей и точек доступа Wi-Fi.
+
+Returns:
+True, если очистка успешна.
+
+Return type:
+bool
+
+Raises:
+Exception – Если произошла ошибка при записи в один из файлов.
+
+
+
+
+
+
+clear_subnet ( subnet : str | None = None ) → bool [source]
+Очищает данные узлов подсети в указанном файле или во всех файлах.
+
+Parameters:
+subnet (Optional [ str ] ) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+Если None, очищает все подсети.
+
+Returns:
+True, если очистка успешна.
+
+Return type:
+bool
+
+Raises:
+Exception – Если произошла ошибка при записи в файл.
+
+
+
+
+
+
+clear_wifi ( ) → bool [source]
+Очищает данные точек доступа Wi-Fi в файле wifi.json.
+
+Returns:
+True, если очистка успешна.
+
+Return type:
+bool
+
+Raises:
+Exception – Если произошла ошибка при записи в файл.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/subnet_service.html b/SRV/docs/_build/html/api/services/subnet_service.html
new file mode 100644
index 0000000..cbb7a17
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/subnet_service.html
@@ -0,0 +1,711 @@
+
+
+
+
+
+
+
+
+ Subnet Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Subnet Service
+
+
+class services.subnet_service. DeviceInfoCollector ( translations : Translations ) [source]
+Bases: object
+Класс для сбора информации об устройствах, включая вендоров и хостнеймы.
+
+Источники данных:
+MacLookup: сопоставление префикса MAC (OUI) с названием производителя.
+DNS: gethostbyaddr с малым таймаутом для обратного разрешения имени.
+NetBIOS (nbtscan): опрос сегмента сети для извлечения NetBIOS-имён.
+
+
+Политика кэширования:
+
+
+
+
+get_hostname ( ip : str ) → str | None [source]
+Получает хостнейм для указанного IP-адреса.
+Пробует сначала DNS, затем NetBIOS.
+
+Parameters:
+ip (str ) – IP-адрес устройства.
+
+Returns:
+Хостнейм или None, если не удалось определить.
+
+Return type:
+Optional[str]
+
+
+
+
+
+
+get_vendor ( mac : str ) → str [source]
+Получает вендора устройства по MAC-адресу.
+Использует кэширование для повышения производительности.
+
+Parameters:
+mac (str ) – MAC-адрес устройства.
+
+Returns:
+Название вендора или “Неизвестный” в случае ошибки.
+
+Return type:
+str
+
+
+
+
+
+
+scan_nbtscan ( subnet : str ) → None [source]
+Сканирует подсеть с помощью nbtscan для получения хостнеймов.
+Сохраняет результаты в кэш и логирует процесс.
+
+Parameters:
+subnet (str ) – Подсеть в формате CIDR.
+
+
+
+
+
+
+
+
+class services.subnet_service. DeviceStorage ( base_path : str = 'storage/subnets' , translations : Translations = None ) [source]
+Bases: object
+Класс для хранения и управления данными об устройствах.
+Формат файла подсети:
+{
+ "devices" : [ Device , ... ]
+}
+
+
+Политика очистки:
+
+
+
+get_subnet_file ( subnet : str ) → Path [source]
+Возвращает путь к файлу для конкретной подсети.
+Заменяет символы ‘/’ на ‘_’ для корректного имени файла.
+
+Parameters:
+subnet (str ) – Подсеть в формате CIDR.
+
+Returns:
+Путь к файлу.
+
+Return type:
+Path
+
+
+
+
+
+
+load_devices ( subnet : str ) → Dict [ str , Device ] [source]
+Загружает устройства для указанной подсети.
+Читает данные из JSON-файла и фильтрует устаревшие устройства.
+
+Parameters:
+subnet (str ) – Подсеть в формате CIDR.
+
+Returns:
+Словарь устройств с MAC как ключом.
+
+Return type:
+Dict[str, Device]
+
+
+
+
+
+
+save_devices ( subnet : str , devices : List [ Device ] ) → None [source]
+Сохраняет устройства для указанной подсети.
+Записывает данные в JSON-файл.
+
+Parameters:
+
+
+Raises:
+Exception – Если произошла ошибка при сохранении.
+
+
+
+
+
+
+
+
+class services.subnet_service. NetworkScanner ( interface : str , translations : Translations ) [source]
+Bases: object
+Класс для сканирования подсети с использованием ARP-запросов.
+
+Назначение: Обнаруживает активные устройства в локальной сети, отправляя широковещательные
+ARP-запросы по указанной подсети и собирая ответы (IP, MAC).
+
+Детали реализации:
+Использует scapy (Ether + ARP ) для генерации пакетов и srp для
+отправки/получения на канальном уровне от имени заданного интерфейса.
+Таймаут ожидания ответа короткий (2 секунды), ориентирован на интерактивные
+сценарии UI.
+
+
+Ограничения и замечания:
+
+
+
+
+scan ( subnet : str ) → List [ Tuple [ str , str ] ] [source]
+Сканирует подсеть с использованием ARP-запросов.
+Отправляет ARP-пакеты для обнаружения активных устройств и возвращает
+список кортежей (IP, MAC).
+
+Parameters:
+subnet (str ) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+
+Returns:
+Список кортежей (IP-адрес, MAC-адрес) обнаруженных устройств.
+
+Return type:
+List[Tuple[str, str]]
+
+Raises:
+Exception – Если произошла ошибка сканирования.
+
+
+
+
+
+
+
+
+class services.subnet_service. PortScanner ( interface : str , translations : Translations ) [source]
+Bases: object
+Класс для сканирования открытых портов на устройствах.
+
+Назначение: Плейсхолдер для будущего функционала активного сканирования TCP/UDP-портов
+у обнаруженных узлов. В текущей версии возвращает пустой список.
+
+План развития:
+Добавить неблокирующее сканирование наиболее распространённых портов
+(22, 80, 443 и т.д.) с ограничением по времени.
+Настраиваемые профили глубины и скорости сканирования.
+
+
+
+
+
+scan ( ip : str ) → List [ str ] [source]
+Сканирует открытые порты на указанном IP-адресе.
+Пока возвращает пустой список, так как функциональность не реализована.
+
+Parameters:
+ip (str ) – IP-адрес устройства для сканирования.
+
+Returns:
+Список открытых портов (пока пустой).
+
+Return type:
+List[str]
+
+
+
+
+
+
+
+
+class services.subnet_service. SubnetService ( translations : Translations ) [source]
+Bases: object
+Сервис для сканирования подсетей и управления устройствами.
+
+Роль сервиса: Объединяет сетевое сканирование (ARP), сбор дополнительной информации
+(вендор, хостнейм), слияние с кэшем предыдущих результатов и персистентное
+хранение данных по подсетям. Предоставляет вспомогательные операции для
+локального узла и работы с заметками устройств.
+
+Поток выполнения скана (scan_subnet ):
+Определение подсети (вручную или авто по интерфейсу).
+Загрузка ранее сохранённых устройств подсети.
+Добавление локального устройства (если принадлежит подсети).
+ARP-сканирование и параллельная обработка узлов (обогащение данных).
+Маркировка оффлайн-узлов из кэша, не встреченных в текущем скане.
+Сортировка по IP и сохранение объединённого списка в JSON.
+
+
+
+
+
+add_note ( mac : str , note : str , translations : Translations ) → Tuple [ bool , Tuple [ str , Dict ] | None ] [source]
+Добавляет заметку для устройства по MAC-адресу.
+Привязывает заметку к устройству, если оно существует.
+
+Parameters:
+
+mac (str ) – MAC-адрес устройства.
+note (str ) – Текст заметки.
+translations (Translations ) – Объект переводов для текущего языка.
+
+
+Returns:
+
+Кортеж (успех, результат), где результат содержит код ошибки/успеха и данные.
+
+
+
+
+Return type:
+Tuple[bool, Optional[Tuple[str, Dict]]]
+
+
+
+
+
+
+delete_note ( mac : str , translations : Translations ) → Tuple [ bool , Tuple [ str , Dict ] | None ] [source]
+Удаляет заметку для устройства по MAC-адресу.
+Удаляет привязанную заметку, если она существует.
+
+Parameters:
+
+
+Returns:
+
+Кортеж (успех, результат), где результат содержит код ошибки/успеха и данные.
+
+
+
+
+Return type:
+Tuple[bool, Optional[Tuple[str, Dict]]]
+
+
+
+
+
+
+scan_subnet ( subnet : str | None = None ) → SubnetScanResult [source]
+Сканирует указанную подсеть и возвращает результаты.
+Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные.
+
+Parameters:
+subnet (Optional [ str ] ) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+Если None или пустая строка, определяется автоматически.
+
+Returns:
+
+Объект с результатами сканирования, содержащий список устройств или сообщение об ошибке.
+
+
+
+
+Return type:
+SubnetScanResult
+
+Raises:
+Exception – Если произошла ошибка сканирования.
+
+
+
+
+
+
+update_note ( mac : str , note : str , translations : Translations ) → Tuple [ bool , Tuple [ str , Dict ] | None ] [source]
+Обновляет заметку для устройства по MAC-адресу.
+Изменяет существующую заметку, если она есть.
+
+Parameters:
+
+mac (str ) – MAC-адрес устройства.
+note (str ) – Новый текст заметки.
+translations (Translations ) – Объект переводов для текущего языка.
+
+
+Returns:
+
+Кортеж (успех, результат), где результат содержит код ошибки/успеха и данные.
+
+
+
+
+Return type:
+Tuple[bool, Optional[Tuple[str, Dict]]]
+
+
+
+
+
+
+
+
+services.subnet_service. get_subnet_service ( translations : Translations = Depends(translations_dependency) ) [source]
+Фабричная функция для создания экземпляра SubnetService.
+Использует зависимости FastAPI для получения объекта переводов и возвращает
+экземпляр SubnetService с текущей локализацией.
+
+Parameters:
+translations (Translations ) – Объект переводов для текущего языка.
+
+Returns:
+Экземпляр сервиса для работы с подсетями.
+
+Return type:
+SubnetService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/ufw_service.html b/SRV/docs/_build/html/api/services/ufw_service.html
new file mode 100644
index 0000000..ac030a8
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/ufw_service.html
@@ -0,0 +1,506 @@
+
+
+
+
+
+
+
+
+ UFW Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+UFW Service
+
+
+class services.ufw_service. UFWService [source]
+Bases: object
+Сервис взаимодействия с UFW (Uncomplicated Firewall).
+
+Назначение:
+Чтение статуса и списка правил брандмауэра.
+Добавление, удаление и массовая обработка правил с поддержкой диапазонов портов,
+комментариев и выбором протоколов.
+
+
+Детали реализации:
+Команды выполняются через sudo ufw … ; вывод парсится в модели pydantic.
+Для операций удаления соблюдается порядок с конца, чтобы не нарушать нумерацию.
+
+
+
+
+
+add_rules ( rules : List [ UFWRule ] ) → dict [source]
+Добавляет правила UFW.
+
+Parameters:
+rules – Список правил для добавления
+
+Returns:
+Результат добавления правил
+
+Return type:
+dict
+
+
+
+
+
+
+add_rules_base ( rules : List [ UFWRule ] , direction : str = '' ) → bool [source]
+Базовый метод для добавления правил UFW.
+
+Parameters:
+
+
+Returns:
+True если все правила были успешно добавлены, False в противном случае
+
+Return type:
+bool
+
+
+
+
+
+
+add_ufw_in_rules ( rules : List [ UFWRule ] ) → bool [source]
+Добавляет входящие (in) правила UFW.
+
+Parameters:
+rules (List [ UFWRule ] ) – Список правил для добавления
+
+Returns:
+True если все правила были успешно добавлены, False в противном случае
+
+Return type:
+bool
+
+
+
+
+
+
+add_ufw_out_rules ( rules : List [ UFWRule ] ) → bool [source]
+Добавляет исходящие (out) правила UFW.
+
+Parameters:
+rules (List [ UFWRule ] ) – Список правил для добавления
+
+Returns:
+True если все правила были успешно добавлены, False в противном случае
+
+Return type:
+bool
+
+
+
+
+
+
+delete_rules ( rule_numbers : List [ int ] ) → dict [source]
+Удаляет правила UFW по их номерам.
+
+Parameters:
+rule_numbers (List [ int ] ) – Список номеров правил для удаления
+
+Returns:
+Результат операции удаления
+
+Return type:
+dict
+
+
+
+
+
+
+disable_ufw ( ) → bool [source]
+Выключает UFW.
+
+Returns:
+True если успешно выключен, False в противном случае
+
+Return type:
+bool
+
+
+
+
+
+
+enable_ufw ( ) → bool [source]
+Включает UFW.
+
+Returns:
+True если успешно включен, False в противном случае
+
+Return type:
+bool
+
+
+
+
+
+
+get_ufw_rules ( ) → UFWRulesResponse [source]
+Получает список правил UFW.
+
+Returns:
+Объект, содержащий статус UFW и список правил
+
+Return type:
+UFWRulesResponse
+
+
+
+
+
+
+get_ufw_status ( ) → UFWStatus [source]
+Проверяет статус UFW.
+
+Returns:
+Объект с информацией о статусе UFW
+
+Return type:
+UFWStatus
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/services/wifi_service.html b/SRV/docs/_build/html/api/services/wifi_service.html
new file mode 100644
index 0000000..4270e9d
--- /dev/null
+++ b/SRV/docs/_build/html/api/services/wifi_service.html
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+ Wifi Service - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Wifi Service
+
+
+class services.wifi_service. WifiService ( translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Сервис для сканирования текущих Wi-Fi точек доступа и заметок.
+
+Источники данных:
+pywifi для базового сканирования и получения списка AP.
+iw dev <iface> scan для уточнения частоты, сигнала и безопасности.
+MacLookup для определения вендора по BSSID.
+
+
+Политики:
+Выбор лучшего AP по SSID по уровню сигнала.
+Кэширование определения вендора.
+Сохранение SSID даже без заметок для будущих операций.
+
+
+
+
+
+add_note ( ssid : str , note : str ) → Tuple [ bool , str | None ] [source]
+Добавляет заметку к сети Wi-Fi по SSID.
+
+
+
+
+delete_note ( ssid : str ) → Tuple [ bool , str | None ] [source]
+Удаляет заметку сети Wi-Fi по SSID.
+
+
+
+
+get_vendor ( mac : str ) → str [source]
+Определяет вендора по MAC-адресу с использованием кэширования.
+
+
+
+
+scan_wifi ( timeout : int = 30 ) → WifiScanResult [source]
+Сканирует текущие Wi-Fi точки доступа.
+
+
+
+
+update_note ( ssid : str , note : str ) → Tuple [ bool , str | None ] [source]
+Обновляет заметку сети Wi-Fi по SSID.
+
+
+
+
+
+
+class services.wifi_service. WifiStorage ( storage_path : str = 'storage/wifi/wifi.json' , translations : Translations = Depends(translations_dependency) ) [source]
+Bases: object
+Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.
+Формат:
+{
+ "networks" : [{ "ssid" : str , "note" : Optional [ str ]}]
+}
+
+
+Поведение:
+
+Создаёт директорию хранения при инициализации, ведёт логирование путей.
+Валидирует структуру при чтении и безопасно записывает JSON с Unicode и отступами.
+
+
+
+load_notes ( ) → Dict [ str , str | None ] [source]
+Загружает заметки по SSID из файла хранения.
+
+
+
+
+save_notes ( notes : Dict [ str , str | None ] ) → None [source]
+Сохраняет заметки по SSID в файл.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/storage.html b/SRV/docs/_build/html/api/storage.html
new file mode 100644
index 0000000..23a89a3
--- /dev/null
+++ b/SRV/docs/_build/html/api/storage.html
@@ -0,0 +1,348 @@
+
+
+
+
+
+
+
+
+ Хранилища - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/storage/json_storage.html b/SRV/docs/_build/html/api/storage/json_storage.html
new file mode 100644
index 0000000..16b9c50
--- /dev/null
+++ b/SRV/docs/_build/html/api/storage/json_storage.html
@@ -0,0 +1,437 @@
+
+
+
+
+
+
+
+
+ storage.json_storage - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+storage.json_storage
+
+
+class storage.json_storage. JsonStorage ( file_path : str | None = None ) [source]
+Bases: object
+Хранилище пользователей в JSON-файле.
+Назначение:
+
+Обеспечивает простое файловое хранилище учетных записей без полноценной СУБД.
+Подходит для встраиваемых окружений, где критична минимальная зависимость от внешних сервисов.
+
+Формат данных:
+{
+ "users" : [
+ { "id" : int , "username" : str , "password_hash" : str , "is_admin" : bool },
+ ...
+ ],
+ "next_id" : int
+}
+
+
+Безопасность:
+
+Ограничения:
+
+Нет блокировок для конкурентного доступа — в многопроцессной среде требуется внешняя синхронизация.
+Значение пути по умолчанию /etc/gonets/users.json предполагает наличие прав на запись и предварительную настройку окружения.
+
+
+
+create_user ( user : UserCreate ) → UserResponse [source]
+Создаёт нового пользователя с хешированием пароля.
+
+Parameters:
+user (UserCreate ) – Объект с данными нового пользователя (username, password, is_admin).
+
+Returns:
+Объект с данными созданного пользователя (id, username, is_admin).
+
+Return type:
+UserResponse
+
+Raises:
+
+
+
+
+
+
+
+get_user_by_username ( username : str ) → dict | None [source]
+Получает данные пользователя по имени.
+
+Parameters:
+username (str ) – Имя пользователя для поиска.
+
+Returns:
+Словарь с данными пользователя или None, если пользователь не найден.
+
+Return type:
+Optional[dict]
+
+Raises:
+Exception – Если произошла ошибка при чтении файла.
+
+
+
+
+
+
+verify_password ( plain_password : str , hashed_password : str ) → bool [source]
+Проверяет соответствие введённого пароля хэшированному.
+
+Parameters:
+
+
+Returns:
+True, если пароль верен, False в противном случае.
+
+Return type:
+bool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/storage/session_storage.html b/SRV/docs/_build/html/api/storage/session_storage.html
new file mode 100644
index 0000000..b4716ea
--- /dev/null
+++ b/SRV/docs/_build/html/api/storage/session_storage.html
@@ -0,0 +1,470 @@
+
+
+
+
+
+
+
+
+ storage.session_storage - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+storage.session_storage
+
+
+class storage.session_storage. SessionStorage ( file_path : str | None = None ) [source]
+Bases: object
+Хранилище сессий пользователей в JSON-файле.
+Назначение:
+
+Лёгкое файловое хранилище сессионных токенов, сопоставленных с client_id (например, IP клиента).
+Предназначено для сред без БД и persistent-кэшей; обеспечивает простые операции и автоочистку истёкших сессий.
+
+Формат данных:
+{
+ "sessions" : {
+ "<session_id>" : {
+ "username" : str ,
+ "token" : str ,
+ "client_id" : str ,
+ "created_at" : ISO8601 str ,
+ "expiry" : ISO8601 str
+ },
+ ...
+ }
+}
+
+
+Политика TTL:
+
+Ограничения:
+
+
+
+clean_expired_sessions ( ) [source]
+Удаляет все истекшие сессии из хранилища.
+
+Raises:
+Exception – Если произошла ошибка при чтении или записи данных.
+
+
+
+
Note
+
Проверяет срок действия каждой сессии и удаляет те, что истекли,
+записывая обновлённые данные.
+
+
+
+
+
+create_session ( username : str , token : str , client_id : str ) → str [source]
+Создаёт новую сессию и возвращает уникальный session_id.
+
+Parameters:
+
+username (str ) – Имя пользователя, для которого создаётся сессия.
+token (str ) – Токен аутентификации.
+client_id (str ) – Идентификатор клиента.
+
+
+Returns:
+Уникальный идентификатор сессии (session_id).
+
+Return type:
+str
+
+Raises:
+Exception – Если произошла ошибка при создании или записи сессии.
+
+
+
+
Note
+
Удаляет истекшие сессии перед созданием новой и устанавливает срок
+действия 30 минут.
+
+
+
+
+
+delete_session ( session_id : str ) [source]
+Удаляет сессию по указанному session_id.
+
+Parameters:
+session_id (str ) – Уникальный идентификатор сессии.
+
+Raises:
+Exception – Если произошла ошибка при чтении или записи данных.
+
+
+
+
Note
+
Очищает истекшие сессии перед удалением.
+
+
+
+
+
+get_latest_session ( client_id : str ) → Dict | None [source]
+Получает последнюю активную сессию для указанного client_id.
+
+Parameters:
+client_id (str ) – Идентификатор клиента.
+
+Returns:
+
+Словарь с данными последней активной сессии, включая ”session_id”, или None, если сессия не найдена.
+
+
+
+
+Return type:
+Optional[Dict]
+
+Raises:
+Exception – Если произошла ошибка при чтении или обработке данных.
+
+
+
+
Note
+
Очищает истекшие сессии перед поиском и возвращает сессию с самым
+поздним временем создания.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/utils.html b/SRV/docs/_build/html/api/utils.html
new file mode 100644
index 0000000..8e3c4fe
--- /dev/null
+++ b/SRV/docs/_build/html/api/utils.html
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+ Утилиты - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/api/utils/i18n.html b/SRV/docs/_build/html/api/utils/i18n.html
new file mode 100644
index 0000000..9bd4023
--- /dev/null
+++ b/SRV/docs/_build/html/api/utils/i18n.html
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+ utils.i18n - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+utils.i18n
+Утилиты локализации и зависимости переводов для FastAPI.
+
+Содержимое модуля:
+get_translations() : фабрика зависимости FastAPI, считывающая локаль из
+query-параметра locale , cookie locale или заголовка Accept-Language
+(по приоритету), с дефолтом ru . Возвращает функцию-зависимость, которая
+подгружает babel.support.Translations из каталога SRV/locales .
+_() : удобная обёртка для получения перевода строки с использованием
+текущей локали (или дефолтного ru , если контекст запроса не передан).
+
+
+Особенности:
+При отсутствии словаря перевода для выбранной локали выполняется fallback на
+русскую локаль, что гарантирует наличие строк интерфейса.
+Локаль определяется только по первому языку из Accept-Language (без
+региональных суффиксов) для упрощения.
+Модуль ведёт отладочное логирование выбора и загрузки локали.
+
+
+
+
+
+utils.i18n. get_translations ( ) → callable [source]
+Создаёт зависимость для загрузки переводов на основе заголовка Accept-Language
+или параметра запроса ‘locale’. По умолчанию используется русский язык.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/genindex.html b/SRV/docs/_build/html/genindex.html
new file mode 100644
index 0000000..3c39332
--- /dev/null
+++ b/SRV/docs/_build/html/genindex.html
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+ Index - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/index.html b/SRV/docs/_build/html/index.html
new file mode 100644
index 0000000..2822e58
--- /dev/null
+++ b/SRV/docs/_build/html/index.html
@@ -0,0 +1,411 @@
+
+
+
+
+
+
+
+
+ GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+GONETS SRV Documentation
+Добро пожаловать в документацию серверной части GONETS (SRV).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/objects.inv b/SRV/docs/_build/html/objects.inv
new file mode 100644
index 0000000..db6a911
Binary files /dev/null and b/SRV/docs/_build/html/objects.inv differ
diff --git a/SRV/docs/_build/html/py-modindex.html b/SRV/docs/_build/html/py-modindex.html
new file mode 100644
index 0000000..c782e07
--- /dev/null
+++ b/SRV/docs/_build/html/py-modindex.html
@@ -0,0 +1,573 @@
+
+
+
+
+
+
+ Python Module Index - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/search.html b/SRV/docs/_build/html/search.html
new file mode 100644
index 0000000..b5afcfd
--- /dev/null
+++ b/SRV/docs/_build/html/search.html
@@ -0,0 +1,329 @@
+
+
+
+
+
+
+
+
+
+Search - GONETS SRV 0.0.3 documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Contents
+
+
+
+
+
+
+ Expand
+
+
+
+
+
+ Light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark mode
+
+
+
+
+
+
+ Auto light/dark, in light mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto light/dark, in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to top
+
+
+
+
+
+
+
Error
+
+ Please activate JavaScript to enable the search functionality.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SRV/docs/_build/html/searchindex.js b/SRV/docs/_build/html/searchindex.js
new file mode 100644
index 0000000..7739098
--- /dev/null
+++ b/SRV/docs/_build/html/searchindex.js
@@ -0,0 +1 @@
+Search.setIndex({"alltitles":{"API \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b":[[6,null]],"ARP Service":[[22,null]],"Auth Service":[[23,null]],"Connect Service":[[24,null]],"Core":[[4,null]],"Firewalld Service":[[25,null]],"GONETS SRV Documentation":[[37,null]],"IP Service":[[26,null]],"Network Service":[[27,null]],"Settings Service":[[28,null]],"Subnet Service":[[29,null]],"UFW Service":[[30,null]],"Wifi Service":[[31,null]],"api.v1.auth":[[7,null]],"api.v1.ip":[[8,null]],"api.v1.network":[[9,null]],"api.v1.settings":[[10,null]],"api.v1.subnet":[[11,null]],"api.v1.ufw":[[12,null]],"api.v1.websocket":[[13,null]],"config.cors":[[1,null]],"config.init_config":[[2,null]],"config.ports":[[3,null]],"core.cli_executor":[[5,null]],"models.auth":[[15,null]],"models.network":[[16,null]],"models.request":[[17,null]],"models.subnet":[[18,null]],"models.ufw":[[19,null]],"models.wifi":[[20,null]],"storage.json_storage":[[33,null]],"storage.session_storage":[[34,null]],"utils.i18n":[[36,null]],"\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f":[[0,null]],"\u041c\u043e\u0434\u0435\u043b\u0438":[[14,null]],"\u0420\u0430\u0437\u0434\u0435\u043b\u044b":[[37,null]],"\u0421\u0435\u0440\u0432\u0438\u0441\u044b":[[21,null]],"\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435":[[37,"id1"]],"\u0423\u0442\u0438\u043b\u0438\u0442\u044b":[[35,null]],"\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430":[[32,null]]},"docnames":["api/config","api/config/cors","api/config/init_config","api/config/ports","api/core","api/core/cli_executor","api/endpoints","api/endpoints/auth","api/endpoints/ip","api/endpoints/network","api/endpoints/settings","api/endpoints/subnet","api/endpoints/ufw","api/endpoints/websocket","api/models","api/models/auth","api/models/network","api/models/request","api/models/subnet","api/models/ufw","api/models/wifi","api/services","api/services/arp_service","api/services/auth_service","api/services/connect_service","api/services/firewalld_service","api/services/ip_service","api/services/network_service","api/services/settings_service","api/services/subnet_service","api/services/ufw_service","api/services/wifi_service","api/storage","api/storage/json_storage","api/storage/session_storage","api/utils","api/utils/i18n","index"],"envversion":{"sphinx":65,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.viewcode":1},"filenames":["api/config.rst","api/config/cors.rst","api/config/init_config.rst","api/config/ports.rst","api/core.rst","api/core/cli_executor.rst","api/endpoints.rst","api/endpoints/auth.rst","api/endpoints/ip.rst","api/endpoints/network.rst","api/endpoints/settings.rst","api/endpoints/subnet.rst","api/endpoints/ufw.rst","api/endpoints/websocket.rst","api/models.rst","api/models/auth.rst","api/models/network.rst","api/models/request.rst","api/models/subnet.rst","api/models/ufw.rst","api/models/wifi.rst","api/services.rst","api/services/arp_service.rst","api/services/auth_service.rst","api/services/connect_service.rst","api/services/firewalld_service.rst","api/services/ip_service.rst","api/services/network_service.rst","api/services/settings_service.rst","api/services/subnet_service.rst","api/services/ufw_service.rst","api/services/wifi_service.rst","api/storage.rst","api/storage/json_storage.rst","api/storage/session_storage.rst","api/utils.rst","api/utils/i18n.rst","index.rst"],"indexentries":{},"objects":{},"objnames":{},"objtypes":{},"terms":{"0":[10,16,18,19,25,27,28,29],"0o600":33,"1":[9,10,18,19,28,29],"10":[10,27,28,29],"100":27,"168":[18,19],"192":[18,19],"2":[9,13,20,22,29],"22":29,"24":[10,18,19,28,29],"3":22,"30":[11,31,34],"3000":19,"3999":19,"4":[20,27],"400":[7,9,11,12],"401":7,"404":[8,10,11],"443":29,"4ghz":[],"5":20,"500":[7,8,9,10,11,12],"5173":1,"5174":1,"5ghz":[],"6":27,"7":29,"80":[19,29],"IN":19,"_":[29,36],"_clean_old_devic":29,"accept":36,"access_point":20,"access_token":[7,15],"action":19,"activ":[9,16],"active_connect":16,"add":25,"add_cors_middlewar":1,"add_not":[11,29,31],"add_port":25,"add_rul":30,"add_rules_bas":30,"add_servic":25,"add_sourc":25,"add_ufw_in_rul":[12,30],"add_ufw_out_rul":[12,30],"add_wifi_not":11,"address":[8,16,26],"all":25,"allow":[12,19],"allow_credenti":1,"allow_head":1,"allow_method":1,"ani":[8,26],"ap":31,"api":[27,37],"app":1,"apply_ip_config":[8,26],"arg":26,"arp":[8,21,26,29,37],"arp_servic":22,"async":[7,8,9,10,12,13,26,27],"attempt":22,"attribut":[],"auth":[6,14,21,37],"auth_servic":23,"authservic":23,"auto":27,"avail":[8,26],"babel":36,"base":[1,3,5,9,12,13,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,33,34],"base_path":29,"basemodel":[12,15,16,17,18,19,20],"bcrypt":33,"bearer":[7,15],"bitrat":[16,27],"bodi":[8,9,10,11],"bool":[7,8,9,15,16,19,20,24,26,28,29,30,31,33],"both":19,"broadcast":13,"broadcast_data":13,"bssid":[16,20,27,31],"bu":[25,26,27],"callabl":36,"calledprocesserror":24,"capabl":[22,29],"channel":20,"check_ip_config":[8,26],"check_users_fil":7,"cidr":[10,16,18,22,28,29],"class":[1,3,5,9,12,13,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,33,34],"classmethod":19,"classvar":[12,15,16,17,18,19,20],"clean_expired_sess":34,"clear_all_data":[10,28],"clear_subnet":[10,28],"clear_wifi":[10,28],"cli":5,"cli_executor":[4,37],"client":22,"client_id":[7,23,34],"cliexecutor":5,"command":5,"comment":19,"config":[0,12,15,16,17,18,19,20,37],"configdict":[12,15,16,17,18,19,20],"configur":[12,15,16,17,18,19,20],"conform":[12,15,16,17,18,19,20],"connect":[11,13,21,27,37],"connect_servic":24,"connect_to_wifi":11,"connection_id":[8,26],"container_i":[9,16,27],"container_id":9,"container_nam":9,"container_x":[9,16,27],"containerpositionmanag":9,"context":33,"cooki":36,"cor":[0,37],"core":37,"cors_config":1,"corsmiddlewar":1,"corsmiddlewareconfig":1,"create_access_token":23,"create_sess":34,"create_us":33,"created_at":34,"cryptcontext":33,"custom_port":3,"d":[25,26,27],"data":[8,9,23,26],"dbm":20,"deactiv":[9,16],"default":25,"delete_not":[11,29,31],"delete_rul":30,"delete_sess":34,"delete_ufw_rul":12,"delete_wifi_not":11,"deni":[12,19],"depend":[7,8,9,10,11,13,22,23,26,27,28,29,31],"destin":16,"dev":[1,31],"devic":[10,18,27,29],"device_mtu":16,"device_st":16,"deviceinfocollector":29,"devicestorag":29,"dict":[3,7,8,9,10,11,12,13,22,23,25,26,27,29,30,31,33,34],"dictionari":[12,15,16,17,18,19,20],"direct":[19,30],"disable_ufw":[12,30],"disconnect":[13,24],"disconnect_from_wifi":11,"dn":[16,29],"enabl":19,"enable_ufw":[12,30],"encrypt":20,"enum":19,"error":18,"etc":[33,34],"ether":[22,29],"ethernet":27,"except":[13,24,26,27,28,29,33,34],"exist":7,"expiri":34,"export_devic":10,"export_wifi":10,"factori":19,"fallback":36,"fals":[15,20,24,30,33],"fastapi":[1,29,36],"fedoraproject":25,"fi":[10,11,16,17,20,24,27,28,31],"file_path":[33,34],"filerespons":10,"firewal":[25,30],"firewalld":[21,37],"firewalld1":25,"firewalld_servic":25,"firewalldservic":25,"float":16,"frequenc":[16,20,27],"from":25,"from_ip":19,"from_ufw_output":19,"functool":29,"gatewai":16,"gener":27,"get":25,"get_activated_interfac":27,"get_activated_interfaces_contain":27,"get_connection_nam":27,"get_current_us":[7,23],"get_deactivated_interfac":27,"get_deactivated_interfaces_contain":27,"get_default_zon":25,"get_hostnam":[9,27,29],"get_interfaces_by_st":27,"get_interfaces_contain":27,"get_ip_config":27,"get_ip_method":27,"get_latest_sess":34,"get_network_data":9,"get_network_interfac":27,"get_network_interfaces_contain":27,"get_port":[3,25],"get_port_nam":3,"get_position_and_st":9,"get_servic":25,"get_sourc":25,"get_statu":24,"get_subnet_fil":29,"get_subnet_servic":[11,29],"get_system_languag":10,"get_transl":36,"get_ufw_rul":[12,30],"get_ufw_statu":[12,30],"get_user_by_sess":23,"get_user_by_usernam":33,"get_vendor":[29,31],"get_wifi_info":27,"get_wifi_statu":11,"get_zon":25,"get_zone_set":25,"gethostbyaddr":29,"gonet":[1,33,34],"has_us":7,"hashed_password":33,"hidden":20,"hostnam":[9,18,27],"httpexcept":[7,8,9,10,11,12],"i18n":[35,37],"id":[7,15,16,26,33],"ifac":31,"individualipconfig":[16,27],"ini":[],"init_config":[0,37],"initial_posit":27,"int":[3,5,9,11,12,15,16,19,20,22,25,27,30,31,33],"interfac":[16,22,29],"ip":[6,7,16,18,19,21,22,27,29,34,37],"ip_servic":26,"ip_typ":27,"ip_vers":27,"ipconfig":16,"ipservic":[22,26],"ipv4":[8,16,26,27],"ipv4_upd":[8,26],"ipv4rout":16,"ipv6":[16,27],"ipv6rout":16,"is_admin":[7,15,33],"iso8601":34,"isopen":[9,16],"iw":31,"json":[7,10,13,27,28,29,31,33,34],"json_storag":[32,37],"jsonstorag":33,"jwt":15,"lang":10,"languag":36,"last_check":19,"last_seen":18,"line":19,"list":[3,5,12,16,18,19,20,22,25,27,29,30],"load_devic":29,"load_not":31,"local":[10,36],"login":[7,23],"logout":[7,23],"loopback":27,"lru_cach":29,"mac":[11,16,18,22,29,31],"mac_address":16,"maclookup":[29,31],"manual":27,"messag":[7,8,9,12,13,19,26],"method":16,"metric":16,"middlewar":1,"mode":[16,27],"model":[12,14,37],"model_config":[12,15,16,17,18,19,20],"mtu":16,"name":16,"nbtscan":29,"nbtscan_cach":29,"nbtscan_result":29,"netbio":29,"netifac":22,"network":[6,14,21,31,37],"network_servic":27,"networkdevicegener":27,"networkdevicewireless":27,"networkinterfac":[16,27],"networkinterfacescontain":[16,27],"networkmanag":[8,24,26,27],"networkscann":[22,26,29],"networkservic":27,"next_hop":16,"next_id":33,"nmcli":24,"none":[1,3,9,10,15,16,18,19,20,22,23,24,25,26,27,28,29,31,33,34],"note":[11,17,18,20,29,31],"noterequest":[11,17],"number":[12,19],"oauth2passwordbear":23,"object":[1,3,5,9,13,22,23,24,25,26,27,28,29,30,31,33,34],"offlin":18,"onlin":18,"open_port":18,"option":[10,11,15,16,18,20,22,24,27,28,29,31,33,34],"org":25,"origin":1,"oui":29,"out":[12,19,30],"paramet":[3,5,7,8,9,10,11,12,13,19,22,24,26,27,28,29,30,33,34],"passlib":33,"password":[7,11,15,17,24,33],"password_hash":33,"path":29,"persist":34,"plain_password":33,"port":[0,19,25,37],"portconfig":3,"portscann":29,"prefix":16,"print_client":22,"properti":19,"protocol":[19,25],"pydant":[12,15,16,17,18,19,20,27,30],"pydanticundefin":[8,9,10,11],"pydbu":25,"pywifi":31,"queri":36,"rais":[7,8,9,10,11,12,13,19,24,26,27,28,29,33,34],"react":1,"regist":[7,23],"reject":[12,19],"reload":25,"remov":25,"remove_port":25,"remove_servic":25,"remove_sourc":25,"request":[7,10,11,14,37],"respons":10,"return":[3,5,7,8,9,10,11,12,19,22,24,26,27,28,29,30,33,34],"root":29,"rout":16,"ru":36,"rule":[12,19,25,30],"rule_numb":[12,30],"rulenumb":12,"run":5,"runtimeerror":[26,27],"save_devic":29,"save_ip_config":8,"save_not":31,"scan":[29,31],"scan_nbtscan":29,"scan_network":22,"scan_subnet":[11,29],"scan_wifi":[11,31],"scapi":[22,29],"sdbus_async":[26,27],"secret_kei":[],"server":19,"servic":[21,37],"session":34,"session_id":[7,15,34],"session_storag":[32,37],"sessionstorag":34,"set":[6,21,25,37],"set_default_zon":25,"set_languag":10,"settings_servic":28,"settingsservic":28,"should":[12,15,16,17,18,19,20],"signal":20,"sourc":[1,3,5,7,8,9,10,11,12,13,15,16,17,18,19,20,22,23,24,25,26,27,28,29,30,31,33,34,36],"srp":29,"srv":36,"ssid":[11,16,17,20,24,27,31],"startup_ev":13,"state":27,"static":5,"statu":[11,12,18,19],"status_cod":[7,8,9,10,11,12],"stdout":5,"storag":[29,31,32,37],"storage_path":31,"str":[3,5,7,8,9,10,11,15,16,17,18,19,20,23,24,25,26,27,28,29,30,31,33,34],"strength":[16,27],"subnet":[6,10,14,21,28,37],"subnet_servic":[11,29],"subnetscanrequest":[10,11,18],"subnetscanresult":[11,18,29],"subnetservic":[11,29],"subprocess":[5,24],"success":[8,9],"sudo":30,"support":36,"systembu":25,"tcp":[19,29],"timeout":[11,22,25,31],"to_ip":19,"to_ufw_command":19,"token":[7,15,23,34],"token_typ":[7,15],"total_rul":19,"translat":[7,8,9,10,11,13,22,23,24,26,27,28,29,31,36],"translations_depend":[7,8,9,10,11,13,22,23,26,27,28,29,31],"true":[1,16,24,28,30,33],"ttl":34,"tupl":[5,9,24,27,29,31],"type":[5,7,8,9,10,11,12,16,19,22,24,26,27,28,29,30,33,34],"udp":[19,29],"ufw":[6,14,21,37],"ufw_servic":30,"ufwrul":[12,19,30],"ufwrulesrespons":[12,19,30],"ufwservic":[12,30],"ufwstatu":[12,19,30],"ui":[3,29],"uncompl":30,"unicod":31,"union":[9,16,19,27],"unknown":27,"update_container_posit":9,"update_not":[11,29,31],"update_posit":9,"update_wifi_not":11,"user":[7,15,23,33],"usercr":[7,15,23,33],"userlogin":[7,15,23],"usernam":[7,15,33,34],"userrespons":[7,15,23,33],"util":[35,37],"v":19,"v1":[6,37],"validate_act":19,"validate_ip":19,"validate_ip_version_compat":19,"validate_port":19,"validate_protocol":19,"valu":19,"valueerror":[9,19,26,33],"vendor":[18,20],"verify_password":33,"verify_token":23,"vite":1,"web":19,"websocket":[6,37],"websocket_endpoint":13,"websocketdisconnect":13,"websocketmanag":13,"wi":[10,11,16,17,20,24,27,28,31],"wifi":[10,14,21,28,37],"wifi_devic":27,"wifi_servic":31,"wifiaccesspoint":20,"wificonnectionservic":24,"wificonnectrequest":[11,17],"wifidevic":[16,27],"wifidisconnectrequest":[11,17],"wifiscanresult":[11,20,31],"wifiservic":31,"wifistorag":31,"wpa":20,"wpa2":20,"wpa3":20,"x":[9,16,27],"y":[9,16,27],"zone":25,"\u0430":27,"\u0430\u0432\u0442\u043e":29,"\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438":[24,29],"\u0430\u0432\u0442\u043e\u043e\u0447\u0438\u0441\u0442\u043a\u0443":34,"\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438":[7,15,23],"\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u0442":[7,23],"\u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u044f":27,"\u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e":15,"\u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u044b\u0439":[],"\u0430\u0434\u0440\u0435\u0441":[7,8,11,16,18,22,26,29],"\u0430\u0434\u0440\u0435\u0441\u0430":[8,16,19,22,26,27,29],"\u0430\u0434\u0440\u0435\u0441\u0435":29,"\u0430\u0434\u0440\u0435\u0441\u043e\u0432":[16,19,22],"\u0430\u0434\u0440\u0435\u0441\u0443":[11,29,31],"\u0430\u043a\u043a\u0443\u043c\u0443\u043b\u0438\u0440\u0443\u044e\u0442\u0441\u044f":29,"\u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0438":9,"\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435":[],"\u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445":[],"\u0430\u043a\u0442\u0438\u0432\u043d\u043e":11,"\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e":[16,29],"\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439":34,"\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c":[],"\u0430\u043a\u0442\u0438\u0432\u043d\u0443\u044e":[7,34],"\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435":[9,16,29],"\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0439":24,"\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c":13,"\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438":27,"\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445":[11,13,16,22,27,29],"\u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c":29,"\u0430\u043d\u0430\u043b\u0438\u0437\u0430":3,"\u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u0432":5,"\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0443\u044e":13,"\u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435":[26,27],"\u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b":[12,15,16,17,18,20],"\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438":34,"\u0431\u0430\u0437\u043e\u0432\u043e\u0433\u043e":[16,31],"\u0431\u0430\u0437\u043e\u0432\u044b\u0435":[],"\u0431\u0430\u0437\u043e\u0432\u044b\u0439":30,"\u0431\u0434":34,"\u0431\u0435\u0437":[5,19,24,31,33,34,36],"\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f":[],"\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e":31,"\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e":5,"\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438":31,"\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c":33,"\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438":34,"\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043e\u043a":33,"\u0431\u043e\u043b\u0435\u0435":[22,29],"\u0431\u0440\u0430\u043d\u0434\u043c\u0430\u0443\u044d\u0440\u0430":[12,30],"\u0431\u0443\u0434\u0443\u0442":24,"\u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e":29,"\u0431\u0443\u0434\u0443\u0449\u0438\u0445":31,"\u0431\u044b\u043b\u0438":30,"\u0431\u044b\u0442\u044c":[20,27,29],"\u0432":[1,3,5,9,10,11,12,13,15,16,18,19,20,22,24,27,28,29,30,31,33,34,37],"\u0432\u0430\u0436\u043d\u0430":[],"\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0435\u0439":19,"\u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f":[],"\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435":19,"\u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u0442":[24,31],"\u0432\u0432\u0435\u0434\u0451\u043d\u043d\u043e\u0433\u043e":33,"\u0432\u0432\u0435\u0434\u0451\u043d\u043d\u044b\u0439":33,"\u0432\u0435\u0434\u0451\u0442":[31,36],"\u0432\u0435\u043d\u0434\u043e\u0440":29,"\u0432\u0435\u043d\u0434\u043e\u0440\u0430":[29,31],"\u0432\u0435\u043d\u0434\u043e\u0440\u043e\u0432":29,"\u0432\u0435\u0440\u0435\u043d":33,"\u0432\u0435\u0440\u0441\u0438\u0438":29,"\u0432\u0435\u0440\u0441\u0438\u0439":19,"\u0432\u0435\u0440\u0441\u0438\u044f":27,"\u0432\u0435\u0449\u0430\u043d\u0438\u044f":[],"\u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f":[12,27,30],"\u0432\u0438\u0434\u0430":[3,19],"\u0432\u0438\u0434\u0435":[9,12,33],"\u0432\u0438\u0437\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438":27,"\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442":[12,30],"\u0432\u043a\u043b\u044e\u0447\u0430\u044f":[8,22,26,27,29,34],"\u0432\u043a\u043b\u044e\u0447\u0435\u043d":[19,30],"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435":12,"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0441\u0442\u0438":12,"\u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b":15,"\u0432\u043b\u0430\u0434\u0435\u043b\u0435\u0446":[],"\u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0443":33,"\u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445":26,"\u0432\u043d\u0435\u0448\u043d\u0438\u0445":33,"\u0432\u043d\u0435\u0448\u043d\u044f\u044f":33,"\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445":12,"\u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f":[7,8,11],"\u0432\u043e":28,"\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430":5,"\u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0435":15,"\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442":[3,5,7,8,9,11,12,19,23,27,29,34,36],"\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0439":12,"\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b":34,"\u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c":34,"\u0432\u0440\u0435\u043c\u0435\u043d\u0438":29,"\u0432\u0440\u0435\u043c\u044f":[18,19,29],"\u0432\u0440\u0443\u0447\u043d\u0443\u044e":29,"\u0432\u0441\u0435":[1,10,12,27,28,30,34],"\u0432\u0441\u0435\u0433\u0434\u0430":7,"\u0432\u0441\u0435\u043c":13,"\u0432\u0441\u0435\u0445":[3,28],"\u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435":29,"\u0432\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u0445":33,"\u0432\u0441\u0442\u0440\u0435\u0447\u0435\u043d\u043d\u044b\u0445":29,"\u0432\u0445\u043e\u0434\u0430":7,"\u0432\u0445\u043e\u0434\u043d\u044b\u0445":26,"\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435":[12,30],"\u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0439":19,"\u0432\u044b\u0431\u043e\u0440":31,"\u0432\u044b\u0431\u043e\u0440\u0430":36,"\u0432\u044b\u0431\u043e\u0440\u043e\u043c":30,"\u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0433\u043e":22,"\u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439":36,"\u0432\u044b\u0432\u043e\u0434":[5,30],"\u0432\u044b\u0432\u043e\u0434\u0430":[19,22,27],"\u0432\u044b\u0432\u043e\u0434\u0435":12,"\u0432\u044b\u0432\u043e\u0434\u0438\u0442":[10,22],"\u0432\u044b\u0437\u043e\u0432":5,"\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f":[],"\u0432\u044b\u043a\u043b\u044e\u0447\u0430\u0435\u0442":[12,30],"\u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d":30,"\u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435":12,"\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c":26,"\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438":[12,24,27],"\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f":[5,12,25,29],"\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442":[5,8,9,10,11,13,23,26,29],"\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f":36,"\u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f":30,"\u0432\u044b\u0441\u043e\u043a\u043e\u0433\u043e":22,"\u0432\u044b\u0445\u043e\u0434":[7,23],"\u0432\u044b\u0445\u043e\u0434\u0435":7,"\u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442":36,"\u0433\u0433\u0446":20,"\u0433\u0434\u0435":[27,29,33],"\u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438":[5,29],"\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0442":25,"\u0433\u043b\u0443\u0431\u0438\u043d\u044b":29,"\u0433\u043e\u043d\u043a\u0438":34,"\u0433\u0440\u0443\u043f\u043f\u043e\u0432\u0430\u044f":16,"\u0433\u0440\u0443\u043f\u043f\u044b":[],"\u0434":29,"\u0434\u0430\u0436\u0435":31,"\u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0433\u043e":7,"\u0434\u0430\u043d\u043d\u044b\u0435":[7,9,10,11,13,15,28,29,33,34],"\u0434\u0430\u043d\u043d\u044b\u043c\u0438":[7,8,10,26,27,29,33,34],"\u0434\u0430\u043d\u043d\u044b\u0445":[7,9,11,13,26,27,28,29,31,33,34],"\u0434\u0435\u0430\u043a\u0442\u0438\u0432\u0430\u0446\u0438\u0438":9,"\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439":19,"\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f":[12,34],"\u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442":34,"\u0434\u0435\u0442\u0430\u043b\u0438":[29,30],"\u0434\u0435\u0442\u0430\u043b\u044f\u043c\u0438":12,"\u0434\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0433\u043e":36,"\u0434\u0435\u0444\u043e\u043b\u0442\u043d\u043e\u0439":25,"\u0434\u0435\u0444\u043e\u043b\u0442\u043e\u043c":36,"\u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d":[19,20],"\u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430":19,"\u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u043e\u0432":30,"\u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u044b":12,"\u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e":31,"\u0434\u043b\u044f":[1,3,5,7,8,9,10,11,12,13,16,19,22,23,24,25,26,27,28,29,30,31,33,34,36],"\u0434\u043d\u0435\u0439":29,"\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c":29,"\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435":[1,17,25,29,30],"\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438":[11,12],"\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f":[12,30],"\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445":12,"\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b":30,"\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c":1,"\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442":[11,12,13,22,29,30,31],"\u0434\u043e\u0431\u0440\u043e":37,"\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e":37,"\u0434\u043e\u043b\u0436\u043d\u0430":26,"\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439":29,"\u0434\u043e\u0441\u0442\u0443\u043f":26,"\u0434\u043e\u0441\u0442\u0443\u043f\u0430":[10,11,15,16,17,20,24,25,27,28,31,33],"\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d":[8,26],"\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u0438":26,"\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c":[8,26],"\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445":11,"\u0434\u0440":1,"\u0434\u0440\u0443\u0433\u0430\u044f":13,"\u0434\u0440\u0443\u0433\u043e\u0435":11,"\u0434\u0440\u0443\u0436\u0435\u043b\u044e\u0431\u043d\u044b\u0435":25,"\u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0442\u044b":22,"\u0435\u0433\u043e":[8,9,26],"\u0435\u0441\u043b\u0438":[7,8,9,10,11,12,13,15,19,24,26,27,28,29,30,33,34,36],"\u0435\u0441\u0442\u044c":29,"\u0436\u0438\u0437\u043d\u0438":29,"\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0430\u0441\u044c":24,"\u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u043e\u0441\u044c":12,"\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438":[29,36],"\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c":[33,36],"\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430":36,"\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438":1,"\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442":[29,31],"\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430":29,"\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438":36,"\u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e":29,"\u0437\u0430\u0434\u0430\u0447\u0443":13,"\u0437\u0430\u043a\u0440\u044b\u0442":9,"\u0437\u0430\u043c\u0435\u043d\u044f\u0435\u0442":29,"\u0437\u0430\u043c\u0435\u0442\u043a\u0430":[11,18,20],"\u0437\u0430\u043c\u0435\u0442\u043a\u0430\u043c\u0438":29,"\u0437\u0430\u043c\u0435\u0442\u043a\u0430\u0445":31,"\u0437\u0430\u043c\u0435\u0442\u043a\u0438":[11,17,29,31],"\u0437\u0430\u043c\u0435\u0442\u043a\u0443":[11,29,31],"\u0437\u0430\u043c\u0435\u0442\u043e\u043a":31,"\u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f":29,"\u0437\u0430\u043d\u044f\u0442":[8,26],"\u0437\u0430\u043d\u044f\u0442\u043e\u0441\u0442\u0438":[22,26],"\u0437\u0430\u043d\u044f\u0442\u043e\u0441\u0442\u044c":26,"\u0437\u0430\u043f\u0438\u0441\u0435\u0439":33,"\u0437\u0430\u043f\u0438\u0441\u0438":[10,28,33,34],"\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442":[29,31],"\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044f":34,"\u0437\u0430\u043f\u0438\u0441\u044c":[33,34],"\u0437\u0430\u043f\u0440\u0435\u0442\u0438\u0442\u044c":19,"\u0437\u0430\u043f\u0440\u043e\u0441":[9,10],"\u0437\u0430\u043f\u0440\u043e\u0441\u0430":[7,10,12,17,18,36],"\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432":[22,29],"\u0437\u0430\u043f\u0440\u043e\u0441\u044b":29,"\u0437\u0430\u043f\u0443\u0441\u043a":13,"\u0437\u0430\u043f\u0443\u0441\u043a\u0430":[],"\u0437\u0430\u043f\u0443\u0441\u043a\u0435":13,"\u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e":7,"\u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c":7,"\u0437\u0430\u0442\u0435\u043c":29,"\u0437\u0430\u0445\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d":[],"\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435":[15,19,33],"\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c":[9,27],"\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f":27,"\u0437\u043e\u043d\u0430\u043c":25,"\u0437\u043e\u043d\u044b":25,"\u0438":[1,3,5,7,8,9,10,11,12,13,22,23,24,25,26,27,28,29,30,31,33,34,36],"\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440":[9,11,15,16,20,24,26,34],"\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u0443":9,"\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u043c\u043e\u0439":11,"\u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u043c\u043e\u043c\u0443":11,"\u0438\u0437":[7,10,13,19,23,27,28,29,31,33,34,36],"\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445":3,"\u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442":[10,27],"\u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u044f":29,"\u0438\u0437\u0432\u043b\u0435\u0447\u044c":10,"\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435":25,"\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f":26,"\u0438\u0437\u043c\u0435\u043d\u044f\u0435\u0442":[11,29],"\u0438\u043b\u0438":[1,3,7,8,9,11,12,13,19,22,24,27,28,29,30,33,34,36],"\u0438\u043c\u0435\u043d\u0435\u043c":33,"\u0438\u043c\u0435\u043d\u0438":[9,22,27,29,33],"\u0438\u043c\u044f":[9,15,18,27,33,34],"\u0438\u043c\u0451\u043d":29,"\u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u043e\u0439":16,"\u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435":16,"\u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0445":[],"\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438":31,"\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f":22,"\u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442":[1,5],"\u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0442\u043e\u0440\u0435":[],"\u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435":29,"\u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u043e\u043c":13,"\u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0438\u0440\u0443\u0435\u0442":24,"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441":24,"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430":[16,22,26,27,29,36],"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u043c\u0438":[25,27],"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430\u0445":[9,27],"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432":[16,27],"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0443":29,"\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b":[16,27],"\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439":[12,30],"\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438":[22,27,29],"\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e":[7,11,27,29],"\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f":12,"\u0438\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u0442":27,"\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0439":5,"\u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f":25,"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435":1,"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c":[11,22,24,26,29,31,36],"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438":[],"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f":[7,19],"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0430\u044f":15,"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439":10,"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442":29,"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f":[8,25,27,33,36],"\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f":27,"\u0438\u0441\u0442\u0435\u043a\u043b\u0430":7,"\u0438\u0441\u0442\u0435\u043a\u043b\u0438":34,"\u0438\u0441\u0442\u0435\u043a\u0448\u0438\u0435":34,"\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0438":[29,31],"\u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432":[1,25],"\u0438\u0441\u0442\u0451\u043a\u0448\u0438\u043c":34,"\u0438\u0441\u0442\u0451\u043a\u0448\u0438\u0445":34,"\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e":12,"\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435":[12,30],"\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0439":19,"\u0438\u0442\u043e\u0433":12,"\u0438\u0445":[9,11,12,22,27,30,31],"\u043a":[5,8,9,10,11,12,13,17,19,24,25,26,29,31],"\u043a\u0430\u0434\u0440\u043e\u0432":22,"\u043a\u0430\u0436\u0434\u0430\u044f":34,"\u043a\u0430\u0436\u0434\u043e\u0439":34,"\u043a\u0430\u0436\u0434\u043e\u043c\u0443":12,"\u043a\u0430\u043a":[12,29],"\u043a\u0430\u043d\u0430\u043b":[],"\u043a\u0430\u043d\u0430\u043b\u0430":20,"\u043a\u0430\u043d\u0430\u043b\u044c\u043d\u043e\u043c":29,"\u043a\u0430\u0442\u0430\u043b\u043e\u0433":[],"\u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430":36,"\u043a\u043b\u0430\u0441\u0441":[1,9,13,29,31],"\u043a\u043b\u0438\u0435\u043d\u0442":13,"\u043a\u043b\u0438\u0435\u043d\u0442\u0430":[7,12,13,34],"\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c":13,"\u043a\u043b\u0438\u0435\u043d\u0442\u0430\u0445":22,"\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432":[13,22],"\u043a\u043b\u044e\u0447\u0430\u043c\u0438":[7,8,9,11,12,22,26],"\u043a\u043b\u044e\u0447\u043e\u043c":[9,27,29],"\u043a\u043e\u0434":[5,29],"\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435":12,"\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e":[19,22,29],"\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c":12,"\u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0435\u0439":12,"\u043a\u043e\u043c\u0430\u043d\u0434":5,"\u043a\u043e\u043c\u0430\u043d\u0434\u0430":24,"\u043a\u043e\u043c\u0430\u043d\u0434\u0435":19,"\u043a\u043e\u043c\u0430\u043d\u0434\u0443":[5,10,19],"\u043a\u043e\u043c\u0430\u043d\u0434\u044b":[5,19,24,27,30],"\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432":30,"\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439":29,"\u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e\u0433\u043e":33,"\u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u044b\u0439":[],"\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440":[16,27],"\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430":[9,27],"\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\u0445":9,"\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432":[9,13],"\u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0443":9,"\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442":[25,36],"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438":[8,16,25,26],"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0439":[22,27],"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e":1,"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e":27,"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f":[1,3,16,37],"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0445":27,"\u043a\u043e\u043d\u0446\u0430":30,"\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442":27,"\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430":[9,16,27],"\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438":[9,27],"\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b":[9,27],"\u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439":29,"\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e":[26,29],"\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c":19,"\u043a\u043e\u0440\u0442\u0435\u0436":[9,24,29],"\u043a\u043e\u0440\u0442\u0435\u0436\u0435\u0439":29,"\u043a\u043e\u0440\u0442\u0435\u0436\u0443":5,"\u043a\u043e\u0442\u043e\u0440\u0430\u044f":36,"\u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e":34,"\u043a\u043e\u0442\u043e\u0440\u043e\u043c":[],"\u043a\u0440\u0435\u0434\u0435\u043d\u0448\u043b\u044b":1,"\u043a\u0440\u0438\u0442\u0438\u0447\u043d\u0430":33,"\u043a\u0443\u043a\u0430\u043c\u0438":1,"\u043a\u0443\u043a\u0443":10,"\u043a\u044d\u0448":29,"\u043a\u044d\u0448\u0430":29,"\u043a\u044d\u0448\u0435\u0439":34,"\u043a\u044d\u0448\u0435\u043c":29,"\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435":[29,31],"\u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f":[29,31],"\u043a\u044d\u0448\u0438\u0440\u0443\u044e\u0442\u0441\u044f":29,"\u043b\u0438":[7,8,19,26],"\u043b\u0438\u0441\u0442\u0438\u043d\u0433\u0435":24,"\u043b\u043e\u0433\u0430\u0445":3,"\u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435":[31,36],"\u043b\u043e\u0433\u0438\u0440\u0443\u0435\u0442":29,"\u043b\u043e\u043a\u0430\u043b\u0438":36,"\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439":29,"\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438":36,"\u043b\u043e\u043a\u0430\u043b\u044c":[1,36],"\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e":29,"\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439":[1,22,29],"\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e":11,"\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435":22,"\u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439":22,"\u043b\u0443\u0447\u0448\u0435\u0433\u043e":31,"\u043b\u0451\u0433\u043a\u043e\u0435":34,"\u043c\u0430\u043b\u044b\u043c":29,"\u043c\u0430\u0440\u043a\u0438\u0440\u043e\u0432\u043a\u0430":29,"\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430":16,"\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440":16,"\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u043e\u0432":16,"\u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b":27,"\u043c\u0430\u0441\u0441\u043e\u0432\u0430\u044f":30,"\u043c\u0435\u043d\u0435\u0434\u0436\u0435\u0440\u0435":9,"\u043c\u0435\u0442\u043e\u0434":[16,27,29,30,34],"\u043c\u0435\u0442\u043e\u0434\u044b":[1,22,24,25,26,27],"\u043c\u0435\u0442\u0440\u0438\u043a\u0430":16,"\u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f":33,"\u043c\u0438\u043d\u0443\u0442":34,"\u043c\u043d\u043e\u0433\u043e\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u043e\u0439":[33,34],"\u043c\u043d\u043e\u0433\u043e\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043d\u044b\u0445":[],"\u043c\u043e\u0433\u0443\u0442":[22,27],"\u043c\u043e\u0434\u0435\u043b\u0435\u0439":27,"\u043c\u043e\u0434\u0435\u043b\u0438":[27,30,37],"\u043c\u043e\u0434\u0435\u043b\u044c":[12,15,16,17,18,19,20],"\u043c\u043e\u0434\u0443\u043b\u044c":[1,36],"\u043c\u043e\u0434\u0443\u043b\u044f":36,"\u043c\u043e\u0436\u0435\u0442":[20,29],"\u043c\u043e\u0436\u043d\u043e":1,"\u043d\u0430":[1,7,9,10,12,16,17,18,22,24,26,29,33,34,36],"\u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0432\u0448\u0438\u0435\u0441\u044f":29,"\u043d\u0430\u0431\u043e\u0440\u0430":16,"\u043d\u0430\u0434":25,"\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435":[3,16,17,20,29],"\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c":29,"\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f":[],"\u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f\u043c\u0438":3,"\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435":[3,5,22,24,25,26,27,29,30,33,34],"\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f":16,"\u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435":29,"\u043d\u0430\u0439\u0434\u0435\u043d":[10,33],"\u043d\u0430\u0439\u0434\u0435\u043d\u0430":[7,11,34],"\u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0435":[18,20],"\u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0445":[],"\u043d\u0430\u0439\u0434\u0435\u043d\u043e":[8,11,24,26],"\u043d\u0430\u043b\u0438\u0447\u0438\u0435":[24,33,36],"\u043d\u0430\u043b\u0438\u0447\u0438\u0438":7,"\u043d\u0430\u043b\u0438\u0447\u0438\u044f":[],"\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435":30,"\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f":[12,19],"\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440":[1,10,18,19,22,27,28,29,34],"\u043d\u0430\u0440\u0443\u0448\u0430\u0442\u044c":30,"\u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u0442\u0441\u044f":[],"\u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u0435":29,"\u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a":[8,26,27],"\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438":28,"\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438":[1,8,26],"\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443":33,"\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0445":27,"\u043d\u0435":[7,8,9,10,11,12,24,26,29,30,33,34,36],"\u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435":[9,27],"\u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438":27,"\u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445":27,"\u043d\u0435\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0435\u0435":29,"\u043d\u0435\u0432\u0435\u0440\u043d\u044b":[7,9],"\u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439":[9,24],"\u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b":24,"\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f":24,"\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439":29,"\u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u0435\u043d":19,"\u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438":1,"\u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u043e":13,"\u043d\u0435\u0442":33,"\u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e":12,"\u043d\u0438":9,"\u043d\u0438\u0436\u043d\u0435\u043c\u0443":19,"\u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u043e\u0433\u043e":22,"\u043d\u0438\u0437\u043a\u043e\u0443\u0440\u043e\u0432\u043d\u0435\u0432\u044b\u0445":29,"\u043d\u0438\u043a\u043e\u0433\u0434\u0430":33,"\u043d\u0438\u0445":11,"\u043d\u043e\u0432\u0430\u044f":9,"\u043d\u043e\u0432\u043e\u0433\u043e":[7,8,11,23,26,33],"\u043d\u043e\u0432\u043e\u0435":9,"\u043d\u043e\u0432\u043e\u0439":34,"\u043d\u043e\u0432\u043e\u043c":9,"\u043d\u043e\u0432\u0443\u044e":34,"\u043d\u043e\u0432\u044b\u0439":[19,29],"\u043d\u043e\u0434\u044b":16,"\u043d\u043e\u043c\u0435\u0440":[3,20],"\u043d\u043e\u043c\u0435\u0440\u0430":12,"\u043d\u043e\u043c\u0435\u0440\u0430\u043c":[12,30],"\u043d\u043e\u043c\u0435\u0440\u043e\u0432":[3,12,30],"\u043d\u0443\u043c\u0435\u0440\u0430\u0446\u0438\u044e":30,"\u043e":[7,8,9,10,11,12,15,19,22,26,27,29,30,31],"\u043e\u0431":[7,8,9,10,11,12,18,24,29],"\u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442":[33,34],"\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435":22,"\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f":[11,18,22,29],"\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e":18,"\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445":[22,29],"\u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442":29,"\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438":[9,11],"\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f":[13,26],"\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438":8,"\u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442":[8,9,11,26,29,31],"\u043e\u0431\u043d\u043e\u0432\u043b\u0451\u043d\u043d\u044b\u0435":34,"\u043e\u0431\u043e\u0433\u0430\u0449\u0435\u043d\u0438\u0435":29,"\u043e\u0431\u043e\u0438\u0445":10,"\u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u044f":[18,20],"\u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442":13,"\u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044f":13,"\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430":[29,30],"\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435":[13,27,34],"\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438":13,"\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a":13,"\u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e":29,"\u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0435":12,"\u043e\u0431\u0449\u0435\u0435":19,"\u043e\u0431\u0449\u0435\u043c":12,"\u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442":29,"\u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0451\u043d\u043d\u043e\u0433\u043e":29,"\u043e\u0431\u044a\u0435\u043a\u0442":[7,8,9,10,11,12,13,26,27,29,30,33],"\u043e\u0431\u044a\u0435\u043a\u0442\u0430":29,"\u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432":27,"\u043e\u0431\u044b\u0447\u043d\u043e":15,"\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435":9,"\u043e\u0431\u0451\u0440\u0442\u043a\u0430":[25,36],"\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435\u043c":29,"\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f":[22,24,26,29,33,34],"\u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e":29,"\u043e\u0434\u0438\u043d":28,"\u043e\u0434\u043d\u043e\u043a\u0440\u0430\u0442\u043d\u043e\u0433\u043e":[],"\u043e\u0434\u043d\u043e\u043c\u0443":9,"\u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f":[22,29],"\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0439":33,"\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f":[1,33],"\u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\u0445":24,"\u043e\u043d":7,"\u043e\u043d\u0430":29,"\u043e\u043d\u043b\u0430\u0439\u043d":[],"\u043e\u043d\u043e":29,"\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438":[8,9,24,25,29,30,34],"\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439":[25,30,31],"\u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\u043c":7,"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435":29,"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f":31,"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c":29,"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442":[22,24,31],"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f":[29,36],"\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0449\u0438\u0445":12,"\u043e\u043f\u0440\u043e\u0441":29,"\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e":[],"\u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d":29,"\u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u0443\u044f\u0441\u044c":24,"\u043e\u0441\u0438":16,"\u043e\u0441\u043d\u043e\u0432\u0435":[1,7,10,36],"\u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439":[],"\u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438":[22,27,36],"\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044f":10,"\u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442":12,"\u043e\u0442":[11,13,17,22,24,29,33],"\u043e\u0442\u0432\u0435\u0442":15,"\u043e\u0442\u0432\u0435\u0442\u0430":[22,29],"\u043e\u0442\u0432\u0435\u0442\u044b":29,"\u043e\u0442\u043a\u0430\u0442\u0430":8,"\u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442":[8,13,26],"\u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f":[11,24],"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d":12,"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435":[11,13,17],"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438":11,"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f":[13,24],"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0435":[],"\u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044b\u0445":[],"\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043b\u0441\u044f":13,"\u043e\u0442\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0445":16,"\u043e\u0442\u043a\u0440\u044b\u0442":9,"\u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f":20,"\u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c":33,"\u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435":[18,29],"\u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445":29,"\u043e\u0442\u043b\u0430\u0434\u043e\u0447\u043d\u043e\u0435":36,"\u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f":[3,16],"\u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u044e":19,"\u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435":13,"\u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438":[22,29],"\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442":[13,22,29],"\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044f":29,"\u043e\u0442\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c":22,"\u043e\u0442\u0441\u0442\u0443\u043f\u0430\u043c\u0438":31,"\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435":24,"\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438":36,"\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442":[],"\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442":[9,34],"\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0430\u044f":24,"\u043e\u0444\u0444\u043b\u0430\u0439\u043d":29,"\u043e\u0447\u0438\u0441\u0442\u043a\u0430":28,"\u043e\u0447\u0438\u0441\u0442\u043a\u0435":10,"\u043e\u0447\u0438\u0441\u0442\u043a\u0438":[10,28,29],"\u043e\u0447\u0438\u0441\u0442\u043a\u0443":[],"\u043e\u0447\u0438\u0449\u0430\u0435\u0442":[10,28,34],"\u043e\u0448\u0438\u0431\u043a\u0430":[7,8,9,10,11,13,24,26,27,28,29,33,34],"\u043e\u0448\u0438\u0431\u043a\u0430\u0445":[5,12,25],"\u043e\u0448\u0438\u0431\u043a\u0435":[12,18,24,29],"\u043e\u0448\u0438\u0431\u043a\u0438":[24,27,29],"\u043e\u0448\u0438\u0431\u043a\u043e\u0439":24,"\u043e\u0448\u0438\u0431\u043e\u043a":12,"\u043f":24,"\u043f\u0430\u043a\u0435\u0442\u043e\u0432":29,"\u043f\u0430\u043a\u0435\u0442\u044b":[22,29],"\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u0430\u044f":29,"\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430":36,"\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c":10,"\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c":1,"\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b":[10,18],"\u043f\u0430\u0440\u043e\u043b\u0438":33,"\u043f\u0430\u0440\u043e\u043b\u044c":[15,17,24,33],"\u043f\u0430\u0440\u043e\u043b\u044e":[11,24],"\u043f\u0430\u0440\u043e\u043b\u044f":33,"\u043f\u0430\u0440\u0441\u0438\u0442\u0441\u044f":30,"\u043f\u0435\u0440\u0432\u043e\u043c\u0443":36,"\u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430":36,"\u043f\u0435\u0440\u0435\u0432\u043e\u0434\u043e\u0432":[7,8,9,10,11,13,26,29,36],"\u043f\u0435\u0440\u0435\u0434":[8,19,22,24,26,34],"\u043f\u0435\u0440\u0435\u0434\u0430\u043d":36,"\u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430":25,"\u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442":26,"\u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442":[8,26],"\u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435":26,"\u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0439":13,"\u043f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u043e\u0435":29,"\u043f\u043b\u0430\u043d":29,"\u043f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440":29,"\u043f\u043e":[7,9,11,12,16,22,23,24,25,27,29,30,31,33,34,36],"\u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435":[1,24,31],"\u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u044f":29,"\u043f\u043e\u0432\u044b\u0448\u0435\u043d\u043d\u044b\u0435":22,"\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0438":[],"\u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0443":[],"\u043f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0435\u0442":36,"\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442":3,"\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c":29,"\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439":30,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442":13,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f":[11,24],"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435":[8,11,13,17,24],"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435\u043c":24,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438":11,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f":[11,13,24,26],"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f\u043c\u0438":[13,24],"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e":11,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0435":22,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u043c":13,"\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0445":13,"\u043f\u043e\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0445":12,"\u043f\u043e\u0434\u0441\u0435\u0442\u0435\u0439":[28,29],"\u043f\u043e\u0434\u0441\u0435\u0442\u0438":[10,11,16,18,28,29],"\u043f\u043e\u0434\u0441\u0435\u0442\u044c":[10,11,28,29],"\u043f\u043e\u0434\u0441\u0435\u0442\u044f\u043c":29,"\u043f\u043e\u0434\u0441\u0435\u0442\u044f\u043c\u0438":29,"\u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442":33,"\u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c":37,"\u043f\u043e\u0437\u0434\u043d\u0438\u043c":34,"\u043f\u043e\u0437\u0438\u0446\u0438\u0439":[9,13],"\u043f\u043e\u0437\u0438\u0446\u0438\u044e":9,"\u043f\u043e\u0437\u0438\u0446\u0438\u044f\u043c\u0438":9,"\u043f\u043e\u0438\u0441\u043a\u0430":33,"\u043f\u043e\u0438\u0441\u043a\u043e\u043c":34,"\u043f\u043e\u043a\u0430":29,"\u043f\u043e\u043b\u0435\u0439":26,"\u043f\u043e\u043b\u0435\u043c":[8,10,11,26],"\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0430":[29,34],"\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438":[1,31],"\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439":33,"\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435":16,"\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438":9,"\u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442":[9,23,27,29,30,33,34],"\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438":[9,13,27],"\u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f":[7,9,27,29,31,36],"\u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c":12,"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435":[7,15],"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439":[7,33,34],"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c":33,"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c":[7,33],"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f":[18,20],"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u043c":3,"\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f":[7,10,15,23,33,34],"\u043f\u043e\u043b\u044f":9,"\u043f\u043e\u043b\u044f\u043c\u0438":11,"\u043f\u043e\u043c\u043e\u0449\u044c":22,"\u043f\u043e\u043c\u043e\u0449\u044c\u044e":[22,29],"\u043f\u043e\u043f\u044b\u0442\u043e\u043a":22,"\u043f\u043e\u0440\u0442":19,"\u043f\u043e\u0440\u0442\u0430":[3,19],"\u043f\u043e\u0440\u0442\u043e\u0432":[1,3,19,25,29,30],"\u043f\u043e\u0440\u0442\u044b":[12,18,29],"\u043f\u043e\u0440\u044f\u0434\u043a\u0435":22,"\u043f\u043e\u0440\u044f\u0434\u043e\u043a":30,"\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e":18,"\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439":[19,34],"\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044e\u044e":[23,34],"\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f":[],"\u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439":7,"\u043f\u043e\u0442\u043e\u043a":29,"\u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u0442\u044c\u0441\u044f":22,"\u043f\u0440\u0430\u0432":[33,34],"\u043f\u0440\u0430\u0432\u0430":[24,26,29],"\u043f\u0440\u0430\u0432\u0430\u043c\u0438":33,"\u043f\u0440\u0430\u0432\u0438\u043b":[12,19,30],"\u043f\u0440\u0430\u0432\u0438\u043b\u0430":[12,19,30],"\u043f\u0440\u0430\u0432\u0438\u043b\u043e":19,"\u043f\u0440\u0430\u0432\u0438\u043b\u0443":12,"\u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e":33,"\u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445":22,"\u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0430":[],"\u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e":34,"\u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445":[7,11],"\u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442":[3,22,24,26,27,29],"\u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442":33,"\u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u044e\u0442":[],"\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f":19,"\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0445":22,"\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c\u0443":8,"\u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445":29,"\u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u0442":19,"\u043f\u0440\u0435\u0444\u0438\u043a\u0441":16,"\u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430":29,"\u043f\u0440\u0438":[1,5,7,9,12,13,15,22,24,25,26,27,28,29,31,33,34,36],"\u043f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0438":22,"\u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442":[5,19],"\u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d\u043d\u0443\u044e":[11,29],"\u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442":[11,29],"\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435":1,"\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f":[1,13],"\u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c":[8,22,26],"\u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0438":8,"\u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f":26,"\u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442":[8,26],"\u043f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u044f":25,"\u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442":29,"\u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442":[9,13],"\u043f\u0440\u0438\u043d\u044f\u0442\u0438\u0438":13,"\u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u0443":36,"\u043f\u0440\u043e\u0431\u0443\u0435\u0442":29,"\u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435":[8,26],"\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430":11,"\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435":[7,22],"\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438":[8,19,24,26],"\u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442":[7,8,11,19,23,24,26,30,33,34],"\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c":[18,20],"\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438":29,"\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f":29,"\u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430":[7,8,9,10,11,13,24,26,27,28,29,33,34],"\u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442":[],"\u043f\u0440\u043e\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c\u0443":[],"\u043f\u0440\u043e\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445":[],"\u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440":25,"\u043f\u0440\u043e\u0441\u0442\u043e\u0435":33,"\u043f\u0440\u043e\u0441\u0442\u044b\u0435":34,"\u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c":[30,33],"\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432":[19,30],"\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u044b":12,"\u043f\u0440\u043e\u0442\u0443\u0445\u0448\u0438\u0445":[],"\u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439":26,"\u043f\u0440\u043e\u0444\u0438\u043b\u0438":29,"\u043f\u0440\u043e\u0444\u0438\u043b\u044e":8,"\u043f\u0440\u043e\u0444\u0438\u043b\u044f":[8,26],"\u043f\u0440\u043e\u0446\u0435\u0441\u0441":29,"\u043f\u0443\u0441\u0442\u0430\u044f":29,"\u043f\u0443\u0441\u0442\u043e\u0439":[7,29],"\u043f\u0443\u0441\u0442\u044b\u043c":10,"\u043f\u0443\u0442\u0435\u0439":31,"\u043f\u0443\u0442\u0438":33,"\u043f\u0443\u0442\u044c":[29,34],"\u043f\u0443\u0442\u0451\u043c":22,"\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442":[],"\u0440\u0430\u0431\u043e\u0442\u0443":[],"\u0440\u0430\u0431\u043e\u0442\u044b":[1,16,29],"\u0440\u0430\u0431\u043e\u0447\u0430\u044f":16,"\u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f":29,"\u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u0442":9,"\u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435":[],"\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438":1,"\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f":29,"\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u044b":1,"\u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c":19,"\u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445":1,"\u0440\u0430\u043d\u0435\u0435":29,"\u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445":[3,29],"\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435":3,"\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f":16,"\u0440\u0430\u0441\u0448\u0438\u0440\u0438\u0442\u044c":1,"\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438":[22,29,30],"\u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430":29,"\u0440\u0435\u0433\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0445":36,"\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438":23,"\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442":[7,23],"\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0443":19,"\u0440\u0435\u0436\u0438\u043c":16,"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442":[5,8,12,18,20,29,30],"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430":7,"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430\u043c\u0438":[11,29],"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435":[8,26],"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432":29,"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b":29,"\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0438\u0440\u0443\u044e\u0449\u0438\u0439":22,"\u0440\u043e\u043b\u044c":29,"\u0440\u0443\u0441\u0441\u043a\u0438\u0439":36,"\u0440\u0443\u0441\u0441\u043a\u0443\u044e":36,"\u0441":[1,3,7,8,9,10,11,12,13,19,22,24,26,27,29,30,31,33,34,36],"\u0441\u0430\u043c\u044b\u043c":34,"\u0441\u0431\u043e\u0440":29,"\u0441\u0431\u043e\u0440\u0430":29,"\u0441\u0431\u043e\u0440\u043a\u0438":27,"\u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439":15,"\u0441\u0432\u0435\u0434\u0435\u043d\u0438\u044f\u043c\u0438":12,"\u0441\u0435\u0433\u043c\u0435\u043d\u0442":26,"\u0441\u0435\u0433\u043c\u0435\u043d\u0442\u0430":29,"\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445":[11,22],"\u0441\u0435\u043a\u0443\u043d\u0434\u044b":[13,29],"\u0441\u0435\u043a\u0446\u0438\u0435\u0439":[],"\u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439":37,"\u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432":16,"\u0441\u0435\u0440\u0432\u0435\u0440\u0443":13,"\u0441\u0435\u0440\u0432\u0435\u0440\u044b":1,"\u0441\u0435\u0440\u0432\u0438\u0441":[11,22,23,24,25,26,27,28,29,30,31],"\u0441\u0435\u0440\u0432\u0438\u0441\u0430":[3,29],"\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c":22,"\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432":[3,25,33],"\u0441\u0435\u0440\u0432\u0438\u0441\u0443":[9,12],"\u0441\u0435\u0440\u0432\u0438\u0441\u044b":37,"\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u043c":12,"\u0441\u0435\u0441\u0441\u0438\u0438":[7,15,34],"\u0441\u0435\u0441\u0441\u0438\u0439":34,"\u0441\u0435\u0441\u0441\u0438\u043e\u043d\u043d\u044b\u0445":34,"\u0441\u0435\u0441\u0441\u0438\u044e":[7,23,34],"\u0441\u0435\u0441\u0441\u0438\u044f":[7,34],"\u0441\u0435\u0441\u0441\u0438\u044f\u043c\u0438":23,"\u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e":[3,8,16,22,27],"\u0441\u0435\u0442\u0435\u0432\u043e\u0435":29,"\u0441\u0435\u0442\u0435\u0432\u043e\u043c\u0443":8,"\u0441\u0435\u0442\u0435\u0432\u044b\u0435":[9,13],"\u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438":[24,27],"\u0441\u0435\u0442\u0435\u0432\u044b\u0445":[9,13,16,19,26,27],"\u0441\u0435\u0442\u0435\u0439":[11,22],"\u0441\u0435\u0442\u0438":[8,11,13,16,20,22,24,26,28,29,31],"\u0441\u0435\u0442\u044c":[11,22,24],"\u0441\u0435\u0442\u044f\u043c":24,"\u0441\u0435\u0442\u044f\u0445":[29,31],"\u0441\u0438\u0433\u043d\u0430\u043b\u0430":[16,20,31],"\u0441\u0438\u043c\u0432\u043e\u043b\u044b":29,"\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u044f":33,"\u0441\u0438\u0441\u0442\u0435\u043c\u0435":[5,10,24],"\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0439":[25,26],"\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439":[10,24],"\u0441\u0438\u0441\u0442\u0435\u043c\u044b":[7,9,10,23],"\u0441\u043a\u0430\u043d\u0430":29,"\u0441\u043a\u0430\u043d\u0435":29,"\u0441\u043a\u0430\u043d\u0435\u0440\u0430":22,"\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435":[8,11,13,18,26,29],"\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438":26,"\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f":[11,18,20,22,26,29,31],"\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u043c\u043e\u0435":[],"\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442":[11,22,29,31],"\u0441\u043a\u0430\u043d\u0438\u0440\u0443\u044f":26,"\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u044f":10,"\u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438":29,"\u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c":16,"\u0441\u043a\u0440\u0438\u043f\u0442\u0435":[],"\u0441\u043a\u0440\u044b\u0442\u0438\u044f":[],"\u0441\u043a\u0440\u044b\u0442\u043e\u0433\u043e":20,"\u0441\u043a\u0440\u044b\u0442\u044b\u043c":20,"\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e":16,"\u0441\u043b\u0438\u044f\u043d\u0438\u0435":29,"\u0441\u043b\u043e\u0432\u0430\u0440\u0435\u0439":22,"\u0441\u043b\u043e\u0432\u0430\u0440\u044c":[7,8,9,10,11,12,26,27,29,33,34],"\u0441\u043b\u043e\u0432\u0430\u0440\u044f":[9,36],"\u0441\u043b\u043e\u0432\u0430\u0440\u044f\u0445":29,"\u0441\u043b\u043e\u0432\u0430\u0440\u0451\u043c":3,"\u0441\u043b\u043e\u0435\u043c":27,"\u0441\u043b\u0443\u0447\u0430\u0435":[12,27,29,30,33],"\u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u043c":[],"\u0441\u043c\u0435\u043d\u044b":10,"\u0441\u043d\u0430\u0447\u0430\u043b\u0430":29,"\u0441\u043e":[1,12,33],"\u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442":29,"\u0441\u043e\u0431\u0438\u0440\u0430\u044f":29,"\u0441\u043e\u0431\u043b\u044e\u0434\u0430\u0435\u0442\u0441\u044f":30,"\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c":19,"\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0435":25,"\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0439":[27,29,30],"\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u043c\u0438":9,"\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435":36,"\u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442":[7,29],"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435":26,"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435\u043c":13,"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439":[13,24],"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044e":26,"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f":[8,13,16,26,27],"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u043c":13,"\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f\u043c\u0438":24,"\u0441\u043e\u0437\u0434\u0430\u0435\u0442":19,"\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c":34,"\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438":34,"\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f":[29,34],"\u0441\u043e\u0437\u0434\u0430\u043d\u043d\u043e\u0433\u043e":[15,33],"\u0441\u043e\u0437\u0434\u0430\u0451\u0442":[7,13,23,31,33,34,36],"\u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f":[33,34],"\u0441\u043e\u043a\u0435\u0442":22,"\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435":[7,8,9,10,13,18,19,24,26,29],"\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\u043c":[10,11,12],"\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439":13,"\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f":13,"\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435":33,"\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c":26,"\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442":9,"\u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445":9,"\u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435":[3,29],"\u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445":34,"\u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0439":12,"\u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430":29,"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435":[9,11,16,18,27],"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c":[9,27],"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438":[7,9,11,12],"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439":9,"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e":[8,27],"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f":27,"\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f\u0445":27,"\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435":[29,31],"\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438":29,"\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f":[8,29],"\u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442":[29,31],"\u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f":33,"\u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435":10,"\u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445":29,"\u0441\u043f\u0438\u0441\u043a\u0430":[12,13,19,29,30,31],"\u0441\u043f\u0438\u0441\u043a\u043e\u043c":1,"\u0441\u043f\u0438\u0441\u043e\u043a":[1,3,5,11,12,13,16,19,22,27,29,30],"\u0441\u0440\u0435\u0434":34,"\u0441\u0440\u0435\u0434\u0435":[33,34],"\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430\u043c\u0438":26,"\u0441\u0440\u043e\u043a":34,"\u0441\u0440\u043e\u043a\u043e\u043c":34,"\u0441\u0442\u0430\u0440\u0442\u0435":13,"\u0441\u0442\u0430\u0442\u0443\u0441":[11,12,24,30],"\u0441\u0442\u0430\u0442\u0443\u0441\u0430":[11,12,19,24,30],"\u0441\u0442\u0430\u0442\u0443\u0441\u0435":[19,30],"\u0441\u0442\u0440\u043e\u0438\u0442":22,"\u0441\u0442\u0440\u043e\u043a":36,"\u0441\u0442\u0440\u043e\u043a\u0430":[3,19,29],"\u0441\u0442\u0440\u043e\u043a\u0438":[19,36],"\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430":[12,26],"\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443":31,"\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b":[],"\u0441\u0443\u0431\u0434":33,"\u0441\u0443\u0444\u0444\u0438\u043a\u0441\u043e\u0432":36,"\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442":[7,11,29,33],"\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e":[11,29],"\u0441\u0445\u0435\u043c\u0435":26,"\u0441\u0445\u0435\u043c\u043e\u0439":33,"\u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432":[],"\u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438":29,"\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0449\u0430\u044f":36,"\u0441\u044b\u0440\u043e\u0439":22,"\u0442":[24,29],"\u0442\u0430\u0431\u043b\u0438\u0446\u044b":22,"\u0442\u0430\u0439\u043c\u0430\u0443\u0442":[11,22,29],"\u0442\u0430\u0439\u043c\u0430\u0443\u0442\u043e\u043c":[11,22,29],"\u0442\u0430\u043a":29,"\u0442\u0430\u043a\u0436\u0435":27,"\u0442\u0430\u043a\u0438\u043c":33,"\u0442\u0430\u0441\u043a":13,"\u0442\u0430\u0441\u043a\u0430":13,"\u0442\u0435":34,"\u0442\u0435\u043a\u0441\u0442":[17,29],"\u0442\u0435\u043a\u0441\u0442\u0430":11,"\u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e":[7,8,9,10,11,12,13,23,24,29],"\u0442\u0435\u043a\u0443\u0449\u0435\u0435":16,"\u0442\u0435\u043a\u0443\u0449\u0435\u0439":[11,24,29,36],"\u0442\u0435\u043a\u0443\u0449\u0435\u043c":[11,29],"\u0442\u0435\u043a\u0443\u0449\u0438\u0435":[9,31],"\u0442\u0435\u043a\u0443\u0449\u0438\u0439":[10,12],"\u0442\u0435\u043a\u0443\u0449\u0438\u043c":10,"\u0442\u0435\u043a\u0443\u0449\u0438\u0445":31,"\u0442\u0438\u043f":[15,16,20,27],"\u0442\u0438\u043f\u0430":27,"\u0442\u0438\u043f\u043e\u0432\u044b\u0445":[1,25],"\u0442\u0438\u043f\u0443":[],"\u0442\u0438\u043f\u044b":9,"\u0442\u043e\u043a\u0435\u043d":[7,23,34],"\u0442\u043e\u043a\u0435\u043d\u0430":15,"\u0442\u043e\u043a\u0435\u043d\u043e\u0432":34,"\u0442\u043e\u043a\u0435\u043d\u0443":23,"\u0442\u043e\u043b\u044c\u043a\u043e":[33,36],"\u0442\u043e\u0447\u0435\u043a":[10,28,31],"\u0442\u043e\u0447\u043a\u0430":[],"\u0442\u043e\u0447\u043a\u0430\u043c":24,"\u0442\u043e\u0447\u043a\u0430\u0445":10,"\u0442\u043e\u0447\u043a\u0435":[],"\u0442\u043e\u0447\u043a\u0438":[11,16,17,20,27,31],"\u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u0443\u0435\u0442":13,"\u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438":13,"\u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044e":13,"\u0442\u0440\u0430\u0444\u0438\u043a":19,"\u0442\u0440\u0430\u0444\u0438\u043a\u0430":[12,19,30],"\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f":[22,26],"\u0442\u0440\u0435\u0431\u0443\u0435\u0442":34,"\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f":33,"\u0442\u0440\u0435\u0431\u0443\u044e\u0442\u0441\u044f":[24,26,29],"\u0443":[26,29],"\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435\u043c":19,"\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f":19,"\u0443\u0434\u0430\u043b\u0430\u0441\u044c":11,"\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435":[7,12,25,30],"\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u043c":34,"\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0438":11,"\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044e":12,"\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f":[12,30],"\u0443\u0434\u0430\u043b\u043e\u0441\u044c":[10,11,12,29],"\u0443\u0434\u0430\u043b\u044f\u0435\u043c\u044b\u0445":12,"\u0443\u0434\u0430\u043b\u044f\u0435\u0442":[7,10,11,12,13,22,29,30,31,34],"\u0443\u0434\u0430\u043b\u044f\u044f":23,"\u0443\u0434\u043e\u0431\u043d\u0430\u044f":36,"\u0443\u0434\u043e\u0431\u043d\u043e\u043c\u0443":5,"\u0443\u0436\u0435":[7,11,33],"\u0443\u0437\u043b\u0430":[16,29],"\u0443\u0437\u043b\u0430\u0445":10,"\u0443\u0437\u043b\u043e\u0432":[10,22,28,29],"\u0443\u043a\u0430\u0437\u0430\u043d":9,"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e":[3,9,11,27,29,34],"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439":[10,11,29],"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c":[28,29],"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c\u0443":[8,11,24,26,27,34],"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u0443\u044e":29,"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439":[8,26],"\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c":[11,26,27],"\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e":[11,16,22,27,33,34,36],"\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439":[20,34],"\u0443\u043d\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445":27,"\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f":[9,13,23,24,25,27,28,29],"\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442":13,"\u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f":36,"\u0443\u0440\u043e\u0432\u0435\u043d\u044c":[16,20],"\u0443\u0440\u043e\u0432\u043d\u0435":29,"\u0443\u0440\u043e\u0432\u043d\u044e":31,"\u0443\u0440\u043e\u0432\u043d\u044f":22,"\u0443\u0441\u043f\u0435\u0445":[24,29],"\u0443\u0441\u043f\u0435\u0445\u0430":29,"\u0443\u0441\u043f\u0435\u0448\u043d\u0430":28,"\u0443\u0441\u043f\u0435\u0448\u043d\u043e":[12,30],"\u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439":10,"\u0443\u0441\u043f\u0435\u0448\u043d\u043e\u043c":[7,8,9,11,12],"\u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0441\u0442\u044c":[8,9],"\u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445":12,"\u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442":[10,34],"\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435":10,"\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439":10,"\u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435":29,"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432":[10,11,29],"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430":[11,16,18,27,29],"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438":29,"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445":[11,29],"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435":27,"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e":[8,11,26],"\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443":[11,17,29],"\u0443\u0442\u0438\u043b\u0438\u0442\u0430":5,"\u0443\u0442\u0438\u043b\u0438\u0442\u044b":[36,37],"\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u044f":31,"\u0443\u0447\u0435\u0442\u043d\u044b\u0435":7,"\u0443\u0447\u0435\u0442\u043d\u044b\u0445":[11,33],"\u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442":22,"\u0443\u0447\u0451\u0442\u043e\u043c":9,"\u0444\u0430\u0431\u0440\u0438\u043a\u0430":36,"\u0444\u0430\u0431\u0440\u0438\u0447\u043d\u0430\u044f":29,"\u0444\u0430\u0439\u043b":[7,10,28,29,31,33],"\u0444\u0430\u0439\u043b\u0430":[1,7,29,31,33],"\u0444\u0430\u0439\u043b\u0430\u0445":[10,28],"\u0444\u0430\u0439\u043b\u0435":[28,33,34],"\u0444\u0430\u0439\u043b\u043e\u0432":28,"\u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0435":[33,34],"\u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0435":34,"\u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0445":[],"\u0444\u0430\u0439\u043b\u0443":29,"\u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0435\u0439":29,"\u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f":27,"\u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u0442":29,"\u0444\u043b\u0430\u0433":[15,16,20],"\u0444\u043e\u0440\u043c\u0430\u0442":[29,31,33,34],"\u0444\u043e\u0440\u043c\u0430\u0442\u0435":[9,10,13,15,16,22,27,28,29],"\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442":22,"\u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430":1,"\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430":29,"\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c":29,"\u0444\u0443\u043d\u043a\u0446\u0438\u044e":36,"\u0444\u0443\u043d\u043a\u0446\u0438\u044f":29,"\u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u043d\u044b\u0445":1,"\u0445\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c":33,"\u0445\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f":[],"\u0445\u043e\u043b\u0441\u0442\u0435":16,"\u0445\u043e\u0441\u0442\u0430":[9,18,22,27],"\u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c":29,"\u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u043e\u0432":29,"\u0445\u043e\u0441\u0442\u043d\u0435\u0439\u043c\u044b":29,"\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435":29,"\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f":[29,31],"\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430":[33,34,37],"\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435":[33,34],"\u0445\u0440\u0430\u043d\u0438\u0442":9,"\u0445\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u043c\u0443":33,"\u0445\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439":33,"\u0447\u0430\u0441\u0442\u0438":37,"\u0447\u0430\u0441\u0442\u043e\u0442":20,"\u0447\u0430\u0441\u0442\u043e\u0442\u0430":16,"\u0447\u0430\u0441\u0442\u043e\u0442\u044b":31,"\u0447\u0430\u0441\u0442\u044b\u0435":24,"\u0447\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u043c\u0438":3,"\u0447\u0435\u0440\u0435\u0437":[8,13,22,24,25,26,27,30],"\u0447\u0438\u0441\u043b\u0435":12,"\u0447\u0438\u0441\u043b\u043e":19,"\u0447\u0438\u0442\u0430\u0435\u0442":29,"\u0447\u0442\u0435\u043d\u0438\u0435":[30,33],"\u0447\u0442\u0435\u043d\u0438\u0438":[31,33,34],"\u0447\u0442\u0435\u043d\u0438\u044f":[12,27],"\u0447\u0442\u043e":[34,36],"\u0447\u0442\u043e\u0431\u044b":[29,30],"\u0448\u0438\u043d\u0435":[25,26],"\u0448\u0438\u043d\u0443":26,"\u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u043d\u0438\u044f":29,"\u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435":29,"\u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445":22,"\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f":20,"\u0448\u043b\u044e\u0437":16,"\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440":[19,29],"\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430":29,"\u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u0442":10,"\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b":37,"\u044f\u0432\u043d\u043e":27,"\u044f\u0437\u044b\u043a":[10,36],"\u044f\u0437\u044b\u043a\u0430":[7,8,9,10,11,13,29],"\u044f\u0437\u044b\u043a\u043e\u043c":10,"\u044f\u0437\u044b\u043a\u0443":36},"titles":["\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f","config.cors","config.init_config","config.ports","Core","core.cli_executor","API \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b","api.v1.auth","api.v1.ip","api.v1.network","api.v1.settings","api.v1.subnet","api.v1.ufw","api.v1.websocket","\u041c\u043e\u0434\u0435\u043b\u0438","models.auth","models.network","models.request","models.subnet","models.ufw","models.wifi","\u0421\u0435\u0440\u0432\u0438\u0441\u044b","ARP Service","Auth Service","Connect Service","Firewalld Service","IP Service","Network Service","Settings Service","Subnet Service","UFW Service","Wifi Service","\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430","storage.json_storage","storage.session_storage","\u0423\u0442\u0438\u043b\u0438\u0442\u044b","utils.i18n","GONETS SRV Documentation"],"titleterms":{"api":[6,7,8,9,10,11,12,13],"arp":22,"auth":[7,15,23],"cli_executor":5,"config":[1,2,3],"connect":24,"cor":1,"core":[4,5],"document":37,"firewalld":25,"gonet":37,"i18n":36,"init_config":2,"ip":[8,26],"json_storag":33,"model":[15,16,17,18,19,20],"network":[9,16,27],"port":3,"request":17,"servic":[22,23,24,25,26,27,28,29,30,31],"session_storag":34,"set":[10,28],"srv":37,"storag":[33,34],"subnet":[11,18,29],"ufw":[12,19,30],"util":36,"v1":[7,8,9,10,11,12,13],"websocket":13,"wifi":[20,31],"\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f":0,"\u043c\u043e\u0434\u0435\u043b\u0438":14,"\u0440\u0430\u0437\u0434\u0435\u043b\u044b":37,"\u0441\u0435\u0440\u0432\u0438\u0441\u044b":21,"\u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435":37,"\u0443\u0442\u0438\u043b\u0438\u0442\u044b":35,"\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430":32,"\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b":6}})
\ No newline at end of file
diff --git a/SRV/docs/_build/markdown/.doctrees/api/config.doctree b/SRV/docs/_build/markdown/.doctrees/api/config.doctree
new file mode 100644
index 0000000..f1718f8
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/config.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/config/cors.doctree b/SRV/docs/_build/markdown/.doctrees/api/config/cors.doctree
new file mode 100644
index 0000000..adbc8a8
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/config/cors.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/config/init_config.doctree b/SRV/docs/_build/markdown/.doctrees/api/config/init_config.doctree
new file mode 100644
index 0000000..5fc3e7b
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/config/init_config.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/config/ports.doctree b/SRV/docs/_build/markdown/.doctrees/api/config/ports.doctree
new file mode 100644
index 0000000..2c9ca03
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/config/ports.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/core.doctree b/SRV/docs/_build/markdown/.doctrees/api/core.doctree
new file mode 100644
index 0000000..7d26919
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/core.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/core/cli_executor.doctree b/SRV/docs/_build/markdown/.doctrees/api/core/cli_executor.doctree
new file mode 100644
index 0000000..63fadc9
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/core/cli_executor.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints.doctree
new file mode 100644
index 0000000..61c2e61
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/auth.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/auth.doctree
new file mode 100644
index 0000000..058369f
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/auth.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/ip.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/ip.doctree
new file mode 100644
index 0000000..eb55312
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/ip.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/network.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/network.doctree
new file mode 100644
index 0000000..b0b711d
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/network.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/settings.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/settings.doctree
new file mode 100644
index 0000000..568530a
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/settings.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/subnet.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/subnet.doctree
new file mode 100644
index 0000000..5de4fd5
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/subnet.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/ufw.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/ufw.doctree
new file mode 100644
index 0000000..72e0601
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/ufw.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/endpoints/websocket.doctree b/SRV/docs/_build/markdown/.doctrees/api/endpoints/websocket.doctree
new file mode 100644
index 0000000..a9e651a
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/endpoints/websocket.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models.doctree b/SRV/docs/_build/markdown/.doctrees/api/models.doctree
new file mode 100644
index 0000000..8b7a591
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/auth.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/auth.doctree
new file mode 100644
index 0000000..95449bf
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/auth.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/network.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/network.doctree
new file mode 100644
index 0000000..68d0891
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/network.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/request.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/request.doctree
new file mode 100644
index 0000000..b554977
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/request.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/subnet.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/subnet.doctree
new file mode 100644
index 0000000..ef397d2
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/subnet.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/ufw.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/ufw.doctree
new file mode 100644
index 0000000..ce999f4
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/ufw.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/models/wifi.doctree b/SRV/docs/_build/markdown/.doctrees/api/models/wifi.doctree
new file mode 100644
index 0000000..4fcaf7e
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/models/wifi.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services.doctree b/SRV/docs/_build/markdown/.doctrees/api/services.doctree
new file mode 100644
index 0000000..05fe385
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/arp_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/arp_service.doctree
new file mode 100644
index 0000000..423411b
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/arp_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/auth_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/auth_service.doctree
new file mode 100644
index 0000000..3d69920
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/auth_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/connect_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/connect_service.doctree
new file mode 100644
index 0000000..e4eb4e2
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/connect_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/firewalld_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/firewalld_service.doctree
new file mode 100644
index 0000000..772c341
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/firewalld_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/ip_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/ip_service.doctree
new file mode 100644
index 0000000..84882fe
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/ip_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/network_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/network_service.doctree
new file mode 100644
index 0000000..b3d4bfd
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/network_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/settings_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/settings_service.doctree
new file mode 100644
index 0000000..e71df78
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/settings_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/subnet_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/subnet_service.doctree
new file mode 100644
index 0000000..66a0b81
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/subnet_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/ufw_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/ufw_service.doctree
new file mode 100644
index 0000000..eff61ba
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/ufw_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/services/wifi_service.doctree b/SRV/docs/_build/markdown/.doctrees/api/services/wifi_service.doctree
new file mode 100644
index 0000000..f3dacae
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/services/wifi_service.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/storage.doctree b/SRV/docs/_build/markdown/.doctrees/api/storage.doctree
new file mode 100644
index 0000000..6612d61
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/storage.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/storage/json_storage.doctree b/SRV/docs/_build/markdown/.doctrees/api/storage/json_storage.doctree
new file mode 100644
index 0000000..9155e6d
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/storage/json_storage.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/storage/session_storage.doctree b/SRV/docs/_build/markdown/.doctrees/api/storage/session_storage.doctree
new file mode 100644
index 0000000..811b851
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/storage/session_storage.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/utils.doctree b/SRV/docs/_build/markdown/.doctrees/api/utils.doctree
new file mode 100644
index 0000000..8b267dc
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/utils.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/api/utils/i18n.doctree b/SRV/docs/_build/markdown/.doctrees/api/utils/i18n.doctree
new file mode 100644
index 0000000..4960a5b
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/api/utils/i18n.doctree differ
diff --git a/SRV/docs/_build/markdown/.doctrees/environment.pickle b/SRV/docs/_build/markdown/.doctrees/environment.pickle
new file mode 100644
index 0000000..3fec168
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/environment.pickle differ
diff --git a/SRV/docs/_build/markdown/.doctrees/index.doctree b/SRV/docs/_build/markdown/.doctrees/index.doctree
new file mode 100644
index 0000000..f79457a
Binary files /dev/null and b/SRV/docs/_build/markdown/.doctrees/index.doctree differ
diff --git a/SRV/docs/_build/markdown/api/config.md b/SRV/docs/_build/markdown/api/config.md
new file mode 100644
index 0000000..2a95be6
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/config.md
@@ -0,0 +1,5 @@
+# Конфигурация
+
+* [config.cors](config/cors.md)
+* [config.init_config](config/init_config.md)
+* [config.ports](config/ports.md)
diff --git a/SRV/docs/_build/markdown/api/config/cors.md b/SRV/docs/_build/markdown/api/config/cors.md
new file mode 100644
index 0000000..227c54f
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/config/cors.md
@@ -0,0 +1,27 @@
+# config.cors
+
+Конфигурация CORS для FastAPI-приложения GONETS.
+
+Модуль инкапсулирует добавление middleware CORSMiddleware со списком
+разрешённых источников (origins), характерных для локальной разработки и
+типовых портов фронтенда (например, Vite/React на 5173/5174, dev-серверы).
+
+Использование:
+: cors_config = CORSMiddlewareConfig(app)
+ cors_config.add_cors_middleware()
+
+Поведение:
+: - Разрешены все методы (allow_methods=[“\*”]) и заголовки (allow_headers=[“\*”]).
+ - Разрешены креденшлы (allow_credentials=True) для работы с куками (локаль и др.).
+ - При необходимости список origins можно расширить или параметризовать
+ на основе конфигурационного файла окружения.
+
+### *class* config.cors.CORSMiddlewareConfig(app: FastAPI)
+
+Bases: `object`
+
+Класс для настройки CORS политики в FastAPI
+
+#### add_cors_middleware() → None
+
+Добавляем CORS в приложение FastAPI
diff --git a/SRV/docs/_build/markdown/api/config/init_config.md b/SRV/docs/_build/markdown/api/config/init_config.md
new file mode 100644
index 0000000..881b670
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/config/init_config.md
@@ -0,0 +1 @@
+# config.init_config
diff --git a/SRV/docs/_build/markdown/api/config/ports.md b/SRV/docs/_build/markdown/api/config/ports.md
new file mode 100644
index 0000000..c8f9809
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/config/ports.md
@@ -0,0 +1,28 @@
+# config.ports
+
+### *class* config.ports.PortConfig(custom_ports: Dict[int, str] = None)
+
+Bases: `object`
+
+Конфигурация известных портов для сетевого анализа.
+
+Назначение:
+: Предоставляет сопоставление распространённых портов с человекочитаемыми
+ названиями сервисов для отображения в UI и логах. Поддерживает расширение
+ пользовательским словарём.
+
+#### get_port_name(port: int) → str
+
+Возвращает название сервиса для указанного порта.
+
+* **Parameters:**
+ **port** – Номер порта.
+* **Returns:**
+ Название сервиса или строка вида ‘Port-’.
+
+#### get_ports() → List[int]
+
+Возвращает список всех номеров портов.
+
+* **Returns:**
+ Список номеров портов.
diff --git a/SRV/docs/_build/markdown/api/core.md b/SRV/docs/_build/markdown/api/core.md
new file mode 100644
index 0000000..06015c4
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/core.md
@@ -0,0 +1,3 @@
+# Core
+
+* [core.cli_executor](core/cli_executor.md)
diff --git a/SRV/docs/_build/markdown/api/core/cli_executor.md b/SRV/docs/_build/markdown/api/core/cli_executor.md
new file mode 100644
index 0000000..1c3c3a3
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/core/cli_executor.md
@@ -0,0 +1,22 @@
+# core.cli_executor
+
+### *class* core.cli_executor.CLIExecutor
+
+Bases: `object`
+
+Утилита для безопасного выполнения CLI-команд.
+
+Назначение:
+: Инкапсулирует вызов subprocess.run и приводит результат к удобному
+ кортежу (код возврата, stdout) без генерации исключений при ошибках.
+
+#### *static* run(command: list) → Tuple[int, str]
+
+Выполняет команду в системе и возвращает код и вывод.
+
+* **Parameters:**
+ **command** (*list*) – Список аргументов команды для выполнения.
+* **Returns:**
+ Код возврата и вывод команды.
+* **Return type:**
+ tuple
diff --git a/SRV/docs/_build/markdown/api/endpoints.md b/SRV/docs/_build/markdown/api/endpoints.md
new file mode 100644
index 0000000..966af00
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints.md
@@ -0,0 +1,9 @@
+# API эндпоинты
+
+* [api.v1.auth](endpoints/auth.md)
+* [api.v1.ip](endpoints/ip.md)
+* [api.v1.network](endpoints/network.md)
+* [api.v1.settings](endpoints/settings.md)
+* [api.v1.subnet](endpoints/subnet.md)
+* [api.v1.ufw](endpoints/ufw.md)
+* [api.v1.websocket](endpoints/websocket.md)
diff --git a/SRV/docs/_build/markdown/api/endpoints/auth.md b/SRV/docs/_build/markdown/api/endpoints/auth.md
new file mode 100644
index 0000000..5a19f57
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/auth.md
@@ -0,0 +1,90 @@
+# api.v1.auth
+
+### *async* api.v1.auth.check_users_file(translations: Translations = Depends(translations_dependency))
+
+Проверяет, существует ли файл users.json и содержит ли он пользователей.
+
+Возвращает информацию о состоянии файла и наличии пользователей.
+
+* **Returns:**
+ Словарь с ключами:
+ : - ”exists” (bool): Существует ли файл.
+ - ”has_users” (bool): Содержит ли файл пользователей.
+ - ”message” (str): Сообщение с описанием результата.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если произошла ошибка при проверке файла (status_code=500).
+
+### *async* api.v1.auth.get_current_user(request: Request, translations: Translations = Depends(translations_dependency))
+
+Возвращает данные текущего пользователя и токен.
+
+Проверяет активную сессию по client_id и возвращает информацию о пользователе
+и токен для дальнейшего использования.
+
+* **Parameters:**
+ * **request** (*Request*) – Объект запроса для получения client_id (IP-адрес клиента).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами “user” (UserResponse), “access_token” (str) и
+ : ”token_type” (str, всегда “bearer”).
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если сессия не найдена или истекла (status_code=401) или
+ произошла внутренняя ошибка (status_code=500).
+
+### *async* api.v1.auth.login(user: UserLogin, request: Request, translations: Translations = Depends(translations_dependency))
+
+Авторизует пользователя и создаёт сессию.
+
+Проверяет учетные данные пользователя и возвращает токен и session_id для
+последующей авторизации.
+
+* **Parameters:**
+ * **user** (*UserLogin*) – Объект с данными для входа (username, password).
+ * **request** (*Request*) – Объект запроса для получения client_id (IP-адрес клиента).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Объект с access_token и session_id для авторизации.
+* **Return type:**
+ Token
+* **Raises:**
+ **HTTPException** – Если учетные данные неверны (status_code=401) или произошла
+ внутренняя ошибка (status_code=500).
+
+### *async* api.v1.auth.logout(request: Request, translations: Translations = Depends(translations_dependency))
+
+Выход из системы, удаление сессии.
+
+Удаляет активную сессию пользователя по client_id.
+
+* **Parameters:**
+ * **request** (*Request*) – Объект запроса для получения client_id (IP-адрес клиента).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Пустой словарь или сообщение об успешном выходе.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если сессия не найдена (status_code=401) или произошла
+ внутренняя ошибка (status_code=500).
+
+### *async* api.v1.auth.register(user: UserCreate, translations: Translations = Depends(translations_dependency))
+
+Регистрирует нового пользователя.
+
+Создаёт нового пользователя на основе предоставленных данных и возвращает
+информацию о зарегистрированном пользователе.
+
+* **Parameters:**
+ * **user** (*UserCreate*) – Объект с данными нового пользователя (username, password, is_admin).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Объект с данными зарегистрированного пользователя (id, username, is_admin).
+* **Return type:**
+ UserResponse
+* **Raises:**
+ **HTTPException** – Если пользователь уже существует (status_code=400) или произошла
+ внутренняя ошибка (status_code=500).
diff --git a/SRV/docs/_build/markdown/api/endpoints/ip.md b/SRV/docs/_build/markdown/api/endpoints/ip.md
new file mode 100644
index 0000000..2997a1e
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/ip.md
@@ -0,0 +1,63 @@
+# api.v1.ip
+
+### *async* api.v1.ip.apply_ip_config(data: Dict[str, Any] = Body(PydanticUndefined), translations: Translations = Depends(translations_dependency))
+
+Применяет проверенные IP-настройки к указанному сетевому профилю.
+
+Обновляет настройки соединения через NetworkManager, отключает и
+переподключает устройство к сети.
+
+* **Parameters:**
+ * **data** (*Dict* *[**str* *,* *Any* *]*) – Словарь с данными конфигурации, включая
+ “connection_id” и “ipv4_updates” с обновлениями IP-настроек.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами:
+ : - ”success” (bool): Успешность операции.
+ - ”message” (str): Сообщение об успешном применении настроек.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если подключение не найдено (status_code=404) или
+ произошла внутренняя ошибка (status_code=500).
+
+### *async* api.v1.ip.check_ip_config(data: Dict[str, Any] = Body(PydanticUndefined), translations: Translations = Depends(translations_dependency))
+
+Проверяет доступность IP-адреса перед его применением.
+
+Выполняет ARP-сканирование для проверки, занят ли указанный IP-адрес,
+и возвращает результат проверки.
+
+* **Parameters:**
+ * **data** (*Dict* *[**str* *,* *Any* *]*) – Словарь с данными конфигурации, включая
+ “ipv4_updates” с полем “ipv4.address-data” для нового IP-адреса.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами:
+ : - ”available” (bool): Доступен ли IP-адрес.
+ - ”message” (str): Сообщение о результате проверки.
+* **Return type:**
+ Dict[str, Any]
+* **Raises:**
+ **HTTPException** – Если произошла внутренняя ошибка (status_code=500).
+
+### *async* api.v1.ip.save_ip_config(data: Dict[str, Any] = Body(PydanticUndefined), translations: Translations = Depends(translations_dependency))
+
+Применяет проверенные IP-настройки для отката сетевого профиля.
+
+Используется для сохранения и отката настроек сетевого профиля к предыдущему
+состоянию через NetworkManager.
+
+* **Parameters:**
+ * **data** (*Dict* *[**str* *,* *Any* *]*) – Словарь с данными конфигурации, включая
+ “connection_id” и “ipv4_updates” с обновлениями IP-настроек.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами:
+ : - ”success” (bool): Успешность операции.
+ - ”message” (str): Сообщение об успешном применении настроек.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если подключение не найдено (status_code=404) или
+ произошла внутренняя ошибка (status_code=500).
diff --git a/SRV/docs/_build/markdown/api/endpoints/network.md b/SRV/docs/_build/markdown/api/endpoints/network.md
new file mode 100644
index 0000000..b911246
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/network.md
@@ -0,0 +1,93 @@
+# api.v1.network
+
+### *class* api.v1.network.ContainerPositionManager
+
+Bases: `object`
+
+Класс для управления позициями и состоянием контейнеров.
+
+Хранит и обновляет координаты (x, y) и состояние (isOpen) для контейнеров
+активации и деактивации.
+
+#### get_position_and_state(container_name: str, translations: Translations = Depends(translations_dependency)) → Tuple[int, int, bool]
+
+Получает текущие координаты и состояние указанного контейнера.
+
+* **Parameters:**
+ * **container_name** (*str*) – Имя контейнера (“activation” или “deactivation”).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Кортеж с координатами (x, y) и состоянием (isOpen).
+* **Return type:**
+ Tuple[int, int, bool]
+* **Raises:**
+ **ValueError** – Если указан неверный идентификатор контейнера.
+
+#### update_position(container_id: int, x: int, y: int, isOpen: bool) → None
+
+Обновляет позицию и состояние контейнера по его идентификатору.
+
+* **Parameters:**
+ * **container_id** (*int*) – Идентификатор контейнера (1 для “activation”, 2 для “deactivation”).
+ * **x** (*int*) – Новая координата X.
+ * **y** (*int*) – Новая координата Y.
+ * **isOpen** (*bool*) – Новое состояние контейнера (открыт/закрыт).
+* **Raises:**
+ **ValueError** – Если container_id не соответствует ни одному контейнеру.
+
+### *async* api.v1.network.get_hostname(translations: Translations = Depends(translations_dependency))
+
+Возвращает имя хоста системы.
+
+Выполняет запрос к сервису для получения имени хоста.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключом “hostname” и значением имени хоста.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если произошла ошибка при получении имени хоста (status_code=500).
+
+### *async* api.v1.network.get_network_data(translations: Translations = Depends(translations_dependency)) → Dict[str, Dict]
+
+Возвращает текущие сетевые данные в виде словаря.
+
+Получает данные о сетевых интерфейсах, разделяет их на активные и неактивные,
+и возвращает их в формате Activation и Deactivation с учётом позиций и состояний
+контейнеров.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами “activation” и “deactivation”, содержащими
+ : данные о соответствующих контейнерах.
+* **Return type:**
+ Dict[str, Dict]
+* **Raises:**
+ **HTTPException** – Если произошла ошибка при получении данных (status_code=500).
+
+### *async* api.v1.network.update_container_position(data: Dict[str, int | bool] = Body(PydanticUndefined), translations: Translations = Depends(translations_dependency))
+
+Обновляет позицию и состояние контейнера.
+
+Принимает данные о новом положении и состоянии контейнера и обновляет их
+в менеджере позиций.
+
+* **Parameters:**
+ * **data** (*Dict* *[**str* *,* *Union* *[**int* *,* *bool* *]* *]*) – Словарь с ключами:
+ - “container_id” (int): Идентификатор контейнера (1 или 2).
+ - “container_x” (int): Новая координата X.
+ - “container_y” (int): Новая координата Y.
+ - “isOpen” (bool): Новое состояние контейнера (открыт/закрыт).
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами:
+ : - ”success” (bool): Успешность операции.
+ - ”message” (str): Сообщение об успешном обновлении.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если отсутствуют обязательные поля (status_code=400) или
+ типы данных неверны.
diff --git a/SRV/docs/_build/markdown/api/endpoints/settings.md b/SRV/docs/_build/markdown/api/endpoints/settings.md
new file mode 100644
index 0000000..a892d4a
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/settings.md
@@ -0,0 +1,104 @@
+# api.v1.settings
+
+### api.v1.settings.clear_all_data(translations: Translations = Depends(translations_dependency))
+
+Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
+
+Удаляет все записи из devices.json и wifi.json.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешной очистке.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если произошла ошибка очистки (status_code=500).
+
+### api.v1.settings.clear_subnet(subnet: str | None = None, translations: Translations = Depends(translations_dependency))
+
+Очищает данные узлов подсети, сохранённые в devices.json.
+
+Удаляет все записи об узлах подсети, оставляя файл пустым.
+
+* **Parameters:**
+ * **subnet** (*Optional* *[**str* *]*) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+ Если None, очищает все подсети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешной очистке.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если произошла ошибка очистки (status_code=500).
+
+### api.v1.settings.clear_wifi(translations: Translations = Depends(translations_dependency))
+
+Очищает данные точек доступа Wi-Fi, сохранённые в wifi.json.
+
+Удаляет все записи о точках доступа, оставляя файл пустым.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешной очистке.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если произошла ошибка очистки (status_code=500).
+
+### *async* api.v1.settings.export_devices(request: SubnetScanRequest = Body(PydanticUndefined), translations: Translations = Depends(translations_dependency))
+
+Экспортирует файл с данными устройств в формате JSON для указанной подсети.
+
+* **Parameters:**
+ * **request** (*SubnetScanRequest*) – Объект с полем subnet в формате CIDR.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Файл .json для скачивания.
+* **Return type:**
+ FileResponse
+* **Raises:**
+ **HTTPException** – Если файл не найден (status_code=404).
+
+### *async* api.v1.settings.export_wifi(translations: Translations = Depends(translations_dependency))
+
+Экспортирует файл с данными устройств в формате JSON.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Файл wifi.json для скачивания.
+* **Return type:**
+ FileResponse
+* **Raises:**
+ **HTTPException** – Если файл не найден (status_code=404).
+
+### *async* api.v1.settings.get_system_language(request: Request, translations: Translations = Depends(translations_dependency))
+
+Выводит текущий язык, установленный в системе на основе locale.
+
+Выполняет команду ‘locale’ в системе и извлекает параметры LANG
+
+* **Parameters:**
+ * **request** (*Request*) – Объект запроса для доступа к параметрам.
+ * **translations** – Объект переводов текущего языка.
+* **Returns:**
+ Словарь с текущим языком системы.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если не удалось извлечь системный язык (status_code=500).
+
+### api.v1.settings.set_language(locale: str, response: Response, translations: Translations = Depends(translations_dependency))
+
+Устанавливает куку locale для смены языка пользователя.
+
+* **Parameters:**
+ * **locale** (*str*) – Используемый язык.
+ * **response** (*Response*) – Запрос.
+ * **translations** – Объект переводов текущего языка.
+* **Returns:**
+ Сообщение об успешной установке языка.
+* **Return type:**
+ dict
diff --git a/SRV/docs/_build/markdown/api/endpoints/subnet.md b/SRV/docs/_build/markdown/api/endpoints/subnet.md
new file mode 100644
index 0000000..cae5698
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/subnet.md
@@ -0,0 +1,193 @@
+# api.v1.subnet
+
+### api.v1.subnet.add_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(translations_dependency))
+
+Добавляет заметку для устройства по MAC-адресу.
+
+Привязывает заметку к устройству, идентифицируемому по MAC-адресу.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **request** (*NoteRequest*) – Объект с полем “note” для текста заметки.
+ * **subnet_service** – Сервис сканирования подсети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном добавлении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если устройство не найдено (status_code=404), заметка уже
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+### api.v1.subnet.add_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(translations_dependency))
+
+Добавляет заметку для сети Wi-Fi по SSID.
+
+Привязывает заметку к Wi-Fi сети, идентифицируемой по SSID.
+
+* **Parameters:**
+ * **ssid** (*str*) – Идентификатор Wi-Fi сети.
+ * **request** (*NoteRequest*) – Объект с полем “note” для текста заметки.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном добавлении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если сеть не найдена (status_code=404), заметка уже
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+### api.v1.subnet.connect_to_wifi(request: WifiConnectRequest, translations: Translations = Depends(translations_dependency))
+
+Подключается к Wi-Fi сети по SSID и паролю.
+
+Выполняет подключение к указанной Wi-Fi сети с использованием предоставленных
+учетных данных.
+
+* **Parameters:**
+ * **request** (*WifiConnectRequest*) – Объект с полями “ssid” и “password”.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном подключении.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если подключение не удалось (status_code=400).
+
+### api.v1.subnet.delete_note(mac: str, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(translations_dependency))
+
+Удаляет заметку для устройства по MAC-адресу.
+
+Удаляет привязанную заметку для указанного устройства.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **subnet_service** – Сервис сканирования подсети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном удалении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если устройство не найдено (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+### api.v1.subnet.delete_wifi_note(ssid: str, translations: Translations = Depends(translations_dependency))
+
+Удаляет заметку для сети Wi-Fi по SSID.
+
+Удаляет привязанную заметку для указанной Wi-Fi сети.
+
+* **Parameters:**
+ * **ssid** (*str*) – Идентификатор Wi-Fi сети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном удалении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если сеть не найдена (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+### api.v1.subnet.disconnect_from_wifi(request: WifiDisconnectRequest, translations: Translations = Depends(translations_dependency))
+
+Отключается от Wi-Fi сети по указанному SSID.
+
+Выполняет отключение от указанной Wi-Fi сети.
+
+* **Parameters:**
+ * **request** (*WifiDisconnectRequest*) – Объект с полем “ssid”.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном отключении.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если отключение не удалось (status_code=400).
+
+### api.v1.subnet.get_wifi_status(translations: Translations = Depends(translations_dependency))
+
+Проверяет статус текущего подключения к Wi-Fi.
+
+Возвращает информацию о текущем состоянии подключения и SSID, если подключение активно.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с ключами:
+ : - ”status” (str): Состояние (“connected” или другое).
+ - ”ssid” (str, optional): SSID текущей сети, если подключено.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если проверка статуса не удалась (status_code=400).
+
+### api.v1.subnet.scan_subnet(request: SubnetScanRequest = Body(PydanticUndefined), subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(translations_dependency))
+
+Сканирует локальную подсеть и возвращает данные об устройствах.
+
+Выполняет сканирование указанной подсети для обнаружения активных устройств.
+
+* **Parameters:**
+ * **request** (*SubnetScanRequest*) – Объект с полем subnet.
+ * **subnet_service** – Сервис сканирования подсети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Объект с результатами сканирования (устройства и их данные).
+* **Return type:**
+ SubnetScanResult
+* **Raises:**
+ **HTTPException** – Если произошла ошибка сканирования (status_code=500).
+
+### api.v1.subnet.scan_wifi(timeout: int = 30, translations: Translations = Depends(translations_dependency))
+
+Сканирует Wi-Fi точки доступа и возвращает данные о них.
+
+Выполняет сканирование доступных Wi-Fi сетей с указанным таймаутом.
+
+* **Parameters:**
+ * **timeout** (*int* *,* *optional*) – Таймаут сканирования в секундах. По умолчанию 30.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Объект с результатами сканирования (список Wi-Fi сетей).
+* **Return type:**
+ WifiScanResult
+* **Raises:**
+ **HTTPException** – Если произошла ошибка сканирования Wi-Fi (status_code=500).
+
+### api.v1.subnet.update_note(mac: str, request: NoteRequest, subnet_service: SubnetService = Depends(get_subnet_service), translations: Translations = Depends(translations_dependency))
+
+Обновляет заметку для устройства по MAC-адресу.
+
+Изменяет существующую заметку, привязанную к устройству.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **request** (*NoteRequest*) – Объект с полем “note” для нового текста заметки.
+ * **subnet_service** – Сервис сканирования подсети.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном обновлении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если устройство не найдено (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
+
+### api.v1.subnet.update_wifi_note(ssid: str, request: NoteRequest, translations: Translations = Depends(translations_dependency))
+
+Обновляет заметку для сети Wi-Fi по SSID.
+
+Изменяет существующую заметку, привязанную к Wi-Fi сети.
+
+* **Parameters:**
+ * **ssid** (*str*) – Идентификатор Wi-Fi сети.
+ * **request** (*NoteRequest*) – Объект с полем “note” для нового текста заметки.
+ * **translations** – Объект переводов для текущего языка.
+* **Returns:**
+ Словарь с сообщением об успешном обновлении заметки.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если сеть не найдена (status_code=404), заметка не
+ существует (status_code=400), или произошла внутренняя ошибка (status_code=500).
diff --git a/SRV/docs/_build/markdown/api/endpoints/ufw.md b/SRV/docs/_build/markdown/api/endpoints/ufw.md
new file mode 100644
index 0000000..6e54570
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/ufw.md
@@ -0,0 +1,110 @@
+# api.v1.ufw
+
+### *class* api.v1.ufw.RuleNumbers(, numbers: List[int])
+
+Bases: `BaseModel`
+
+Модель запроса на удаление UFW правил.
+
+Атрибуты:
+
+- `numbers` (List[int]): Список номеров правил для удаления.
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### numbers *: List[int]*
+
+### *async* api.v1.ufw.add_ufw_in_rules(rules: List[UFWRule])
+
+Добавляет входящие (in) правила UFW.
+
+* **Parameters:**
+ **rules** (*List* *[**UFWRule* *]*) – Список правил, определяющих направления,
+ порты/диапазоны/протоколы и действия (allow/deny/reject).
+* **Returns:**
+ Итог выполнения с количеством успешно добавленных правил и деталями
+ по каждому правилу.
+* **Return type:**
+ dict
+* **Raises:**
+ * **HTTPException** – Если UFW отключен (status_code=400) или при внутренних
+ * **ошибках** **(****status_code=500****)****.** –
+
+### *async* api.v1.ufw.add_ufw_out_rules(rules: List[UFWRule])
+
+Добавляет исходящие (out) правила UFW.
+
+* **Parameters:**
+ **rules** (*List* *[**UFWRule* *]*) – Список правил исходящего трафика для добавления.
+* **Returns:**
+ Объект статуса с сообщением об успешном добавлении.
+* **Return type:**
+ dict
+* **Raises:**
+ * **HTTPException** – Если UFW отключен (status_code=400) или при внутренних
+ * **ошибках** **(****status_code=500****)****.** –
+
+### *async* api.v1.ufw.delete_ufw_rules(rule_numbers: RuleNumbers)
+
+Удаляет правила UFW по их номерам.
+
+* **Parameters:**
+ **rule_numbers** (*RuleNumbers*) – Номера правил, подлежащих удалению (как в
+ выводе ufw status numbered).
+* **Returns:**
+ Информация об общем количестве удаляемых правил, числе успешных и
+ возможной ошибке.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – При ошибках удаления (status_code=500).
+
+### *async* api.v1.ufw.disable_ufw()
+
+Выключает UFW.
+
+* **Returns:**
+ Словарь с ключами status и message при успешном выполнении.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если выключение завершилось неуспешно (status_code=500).
+
+### *async* api.v1.ufw.enable_ufw()
+
+Включает UFW.
+
+* **Returns:**
+ Словарь с ключами status и message при успешном выполнении.
+* **Return type:**
+ dict
+* **Raises:**
+ **HTTPException** – Если включение завершилось неуспешно (status_code=500).
+
+### *async* api.v1.ufw.get_ufw_rules()
+
+Возвращает все правила UFW.
+
+Осуществляет обращение к сервису UFWService для чтения текущего списка
+правил брандмауэра и возвращает результат в сериализованном виде для клиента.
+
+* **Returns:**
+ Структура с коллекцией правил и сопутствующей
+ информацией о состоянии.
+* **Return type:**
+ UFWRulesResponse
+* **Raises:**
+ **HTTPException** – В случае ошибок взаимодействия с UFW (status_code=500).
+
+### *async* api.v1.ufw.get_ufw_status()
+
+Возвращает текущий статус UFW.
+
+* **Returns:**
+ Объект со сведениями о включенности UFW и деталями статуса.
+* **Return type:**
+ UFWStatus
+* **Raises:**
+ **HTTPException** – Если не удалось получить статус (status_code=500).
diff --git a/SRV/docs/_build/markdown/api/endpoints/websocket.md b/SRV/docs/_build/markdown/api/endpoints/websocket.md
new file mode 100644
index 0000000..2e0e595
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/endpoints/websocket.md
@@ -0,0 +1,78 @@
+# api.v1.websocket
+
+### *class* api.v1.websocket.WebSocketManager
+
+Bases: `object`
+
+Класс для управления подключениями к WebSocket-серверу.
+
+Обрабатывает подключение, отключение и трансляцию сообщений для клиентов,
+подключённых через WebSocket.
+
+#### *async* broadcast(message: dict)
+
+Транслирует сообщения всем подключённым клиентам.
+
+Отправляет JSON-данные всем активным соединениям, обрабатывая
+отключения клиентов.
+
+* **Parameters:**
+ **message** (*dict*) – Сообщение для трансляции в формате JSON.
+* **Raises:**
+ * **WebSocketDisconnect** – Если клиент неожиданно отключился.
+ * **Exception** – Если произошла другая ошибка при отправке.
+
+#### *async* connect(websocket: WebSocket)
+
+Подключает клиента к WebSocket-серверу.
+
+Принимает подключение и добавляет WebSocket в список активных соединений.
+
+* **Parameters:**
+ **websocket** (*WebSocket*) – Объект WebSocket для подключения.
+* **Raises:**
+ **Exception** – Если произошла ошибка при принятии подключения.
+
+#### disconnect(websocket: WebSocket)
+
+Отключает клиента от WebSocket-серверу.
+
+Удаляет WebSocket из списка активных соединений.
+
+* **Parameters:**
+ **websocket** (*WebSocket*) – Объект WebSocket для отключения.
+
+### *async* api.v1.websocket.broadcast_data(translations: Translations = Depends(translations_dependency))
+
+Таск для периодической трансляции сетевых данных.
+
+Выполняет сканирование сети и отправляет данные всем подключённым клиентам
+с интервалом в 2 секунды.
+
+* **Parameters:**
+ **translations** – Объект переводов для текущего языка.
+* **Raises:**
+ **Exception** – Если произошла ошибка при получении данных или трансляции.
+
+### *async* api.v1.websocket.startup_event()
+
+Запуск таска для трансляции сетевых данных при старте приложения.
+
+Создаёт асинхронную задачу для периодической трансляции данных.
+
+* **Raises:**
+ **Exception** – Если произошла ошибка при запуске таска.
+
+### *async* api.v1.websocket.websocket_endpoint(websocket: WebSocket, translations: Translations = Depends(translations_dependency))
+
+Обработчик WebSocket-соединения.
+
+Управляет соединением клиента, принимает данные для обновления позиций
+контейнеров и транслирует сетевые данные.
+
+* **Parameters:**
+ * **websocket** (*WebSocket*) – Объект WebSocket для обработки соединения.
+ * **translations** – Объект переводов для текущего языка.
+* **Raises:**
+ * **WebSocketDisconnect** – Если клиент отключился.
+ * **Exception** – Если произошла ошибка при обработке данных.
diff --git a/SRV/docs/_build/markdown/api/models.md b/SRV/docs/_build/markdown/api/models.md
new file mode 100644
index 0000000..9333fab
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models.md
@@ -0,0 +1,8 @@
+# Модели
+
+* [models.auth](models/auth.md)
+* [models.network](models/network.md)
+* [models.request](models/request.md)
+* [models.subnet](models/subnet.md)
+* [models.ufw](models/ufw.md)
+* [models.wifi](models/wifi.md)
diff --git a/SRV/docs/_build/markdown/api/models/auth.md b/SRV/docs/_build/markdown/api/models/auth.md
new file mode 100644
index 0000000..a324a0f
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/auth.md
@@ -0,0 +1,89 @@
+# models.auth
+
+### *class* models.auth.Token(, access_token: str, token_type: str, session_id: str | None = None, user: UserResponse | None = None)
+
+Bases: `BaseModel`
+
+Модель JWT-токена.
+
+Атрибуты:
+
+- `access_token` (str): Значение токена (в формате JWT).
+- `token_type` (str): Тип токена (обычно `bearer`).
+- `session_id` (Optional[str]): Идентификатор сессии.
+- `user` (Optional[UserResponse]): Данные пользователя, если включены в ответ.
+
+#### access_token *: str*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### session_id *: str | None*
+
+#### token_type *: str*
+
+#### user *: UserResponse | None*
+
+### *class* models.auth.UserCreate(, username: str, password: str, is_admin: bool = False)
+
+Bases: `BaseModel`
+
+Модель созданного пользователя.
+
+Атрибуты:
+
+- `username` (str): Имя пользователя.
+- `password` (str): Пароль пользователя.
+- `is_admin` (bool): Флаг административного доступа.
+
+#### is_admin *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### password *: str*
+
+#### username *: str*
+
+### *class* models.auth.UserLogin(, username: str, password: str)
+
+Bases: `BaseModel`
+
+Модель авторизации пользователя.
+
+Атрибуты:
+
+- `username` (str): Имя пользователя.
+- `password` (str): Пароль пользователя.
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### password *: str*
+
+#### username *: str*
+
+### *class* models.auth.UserResponse(, id: int, username: str, is_admin: bool)
+
+Bases: `BaseModel`
+
+Модель, используемая при возврате сведений о пользователе.
+
+Атрибуты:
+
+- `id` (int): Идентификатор пользователя.
+- `username` (str): Имя пользователя.
+- `is_admin` (bool): Флаг административного доступа.
+
+#### id *: int*
+
+#### is_admin *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### username *: str*
diff --git a/SRV/docs/_build/markdown/api/models/network.md b/SRV/docs/_build/markdown/api/models/network.md
new file mode 100644
index 0000000..865ebde
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/network.md
@@ -0,0 +1,247 @@
+# models.network
+
+### *class* models.network.Activation(, id: int, interfaces: List[NetworkInterface | WiFiDevice], container_x: int, container_y: int, isOpen: bool = True)
+
+Bases: `BaseModel`
+
+Модель узла активных интерфейсов.
+
+Атрибуты:
+
+- `id` (int): Идентификатор ноды.
+- `interfaces` (List[Union[NetworkInterface, WiFiDevice]]): Активные интерфейсы.
+- `container_x` (int): Координата X на холсте.
+- `container_y` (int): Координата Y на холсте.
+- `isOpen` (bool): Флаг отображения узла на холсте.
+
+#### container_x *: int*
+
+#### container_y *: int*
+
+#### id *: int*
+
+#### interfaces *: List[NetworkInterface | WiFiDevice]*
+
+#### isOpen *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+### *class* models.network.Deactivation(, id: int, interfaces: List[NetworkInterface | WiFiDevice], container_x: int, container_y: int, isOpen: bool = True)
+
+Bases: `BaseModel`
+
+Модель узла отключённых интерфейсов.
+
+Атрибуты:
+
+- `id` (int): Идентификатор ноды.
+- `interfaces` (List[Union[NetworkInterface, WiFiDevice]]): Список отключённых интерфейсов.
+- `container_x` (int): Координата X на холсте.
+- `container_y` (int): Координата Y на холсте.
+- `isOpen` (bool): Флаг отображения узла на холсте.
+
+#### container_x *: int*
+
+#### container_y *: int*
+
+#### id *: int*
+
+#### interfaces *: List[NetworkInterface | WiFiDevice]*
+
+#### isOpen *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+### *class* models.network.IPConfig(, configurations: List[IndividualIPConfig])
+
+Bases: `BaseModel`
+
+Групповая конфигурация IP-адресов.
+
+Атрибуты:
+
+- `configurations` (List[IndividualIPConfig]): Индивидуальные IP-конфигурации.
+
+#### configurations *: List[IndividualIPConfig]*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+### *class* models.network.IPv4Route(, destination: str, prefix: int, next_hop: str, metric: int)
+
+Bases: `BaseModel`
+
+Модель маршрута IPv4.
+
+Атрибуты:
+
+- `destination` (str): IP-адрес назначения.
+- `prefix` (int): Префикс подсети в формате CIDR.
+- `next_hop` (str): Адрес следующего узла (маршрутизатор).
+- `metric` (int): Метрика маршрута.
+
+#### destination *: str*
+
+#### metric *: int*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### next_hop *: str*
+
+#### prefix *: int*
+
+### *class* models.network.IPv6Route(, destination: str, prefix: int, next_hop: str, metric: int)
+
+Bases: `BaseModel`
+
+Модель маршрута IPv6.
+
+Атрибуты:
+
+- `destination` (str): IP-адрес назначения.
+- `prefix` (int): Префикс подсети в формате CIDR.
+- `next_hop` (str): Адрес следующего узла.
+- `metric` (int): Метрика маршрута.
+
+#### destination *: str*
+
+#### metric *: int*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### next_hop *: str*
+
+#### prefix *: int*
+
+### *class* models.network.IndividualIPConfig(, address: str, routes: List[IPv4Route | IPv6Route], method: str | None, gateway: str, dns: List[str])
+
+Bases: `BaseModel`
+
+Модель индивидуальной конфигурации IP-адреса.
+
+Атрибуты:
+
+- `address` (str): IP-адрес интерфейса.
+- `routes` (List[Union[IPv4Route, IPv6Route]]): Список маршрутов.
+- `method` (Optional[str]): Метод назначения IP.
+- `gateway` (str): Шлюз по умолчанию.
+- `dns` (List[str]): Список DNS-серверов.
+
+#### address *: str*
+
+#### dns *: List[str]*
+
+#### gateway *: str*
+
+#### method *: str | None*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### routes *: List[IPv4Route | IPv6Route]*
+
+### *class* models.network.NetworkInterface(, name: str, type: str, mac_address: str, device_state: str, active_connection: str, device_mtu: str, ipv4: IPConfig, ipv6: IPConfig, container_x: int = 0, container_y: int = 0)
+
+Bases: `BaseModel`
+
+Модель базового сетевого интерфейса.
+
+Атрибуты:
+
+- `name` (str): Название интерфейса.
+- `type` (str): Тип устройства.
+- `mac_address` (str): MAC-адрес.
+- `device_state` (str): Текущее состояние.
+- `active_connection` (str): Название активного соединения.
+- `device_mtu` (str): MTU устройства.
+- `ipv4` (IPConfig): IPv4-конфигурация.
+- `ipv6` (IPConfig): IPv6-конфигурация.
+- `container_x` (int): Положение по оси X на холсте.
+- `container_y` (int): Положение по оси Y на холсте.
+
+#### active_connection *: str*
+
+#### container_x *: int*
+
+#### container_y *: int*
+
+#### device_mtu *: str*
+
+#### device_state *: str*
+
+#### ipv4 *: IPConfig*
+
+#### ipv6 *: IPConfig*
+
+#### mac_address *: str*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### name *: str*
+
+#### type *: str*
+
+### *class* models.network.NetworkInterfacesContainer(, interfaces: List[NetworkInterface | WiFiDevice], container_x: int, container_y: int)
+
+Bases: `BaseModel`
+
+Контейнер для набора сетевых интерфейсов.
+
+Атрибуты:
+
+- `interfaces` (List[Union[NetworkInterface, WiFiDevice]]): Список интерфейсов.
+- `container_x` (int): Положение по оси X.
+- `container_y` (int): Положение по оси Y.
+
+#### container_x *: int*
+
+#### container_y *: int*
+
+#### interfaces *: List[NetworkInterface | WiFiDevice]*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+### *class* models.network.WiFiDevice(, name: str, type: str, mac_address: str, device_state: str, active_connection: str, device_mtu: str, ipv4: IPConfig, ipv6: IPConfig, container_x: int = 0, container_y: int = 0, ssid: str | None = None, bssid: str | None = None, bitrate: int | None = None, strength: int | None = None, frequency: float | None = None, mode: str | int = None)
+
+Bases: `NetworkInterface`
+
+Расширенная модель Wi-Fi устройства.
+
+Атрибуты:
+
+- `ssid` (Optional[str]): Название Wi-Fi сети.
+- `bssid` (Optional[str]): MAC-адрес точки доступа.
+- `bitrate` (Optional[int]): Скорость соединения.
+- `strength` (Optional[int]): Уровень сигнала.
+- `frequency` (Optional[float]): Рабочая частота.
+- `mode` (Union[str, int]): Режим работы.
+
+#### bitrate *: int | None*
+
+#### bssid *: str | None*
+
+#### frequency *: float | None*
+
+#### mode *: str | int*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### ssid *: str | None*
+
+#### strength *: int | None*
diff --git a/SRV/docs/_build/markdown/api/models/request.md b/SRV/docs/_build/markdown/api/models/request.md
new file mode 100644
index 0000000..982568e
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/request.md
@@ -0,0 +1,52 @@
+# models.request
+
+### *class* models.request.NoteRequest(, note: str)
+
+Bases: `BaseModel`
+
+Модель запроса на добавление заметки к устройству.
+
+Атрибуты:
+
+- `note` (str): Текст заметки.
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### note *: str*
+
+### *class* models.request.WifiConnectRequest(, ssid: str, password: str)
+
+Bases: `BaseModel`
+
+Модель запроса на подключение к Wi-Fi.
+
+Атрибуты:
+
+- `ssid` (str): Название точки доступа.
+- `password` (str): Пароль доступа.
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### password *: str*
+
+#### ssid *: str*
+
+### *class* models.request.WifiDisconnectRequest(, ssid: str)
+
+Bases: `BaseModel`
+
+Модель запроса на отключение от Wi-Fi.
+
+Атрибуты:
+
+- `ssid` (str): Название точки доступа.
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### ssid *: str*
diff --git a/SRV/docs/_build/markdown/api/models/subnet.md b/SRV/docs/_build/markdown/api/models/subnet.md
new file mode 100644
index 0000000..301146e
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/subnet.md
@@ -0,0 +1,73 @@
+# models.subnet
+
+### *class* models.subnet.Device(, ip: str, mac: str | None = None, hostname: str | None = None, vendor: str | None = None, open_ports: List[str] = [], status: str, last_seen: str | None = None, note: str | None = None)
+
+Bases: `BaseModel`
+
+Модель устройства, обнаруженного в подсети.
+
+Атрибуты:
+
+- `ip` (str): IP-адрес устройства.
+- `mac` (Optional[str]): MAC-адрес устройства.
+- `hostname` (Optional[str]): Имя хоста.
+- `vendor` (Optional[str]): Производитель оборудования.
+- `open_ports` (List[str]): Открытые порты.
+- `status` (str): Состояние устройства (online/offline).
+- `last_seen` (Optional[str]): Время последнего обнаружения.
+- `note` (Optional[str]): Пользовательская заметка.
+
+#### hostname *: str | None*
+
+#### ip *: str*
+
+#### last_seen *: str | None*
+
+#### mac *: str | None*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### note *: str | None*
+
+#### open_ports *: List[str]*
+
+#### status *: str*
+
+#### vendor *: str | None*
+
+### *class* models.subnet.SubnetScanRequest(, subnet: str | None = None)
+
+Bases: `BaseModel`
+
+Параметры запроса на сканирование подсети.
+
+Атрибуты:
+
+- `subnet` (Optional[str]): CIDR подсети (например, `192.168.1.0/24`).
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### subnet *: str | None*
+
+### *class* models.subnet.SubnetScanResult(, devices: List[Device] = [], error: str | None = None)
+
+Bases: `BaseModel`
+
+Результат сканирования подсети.
+
+Атрибуты:
+
+- `devices` (List[Device]): Найденные устройства.
+- `error` (Optional[str]): Сообщение об ошибке сканирования.
+
+#### devices *: List[Device]*
+
+#### error *: str | None*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
diff --git a/SRV/docs/_build/markdown/api/models/ufw.md b/SRV/docs/_build/markdown/api/models/ufw.md
new file mode 100644
index 0000000..d4074ee
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/ufw.md
@@ -0,0 +1,195 @@
+# models.ufw
+
+### *class* models.ufw.Action(\*values)
+
+Bases: `str`, `Enum`
+
+Enum для действий UFW.
+
+#### ALLOW
+
+Разрешить трафик
+
+#### DENY
+
+Запретить трафик без уведомления
+
+#### REJECT
+
+Запретить трафик с уведомлением отправителю
+
+#### ALLOW *= 'allow'*
+
+#### DENY *= 'deny'*
+
+#### REJECT *= 'reject'*
+
+### *class* models.ufw.Direction(\*values)
+
+Bases: `str`, `Enum`
+
+Enum для направления трафика UFW.
+
+#### IN
+
+Входящий трафик
+
+#### OUT
+
+Исходящий трафик
+
+#### IN *= 'in'*
+
+#### OUT *= 'out'*
+
+### *class* models.ufw.Protocol(\*values)
+
+Bases: `str`, `Enum`
+
+Enum для сетевых протоколов UFW.
+
+#### BOTH *= 'both'*
+
+#### TCP *= 'tcp'*
+
+#### UDP *= 'udp'*
+
+#### *property* protocols *: List[str]*
+
+Возвращает список протоколов для использования в команде UFW.
+
+### *class* models.ufw.UFWRule(, action: Action, direction: Direction, from_ip: str, to_ip: str, port: int | str, protocol: Protocol, comment: str | None = None)
+
+Bases: `BaseModel`
+
+Модель правила UFW
+
+#### action *: Action*
+
+#### comment *: str | None*
+
+#### direction *: Direction*
+
+#### from_ip *: str*
+
+#### *classmethod* from_ufw_output(line: str) → UFWRule
+
+Создает экземпляр UFWRule из строки вывода команды ‘ufw status numbered’.
+
+* **Parameters:**
+ **line** – Строка вывода UFW (например, “[1] 80/tcp ALLOW IN 192.168.1.0/24 # Web Server”)
+* **Returns:**
+ Новый экземпляр правила
+* **Return type:**
+ UFWRule
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### port *: int | str*
+
+#### protocol *: Protocol*
+
+#### to_ip *: str*
+
+#### to_ufw_command() → str
+
+Преобразует правило в команду UFW.
+
+#### *classmethod* validate_action(v)
+
+Приводит значение action к нижнему регистру перед валидацией
+
+#### *classmethod* validate_ip(v)
+
+Проверяет корректность IP адреса
+
+#### *classmethod* validate_ip_version_compatibility(v, values)
+
+Проверяет совместимость версий IP адресов
+
+#### *classmethod* validate_port(v)
+
+Проверяет корректность порта или диапазона портов.
+
+* **Parameters:**
+ **v** – Порт (число) или диапазон портов (строка вида “3000:3999”)
+* **Returns:**
+ Валидированное значение порта
+* **Return type:**
+ Union[int, str]
+* **Raises:**
+ **ValueError** – Если порт или диапазон портов некорректен
+
+#### *classmethod* validate_protocol(v)
+
+Приводит значение protocol к нижнему регистру перед валидацией
+
+### *class* models.ufw.UFWRulesResponse(\*, enabled: bool, rules: ~typing.List[~models.ufw.UFWRule] = )
+
+Bases: `BaseModel`
+
+Модель для представления списка правил UFW.
+
+#### enabled
+
+Включен ли UFW
+
+* **Type:**
+ bool
+
+#### rules
+
+Список правил UFW
+
+* **Type:**
+ List[models.ufw.UFWRule]
+
+#### total_rules
+
+Общее количество правил
+
+#### enabled *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### rules *: List[UFWRule]*
+
+#### *property* total_rules *: int*
+
+Возвращает общее количество правил.
+
+### *class* models.ufw.UFWStatus(, enabled: bool, message: str)
+
+Bases: `BaseModel`
+
+Модель для представления статуса UFW.
+
+#### enabled
+
+Включен ли UFW
+
+* **Type:**
+ bool
+
+#### message
+
+Сообщение о статусе
+
+* **Type:**
+ str
+
+#### last_checked
+
+Время последней проверки статуса
+
+#### enabled *: bool*
+
+#### message *: str*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
diff --git a/SRV/docs/_build/markdown/api/models/wifi.md b/SRV/docs/_build/markdown/api/models/wifi.md
new file mode 100644
index 0000000..149fb60
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/models/wifi.md
@@ -0,0 +1,57 @@
+# models.wifi
+
+### *class* models.wifi.WifiAccessPoint(, ssid: str | None = None, bssid: str, channel: int | None = None, signal: int | None = None, encryption: str | None = None, frequency: str | None = None, vendor: str | None = None, hidden: bool = False, note: str | None = None)
+
+Bases: `BaseModel`
+
+Модель Wi-Fi точки доступа.
+
+Атрибуты:
+
+- `ssid` (Optional[str]): Название сети (может быть скрытым).
+- `bssid` (str): Уникальный идентификатор точки доступа.
+- `channel` (Optional[int]): Номер канала.
+- `signal` (Optional[int]): Уровень сигнала в dBm.
+- `encryption` (Optional[str]): Тип шифрования (WPA/WPA2/WPA3/открытая).
+- `frequency` (Optional[str]): Диапазон частот (2.4/5 ГГц).
+- `vendor` (Optional[str]): Производитель оборудования.
+- `hidden` (bool): Флаг скрытого SSID.
+- `note` (Optional[str]): Пользовательская заметка.
+
+#### bssid *: str*
+
+#### channel *: int | None*
+
+#### encryption *: str | None*
+
+#### frequency *: str | None*
+
+#### hidden *: bool*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
+
+#### note *: str | None*
+
+#### signal *: int | None*
+
+#### ssid *: str | None*
+
+#### vendor *: str | None*
+
+### *class* models.wifi.WifiScanResult(, access_points: List[WifiAccessPoint])
+
+Bases: `BaseModel`
+
+Результат сканирования Wi-Fi.
+
+Атрибуты:
+
+- `access_points` (List[WifiAccessPoint]): Найденные точки доступа.
+
+#### access_points *: List[WifiAccessPoint]*
+
+#### model_config *: ClassVar[ConfigDict]* *= {}*
+
+Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
diff --git a/SRV/docs/_build/markdown/api/services.md b/SRV/docs/_build/markdown/api/services.md
new file mode 100644
index 0000000..c3797c0
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services.md
@@ -0,0 +1,12 @@
+# Сервисы
+
+* [ARP Service](services/arp_service.md)
+* [Auth Service](services/auth_service.md)
+* [Connect Service](services/connect_service.md)
+* [Firewalld Service](services/firewalld_service.md)
+* [IP Service](services/ip_service.md)
+* [Network Service](services/network_service.md)
+* [Settings Service](services/settings_service.md)
+* [Subnet Service](services/subnet_service.md)
+* [UFW Service](services/ufw_service.md)
+* [Wifi Service](services/wifi_service.md)
diff --git a/SRV/docs/_build/markdown/api/services/arp_service.md b/SRV/docs/_build/markdown/api/services/arp_service.md
new file mode 100644
index 0000000..ec7b283
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/arp_service.md
@@ -0,0 +1,55 @@
+# ARP Service
+
+Сервис низкоуровневого ARP-сканирования локальной сети.
+
+Назначение:
+
+- Обнаружение активных узлов в пределах сетей хоста путём широковещательных ARP-запросов от имени выбранного интерфейса.
+- Помощь сервисам более высокого уровня (например, IPService) при проверке занятости адресов перед применением конфигураций.
+
+Особенности реализации:
+
+- Определяет подключённые сети интерфейса через netifaces и строит CIDR для сканирования.
+- Формирует ARP-пакеты с помощью scapy (Ether/ARP) и отправляет их с таймаутом.
+- Добавляет локальный адрес интерфейса в результирующий список клиентов.
+
+Требования/ограничения:
+
+- Для отправки ARP-кадров могут потребоваться повышенные привилегии или capability на сырой сокет.
+
+### *class* services.arp_service.NetworkScanner(interface=None, translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Инициализация сетевого сканера.
+
+Предоставляет методы для сканирования сети с использованием ARP-запросов и
+вывода информации о клиентах.
+
+#### print_clients(clients)
+
+Выводит список адресов клиентов в формате таблицы.
+
+* **Parameters:**
+ **clients** (*List* *[**dict* *]*) – Список словарей с ключами “ip” и “mac”,
+ представляющих клиентов.
+
+Выводит IP-адреса и MAC-адреса в отсортированном по IP порядке.
+
+#### scan_network(attempts=3, timeout=2)
+
+Сканирует сеть для обнаружения активных клиентов.
+
+* **Parameters:**
+ * **attempts** (*int* *,* *optional*) – Количество попыток отправки ARP-запросов.
+ По умолчанию 3.
+ * **timeout** (*int* *,* *optional*) – Таймаут ожидания ответа в секундах.
+ По умолчанию 2.
+* **Returns:**
+ Список словарей с ключами “ip” и “mac”, представляющих
+ : обнаруженных клиентов, включая локальные IP-адреса.
+* **Return type:**
+ List[dict]
+
+#### NOTE
+Учитывает локальные IP-адреса интерфейса и удаляет дубликаты.
diff --git a/SRV/docs/_build/markdown/api/services/auth_service.md b/SRV/docs/_build/markdown/api/services/auth_service.md
new file mode 100644
index 0000000..f9623fd
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/auth_service.md
@@ -0,0 +1,35 @@
+# Auth Service
+
+### *class* services.auth_service.AuthService(translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Сервис для регистрации, авторизации и управления сессиями
+
+#### create_access_token(data: dict) → str
+
+Создаёт токен
+
+#### get_current_user(token: str = Depends(OAuth2PasswordBearer)) → UserResponse
+
+Получает текущего пользователя по токену
+
+#### get_user_by_session(client_id: str) → dict
+
+Получает пользователя и токен по client_id
+
+#### login(user: UserLogin, client_id: str) → dict
+
+Авторизует пользователя, создаёт сессию и возвращает токен
+
+#### logout(client_id: str)
+
+Выполняет выход из системы, удаляя последнюю сессию для client_id
+
+#### register(user: UserCreate) → UserResponse
+
+Регистрирует нового пользователя
+
+#### verify_token(token: str) → dict | None
+
+Проверяет токен
diff --git a/SRV/docs/_build/markdown/api/services/connect_service.md b/SRV/docs/_build/markdown/api/services/connect_service.md
new file mode 100644
index 0000000..37b977b
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/connect_service.md
@@ -0,0 +1,71 @@
+# Connect Service
+
+Сервис управления подключениями Wi-Fi через nmcli.
+
+Назначение:
+
+- Предоставляет операции подключения/отключения к точкам доступа и проверки текущего статуса, ориентируясь на системный NetworkManager.
+
+Поведение:
+
+- Автоматически определяет активный Wi-Fi-интерфейс.
+- Валидирует наличие SSID в листинге перед подключением и интерпретирует частые ошибки nmcli (неверный пароль, отсутствующая сеть и т.п.).
+
+Ограничения:
+
+- Требуются права для управления сетевыми соединениями в системе.
+- В окружениях без NetworkManager команды nmcli будут недоступны.
+
+### *class* services.connect_service.WifiConnectionService(translations: Translations | None = None)
+
+Bases: `object`
+
+Сервис для управления подключением к Wi-Fi сетям с использованием nmcli.
+
+Предоставляет методы для подключения, отключения и проверки статуса Wi-Fi соединений.
+
+#### connect(ssid: str, password: str) → Tuple[bool, str | None]
+
+Подключается к Wi-Fi сети по SSID и паролю.
+
+* **Parameters:**
+ * **ssid** (*str*) – Идентификатор сети Wi-Fi.
+ * **password** (*str*) – Пароль для подключения.
+* **Returns:**
+ Кортеж (успех, сообщение об ошибке).
+ : - Успех: (True, None).
+ - Ошибка: (False, сообщение об ошибке).
+* **Return type:**
+ Tuple[bool, Optional[str]]
+* **Raises:**
+ * **subprocess.CalledProcessError** – Если команда nmcli завершилась с ошибкой.
+ * **Exception** – Если произошла неизвестная ошибка при выполнении.
+
+#### disconnect(ssid: str) → Tuple[bool, str | None]
+
+Отключается от Wi-Fi сети по указанному SSID.
+
+* **Parameters:**
+ **ssid** (*str*) – Идентификатор сети Wi-Fi для отключения.
+* **Returns:**
+ Кортеж (успех, сообщение об ошибке).
+ : - Успех: (True, None или сообщение, если подключение не найдено).
+ - Ошибка: (False, сообщение об ошибке).
+* **Return type:**
+ Tuple[bool, Optional[str]]
+* **Raises:**
+ * **subprocess.CalledProcessError** – Если команда nmcli завершилась с ошибкой.
+ * **Exception** – Если произошла неизвестная ошибка при выполнении.
+
+#### get_status() → Tuple[bool, str | None]
+
+Проверяет статус текущего подключения к Wi-Fi.
+
+* **Returns:**
+ Кортеж (успех, SSID или сообщение об ошибке).
+ : - Успех: (True, SSID текущей сети).
+ - Ошибка или отсутствие подключения: (False, сообщение об ошибке).
+* **Return type:**
+ Tuple[bool, Optional[str]]
+* **Raises:**
+ **Exception** – Если произошла ошибка при выполнении команды nmcli.
diff --git a/SRV/docs/_build/markdown/api/services/firewalld_service.md b/SRV/docs/_build/markdown/api/services/firewalld_service.md
new file mode 100644
index 0000000..cbe8002
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/firewalld_service.md
@@ -0,0 +1,72 @@
+# Firewalld Service
+
+Сервис для управления firewalld через D-Bus.
+
+Назначение:
+
+- Обёртка над интерфейсами org.fedoraproject.FirewallD1 для выполнения типовых операций по зонам: просмотр/изменение дефолтной зоны, добавление и удаление сервисов, портов, источников, перезагрузка конфигурации.
+
+Примечания:
+
+- Используется pydbus.SystemBus для доступа к системной шине.
+- Методы генерируют дружелюбные исключения при ошибках, содержащие контекст операции.
+
+### *class* services.firewalld_service.FirewalldService
+
+Bases: `object`
+
+#### add_port(zone: str, port: int, protocol: str, timeout: int | None = 0) → None
+
+Add a port to a zone.
+
+#### add_service(zone: str, service: str, timeout: int | None = 0) → None
+
+Add a service to a zone.
+
+#### add_source(zone: str, source: str) → None
+
+Add a source to a zone.
+
+#### get_default_zone() → str
+
+Get the default zone.
+
+#### get_ports(zone: str) → List[str]
+
+Get list of ports in a zone.
+
+#### get_services(zone: str) → List[str]
+
+Get list of services in a zone.
+
+#### get_sources(zone: str) → List[str]
+
+Get list of sources in a zone.
+
+#### get_zone_settings(zone: str) → Dict
+
+Get all settings for a zone.
+
+#### get_zones() → List[str]
+
+Get list of all zones.
+
+#### reload() → None
+
+Reload firewall rules.
+
+#### remove_port(zone: str, port: int, protocol: str) → None
+
+Remove a port from a zone.
+
+#### remove_service(zone: str, service: str) → None
+
+Remove a service from a zone.
+
+#### remove_source(zone: str, source: str) → None
+
+Remove a source from a zone.
+
+#### set_default_zone(zone: str) → None
+
+Set the default zone.
diff --git a/SRV/docs/_build/markdown/api/services/ip_service.md b/SRV/docs/_build/markdown/api/services/ip_service.md
new file mode 100644
index 0000000..8c88361
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/ip_service.md
@@ -0,0 +1,61 @@
+# IP Service
+
+Сервис проверки и применения IP-настроек через NetworkManager (D-Bus).
+
+Назначение:
+
+- Перед применением конфигурации проверяет занятость IP адреса, сканируя сегмент сети средствами ARP (через NetworkScanner).
+- Применяет изменения профиля подключения через sdbus_async.networkmanager и выполняет переподключение интерфейса.
+
+Требования/ограничения:
+
+- Доступ к системной шине D-Bus и права на изменения сетевых профилей.
+- Структура входных данных должна соответствовать схеме ipv4_updates для корректного обновления вложенных полей у профиля.
+
+### *class* services.ip_service.IPService
+
+Bases: `object`
+
+Сервис проверки доступности IP-адреса и применения настроек сети.
+
+Предоставляет асинхронные методы для проверки занятости IP-адреса с использованием
+ARP-сканирования и применения настроек через NetworkManager.
+
+#### *async* apply_ip_config(args: Dict[str, Any], translations: Translations = Depends(translations_dependency)) → None
+
+Применяет проверенные настройки IP к указанному соединению.
+
+Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
+отключает и переподключает устройство к сети.
+
+* **Parameters:**
+ * **args** (*Dict* *[**str* *,* *Any* *]*) – Словарь с данными, включая:
+ - “connection_id” (str): Идентификатор соединения.
+ - “ipv4_updates” (Dict): Обновления настроек IPv4.
+ * **translations** (*Translations*) – Объект переводов.
+* **Raises:**
+ * **ValueError** – Если соединение с указанным ID не найдено.
+ * **RuntimeError** – Если устройство для интерфейса не найдено.
+
+#### NOTE
+Требуются права для изменения настроек через NetworkManager.
+
+#### *async* check_ip_config(args: Dict[str, Any], translations: Translations = Depends(translations_dependency)) → Dict[str, Any]
+
+Проверяет доступность IP-адреса перед его применением.
+
+Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
+Перезапускает шину D-Bus перед выполнением.
+
+* **Parameters:**
+ * **args** (*Dict* *[**str* *,* *Any* *]*) – Словарь с данными, включая “ipv4_updates”
+ с полем “ipv4.address-data” для нового IP-адреса.
+ * **translations** (*Translations*) – Объект переводов.
+* **Returns:**
+ Словарь с ключами:
+ : - ”available” (bool): Доступен ли IP-адрес.
+ - ”message” (str): Сообщение о результате проверки.
+* **Return type:**
+ Dict[str, Any]
+* **Raises:**
+ **Exception** – Если произошла ошибка при сканировании ARP.
diff --git a/SRV/docs/_build/markdown/api/services/network_service.md b/SRV/docs/_build/markdown/api/services/network_service.md
new file mode 100644
index 0000000..268651b
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/network_service.md
@@ -0,0 +1,195 @@
+# Network Service
+
+Сервис чтения сетевого состояния через NetworkManager (D-Bus).
+
+Назначение:
+
+- Предоставляет асинхронные методы для сборки унифицированных моделей сетевых интерфейсов (Ethernet/Wi-Fi), их IP-конфигураций, а также получения имени хоста. Используется API-слоем для визуализации и управления.
+
+Особенности:
+
+- Фильтрация loopback и явно неактивных интерфейсов.
+- Адаптация данных D-Bus в pydantic-модели (NetworkInterface, WiFiDevice, NetworkInterfacesContainer).
+
+### *class* services.network_service.NetworkService(translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Сервис для взаимодействия с NetworkManager через D-Bus.
+
+Предоставляет асинхронные методы для получения информации о сетевых интерфейсах,
+их конфигурациях IP, Wi-Fi и состояниях.
+
+#### *async* get_activated_interfaces(initial_positions: Dict[str, Tuple[int, int]] = None) → List[NetworkInterface]
+
+Получает список активных сетевых интерфейсов.
+
+* **Parameters:**
+ **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+* **Returns:**
+ Список активных интерфейсов (state == “100”).
+* **Return type:**
+ List[NetworkInterface]
+
+#### *async* get_activated_interfaces_container(initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) → NetworkInterfacesContainer
+
+Получает объект, содержащий список активных интерфейсов и координаты контейнера.
+
+* **Parameters:**
+ * **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+ * **container_x** (*int* *,* *optional*) – Координата X контейнера. По умолчанию 0.
+ * **container_y** (*int* *,* *optional*) – Координата Y контейнера. По умолчанию 0.
+* **Returns:**
+ Объект с активными интерфейсами и координатами.
+* **Return type:**
+ NetworkInterfacesContainer
+
+#### *async* get_connection_name(device: sdbus_async.networkmanager.NetworkDeviceGeneric) → str
+
+Извлекает имя соединения (GENERAL.CONNECTION) для устройства.
+
+* **Parameters:**
+ **device** (*NetworkDeviceGeneric*) – Объект устройства NetworkManager.
+* **Returns:**
+ Имя соединения или “unknown” в случае ошибки.
+* **Return type:**
+ str
+* **Raises:**
+ **Exception** – Если произошла ошибка при получении настроек соединения.
+
+#### *async* get_deactivated_interfaces(initial_positions: Dict[str, Tuple[int, int]] = None) → List[NetworkInterface]
+
+Получает список неактивных сетевых интерфейсов.
+
+* **Parameters:**
+ **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+* **Returns:**
+ Список интерфейсов с state != “100”.
+* **Return type:**
+ List[NetworkInterface]
+
+#### *async* get_deactivated_interfaces_container(initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) → NetworkInterfacesContainer
+
+Получает объект, содержащий список неактивных интерфейсов и координаты контейнера.
+
+* **Parameters:**
+ * **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+ * **container_x** (*int* *,* *optional*) – Координата X контейнера. По умолчанию 0.
+ * **container_y** (*int* *,* *optional*) – Координата Y контейнера. По умолчанию 0.
+* **Returns:**
+ Объект с неактивными интерфейсами и координатами.
+* **Return type:**
+ NetworkInterfacesContainer
+
+#### *async* get_hostname() → Dict[str, str]
+
+Возвращает имя хоста из команды hostname в формате JSON.
+
+* **Returns:**
+ Словарь с ключом “hostname” и значением имени хоста.
+* **Return type:**
+ Dict[str, str]
+* **Raises:**
+ **RuntimeError** – Если произошла ошибка при выполнении команды hostname
+ или обработке вывода.
+
+#### *async* get_interfaces_by_state(state: str, initial_positions: Dict[str, Tuple[int, int]] = None) → List[NetworkInterface]
+
+Получает список интерфейсов по указанному состоянию.
+
+* **Parameters:**
+ * **state** (*str*) – Состояние интерфейса (например, “100” для активных).
+ * **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+* **Returns:**
+ Список интерфейсов с указанным состоянием.
+* **Return type:**
+ List[NetworkInterface]
+
+#### *async* get_interfaces_container(state: str | None = None, initial_positions: Dict[str, Tuple[int, int]] = None, container_x: int = 0, container_y: int = 0) → NetworkInterfacesContainer
+
+Получает объект, содержащий список интерфейсов и координаты контейнера.
+
+* **Parameters:**
+ * **state** (*Optional* *[**str* *]* *,* *optional*) – Состояние интерфейсов (например, “100”).
+ По умолчанию None (все интерфейсы).
+ * **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None.
+ * **container_x** (*int* *,* *optional*) – Координата X контейнера. По умолчанию 0.
+ * **container_y** (*int* *,* *optional*) – Координата Y контейнера. По умолчанию 0.
+* **Returns:**
+ Объект с интерфейсами и координатами контейнера.
+* **Return type:**
+ NetworkInterfacesContainer
+
+#### *async* get_ip_config(device: sdbus_async.networkmanager.NetworkDeviceGeneric, ip_version: str) → List[IndividualIPConfig]
+
+Получает конфигурацию IP для устройства.
+
+* **Parameters:**
+ * **device** (*NetworkDeviceGeneric*) – Объект устройства NetworkManager.
+ * **ip_version** (*str*) – Версия IP (“4” или “6”).
+* **Returns:**
+ Список конфигураций IP, включая адреса и маршруты.
+* **Return type:**
+ List[IndividualIPConfig]
+* **Raises:**
+ **Exception** – Если произошла ошибка при получении данных IP.
+
+#### *async* get_ip_method(device: sdbus_async.networkmanager.NetworkDeviceGeneric, ip_type: str) → str
+
+Получает метод IP (например, “auto”, “manual”) для указанного устройства.
+
+* **Parameters:**
+ * **device** (*NetworkDeviceGeneric*) – Объект устройства NetworkManager.
+ * **ip_type** (*str*) – Тип IP (например, “ipv4” или “ipv6”).
+* **Returns:**
+ Метод IP или “unknown” в случае ошибки.
+* **Return type:**
+ str
+* **Raises:**
+ **Exception** – Если произошла ошибка при получении настроек соединения.
+
+#### *async* get_network_interfaces(initial_positions: Dict[str, Tuple[int, int]] = None) → List[NetworkInterface | WiFiDevice]
+
+Получает список сетевых интерфейсов.
+
+* **Parameters:**
+ **initial_positions** (*Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *,* *optional*) – Словарь начальных
+ координат (x, y) для интерфейсов. По умолчанию None (используются (100, 100)).
+* **Returns:**
+ Список объектов интерфейсов,
+ : включая Wi-Fi устройства.
+* **Return type:**
+ List[Union[NetworkInterface, WiFiDevice]]
+
+Исключает интерфейсы типа LOOPBACK и неактивные состояния (state == “10”).
+
+#### *async* get_network_interfaces_container(initial_positions: Dict[str, Tuple[int, int]] | None = None) → NetworkInterfacesContainer
+
+Получает контейнер с сетевыми интерфейсами и координатами контейнера.
+
+* **Parameters:**
+ **initial_positions** (*Optional* *[**Dict* *[**str* *,* *Tuple* *[**int* *,* *int* *]* *]* *]* *,* *optional*) – Словарь начальных координат (x, y) для интерфейсов. По умолчанию None.
+* **Returns:**
+ Объект с интерфейсами и координатами (0, 0).
+* **Return type:**
+ NetworkInterfacesContainer
+
+#### *async* get_wifi_info(wifi_device: sdbus_async.networkmanager.NetworkDeviceWireless) → Dict[str, str | int | None]
+
+Получает информацию о Wi-Fi устройстве.
+
+* **Parameters:**
+ **wifi_device** (*NetworkDeviceWireless*) – Объект Wi-Fi устройства NetworkManager.
+* **Returns:**
+ Словарь с данными Wi-Fi (SSID, BSSID,
+ : bitrate, strength, frequency, mode), где значения могут быть None.
+* **Return type:**
+ Dict[str, Optional[Union[str, int]]]
+* **Raises:**
+ **Exception** – Если произошла ошибка при получении данных точки доступа.
diff --git a/SRV/docs/_build/markdown/api/services/settings_service.md b/SRV/docs/_build/markdown/api/services/settings_service.md
new file mode 100644
index 0000000..bc73b07
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/settings_service.md
@@ -0,0 +1,43 @@
+# Settings Service
+
+### *class* services.settings_service.SettingsService(translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Сервис для управления настройками и очистки данных сети.
+
+#### clear_all_data() → bool
+
+Очищает данные узлов всех подсетей и точек доступа Wi-Fi.
+
+* **Returns:**
+ True, если очистка успешна.
+* **Return type:**
+ bool
+* **Raises:**
+ **Exception** – Если произошла ошибка при записи в один из файлов.
+
+#### clear_subnet(subnet: str | None = None) → bool
+
+Очищает данные узлов подсети в указанном файле или во всех файлах.
+
+* **Parameters:**
+ **subnet** (*Optional* *[**str* *]*) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+ Если None, очищает все подсети.
+* **Returns:**
+ True, если очистка успешна.
+* **Return type:**
+ bool
+* **Raises:**
+ **Exception** – Если произошла ошибка при записи в файл.
+
+#### clear_wifi() → bool
+
+Очищает данные точек доступа Wi-Fi в файле wifi.json.
+
+* **Returns:**
+ True, если очистка успешна.
+* **Return type:**
+ bool
+* **Raises:**
+ **Exception** – Если произошла ошибка при записи в файл.
diff --git a/SRV/docs/_build/markdown/api/services/subnet_service.md b/SRV/docs/_build/markdown/api/services/subnet_service.md
new file mode 100644
index 0000000..c350c71
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/subnet_service.md
@@ -0,0 +1,272 @@
+# Subnet Service
+
+### *class* services.subnet_service.DeviceInfoCollector(translations: Translations)
+
+Bases: `object`
+
+Класс для сбора информации об устройствах, включая вендоров и хостнеймы.
+
+Источники данных:
+: - MacLookup: сопоставление префикса MAC (OUI) с названием производителя.
+ - DNS: gethostbyaddr с малым таймаутом для обратного разрешения имени.
+ - NetBIOS (nbtscan): опрос сегмента сети для извлечения NetBIOS-имён.
+
+Политика кэширования:
+: - Результаты get_vendor кэшируются с помощью functools.lru_cache.
+ - Результаты nbtscan аккумулируются в словарях nbtscan_cache/nbtscan_results
+ на время жизни экземпляра.
+
+#### get_hostname(ip: str) → str | None
+
+Получает хостнейм для указанного IP-адреса.
+
+Пробует сначала DNS, затем NetBIOS.
+
+* **Parameters:**
+ **ip** (*str*) – IP-адрес устройства.
+* **Returns:**
+ Хостнейм или None, если не удалось определить.
+* **Return type:**
+ Optional[str]
+
+#### get_vendor(mac: str) → str
+
+Получает вендора устройства по MAC-адресу.
+
+Использует кэширование для повышения производительности.
+
+* **Parameters:**
+ **mac** (*str*) – MAC-адрес устройства.
+* **Returns:**
+ Название вендора или “Неизвестный” в случае ошибки.
+* **Return type:**
+ str
+
+#### scan_nbtscan(subnet: str) → None
+
+Сканирует подсеть с помощью nbtscan для получения хостнеймов.
+
+Сохраняет результаты в кэш и логирует процесс.
+
+* **Parameters:**
+ **subnet** (*str*) – Подсеть в формате CIDR.
+
+### *class* services.subnet_service.DeviceStorage(base_path: str = 'storage/subnets', translations: Translations = None)
+
+Bases: `object`
+
+Класс для хранения и управления данными об устройствах.
+
+Формат файла подсети:
+
+```default
+{
+ "devices": [Device, ...]
+}
+```
+
+Политика очистки:
+
+- Метод \_clean_old_devices удаляет оффлайн-устройства, не наблюдавшиеся более 7 дней,
+ чтобы поддерживать актуальность списка.
+
+#### get_subnet_file(subnet: str) → Path
+
+Возвращает путь к файлу для конкретной подсети.
+
+Заменяет символы ‘/’ на ‘_’ для корректного имени файла.
+
+* **Parameters:**
+ **subnet** (*str*) – Подсеть в формате CIDR.
+* **Returns:**
+ Путь к файлу.
+* **Return type:**
+ Path
+
+#### load_devices(subnet: str) → Dict[str, Device]
+
+Загружает устройства для указанной подсети.
+
+Читает данные из JSON-файла и фильтрует устаревшие устройства.
+
+* **Parameters:**
+ **subnet** (*str*) – Подсеть в формате CIDR.
+* **Returns:**
+ Словарь устройств с MAC как ключом.
+* **Return type:**
+ Dict[str, Device]
+
+#### save_devices(subnet: str, devices: List[Device]) → None
+
+Сохраняет устройства для указанной подсети.
+
+Записывает данные в JSON-файл.
+
+* **Parameters:**
+ * **subnet** (*str*) – Подсеть в формате CIDR.
+ * **devices** (*List* *[**Device* *]*) – Список устройств для сохранения.
+* **Raises:**
+ **Exception** – Если произошла ошибка при сохранении.
+
+### *class* services.subnet_service.NetworkScanner(interface: str, translations: Translations)
+
+Bases: `object`
+
+Класс для сканирования подсети с использованием ARP-запросов.
+
+Назначение:
+: Обнаруживает активные устройства в локальной сети, отправляя широковещательные
+ ARP-запросы по указанной подсети и собирая ответы (IP, MAC).
+
+Детали реализации:
+: - Использует scapy (Ether + ARP) для генерации пакетов и srp для
+ отправки/получения на канальном уровне от имени заданного интерфейса.
+ - Таймаут ожидания ответа короткий (2 секунды), ориентирован на интерактивные
+ сценарии UI.
+
+Ограничения и замечания:
+: - Требуются права для отправки низкоуровневых пакетов (capabilities/root).
+ - В сетях с фильтрацией широковещания количество обнаруженных устройств
+ может быть ограничено.
+
+#### scan(subnet: str) → List[Tuple[str, str]]
+
+Сканирует подсеть с использованием ARP-запросов.
+
+Отправляет ARP-пакеты для обнаружения активных устройств и возвращает
+список кортежей (IP, MAC).
+
+* **Parameters:**
+ **subnet** (*str*) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+* **Returns:**
+ Список кортежей (IP-адрес, MAC-адрес) обнаруженных устройств.
+* **Return type:**
+ List[Tuple[str, str]]
+* **Raises:**
+ **Exception** – Если произошла ошибка сканирования.
+
+### *class* services.subnet_service.PortScanner(interface: str, translations: Translations)
+
+Bases: `object`
+
+Класс для сканирования открытых портов на устройствах.
+
+Назначение:
+: Плейсхолдер для будущего функционала активного сканирования TCP/UDP-портов
+ у обнаруженных узлов. В текущей версии возвращает пустой список.
+
+План развития:
+: - Добавить неблокирующее сканирование наиболее распространённых портов
+ (22, 80, 443 и т.д.) с ограничением по времени.
+ - Настраиваемые профили глубины и скорости сканирования.
+
+#### scan(ip: str) → List[str]
+
+Сканирует открытые порты на указанном IP-адресе.
+
+Пока возвращает пустой список, так как функциональность не реализована.
+
+* **Parameters:**
+ **ip** (*str*) – IP-адрес устройства для сканирования.
+* **Returns:**
+ Список открытых портов (пока пустой).
+* **Return type:**
+ List[str]
+
+### *class* services.subnet_service.SubnetService(translations: Translations)
+
+Bases: `object`
+
+Сервис для сканирования подсетей и управления устройствами.
+
+Роль сервиса:
+: Объединяет сетевое сканирование (ARP), сбор дополнительной информации
+ (вендор, хостнейм), слияние с кэшем предыдущих результатов и персистентное
+ хранение данных по подсетям. Предоставляет вспомогательные операции для
+ локального узла и работы с заметками устройств.
+
+Поток выполнения скана (scan_subnet):
+: 1. Определение подсети (вручную или авто по интерфейсу).
+ 2. Загрузка ранее сохранённых устройств подсети.
+ 3. Добавление локального устройства (если принадлежит подсети).
+ 4. ARP-сканирование и параллельная обработка узлов (обогащение данных).
+ 5. Маркировка оффлайн-узлов из кэша, не встреченных в текущем скане.
+ 6. Сортировка по IP и сохранение объединённого списка в JSON.
+
+#### add_note(mac: str, note: str, translations: Translations) → Tuple[bool, Tuple[str, Dict] | None]
+
+Добавляет заметку для устройства по MAC-адресу.
+
+Привязывает заметку к устройству, если оно существует.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **note** (*str*) – Текст заметки.
+ * **translations** (*Translations*) – Объект переводов для текущего языка.
+* **Returns:**
+ Кортеж (успех, результат),
+ : где результат содержит код ошибки/успеха и данные.
+* **Return type:**
+ Tuple[bool, Optional[Tuple[str, Dict]]]
+
+#### delete_note(mac: str, translations: Translations) → Tuple[bool, Tuple[str, Dict] | None]
+
+Удаляет заметку для устройства по MAC-адресу.
+
+Удаляет привязанную заметку, если она существует.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **translations** (*Translations*) – Объект переводов для текущего языка.
+* **Returns:**
+ Кортеж (успех, результат),
+ : где результат содержит код ошибки/успеха и данные.
+* **Return type:**
+ Tuple[bool, Optional[Tuple[str, Dict]]]
+
+#### scan_subnet(subnet: str | None = None) → SubnetScanResult
+
+Сканирует указанную подсеть и возвращает результаты.
+
+Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные.
+
+* **Parameters:**
+ **subnet** (*Optional* *[**str* *]*) – Подсеть в формате CIDR (например, “10.1.1.0/24”).
+ Если None или пустая строка, определяется автоматически.
+* **Returns:**
+ Объект с результатами сканирования, содержащий список
+ : устройств или сообщение об ошибке.
+* **Return type:**
+ SubnetScanResult
+* **Raises:**
+ **Exception** – Если произошла ошибка сканирования.
+
+#### update_note(mac: str, note: str, translations: Translations) → Tuple[bool, Tuple[str, Dict] | None]
+
+Обновляет заметку для устройства по MAC-адресу.
+
+Изменяет существующую заметку, если она есть.
+
+* **Parameters:**
+ * **mac** (*str*) – MAC-адрес устройства.
+ * **note** (*str*) – Новый текст заметки.
+ * **translations** (*Translations*) – Объект переводов для текущего языка.
+* **Returns:**
+ Кортеж (успех, результат),
+ : где результат содержит код ошибки/успеха и данные.
+* **Return type:**
+ Tuple[bool, Optional[Tuple[str, Dict]]]
+
+### services.subnet_service.get_subnet_service(translations: Translations = Depends(translations_dependency))
+
+Фабричная функция для создания экземпляра SubnetService.
+
+Использует зависимости FastAPI для получения объекта переводов и возвращает
+экземпляр SubnetService с текущей локализацией.
+
+* **Parameters:**
+ **translations** (*Translations*) – Объект переводов для текущего языка.
+* **Returns:**
+ Экземпляр сервиса для работы с подсетями.
+* **Return type:**
+ SubnetService
diff --git a/SRV/docs/_build/markdown/api/services/ufw_service.md b/SRV/docs/_build/markdown/api/services/ufw_service.md
new file mode 100644
index 0000000..379e445
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/ufw_service.md
@@ -0,0 +1,108 @@
+# UFW Service
+
+### *class* services.ufw_service.UFWService
+
+Bases: `object`
+
+Сервис взаимодействия с UFW (Uncomplicated Firewall).
+
+Назначение:
+: - Чтение статуса и списка правил брандмауэра.
+ - Добавление, удаление и массовая обработка правил с поддержкой диапазонов портов,
+ комментариев и выбором протоколов.
+
+Детали реализации:
+: - Команды выполняются через sudo ufw …; вывод парсится в модели pydantic.
+ - Для операций удаления соблюдается порядок с конца, чтобы не нарушать нумерацию.
+
+#### add_rules(rules: List[UFWRule]) → dict
+
+Добавляет правила UFW.
+
+* **Parameters:**
+ **rules** – Список правил для добавления
+* **Returns:**
+ Результат добавления правил
+* **Return type:**
+ dict
+
+#### add_rules_base(rules: List[UFWRule], direction: str = '') → bool
+
+Базовый метод для добавления правил UFW.
+
+* **Parameters:**
+ * **rules** – Список правил для добавления
+ * **direction** – Направление трафика (“in” или “out”)
+* **Returns:**
+ True если все правила были успешно добавлены, False в противном случае
+* **Return type:**
+ bool
+
+#### add_ufw_in_rules(rules: List[UFWRule]) → bool
+
+Добавляет входящие (in) правила UFW.
+
+* **Parameters:**
+ **rules** (*List* *[**UFWRule* *]*) – Список правил для добавления
+* **Returns:**
+ True если все правила были успешно добавлены, False в противном случае
+* **Return type:**
+ bool
+
+#### add_ufw_out_rules(rules: List[UFWRule]) → bool
+
+Добавляет исходящие (out) правила UFW.
+
+* **Parameters:**
+ **rules** (*List* *[**UFWRule* *]*) – Список правил для добавления
+* **Returns:**
+ True если все правила были успешно добавлены, False в противном случае
+* **Return type:**
+ bool
+
+#### delete_rules(rule_numbers: List[int]) → dict
+
+Удаляет правила UFW по их номерам.
+
+* **Parameters:**
+ **rule_numbers** (*List* *[**int* *]*) – Список номеров правил для удаления
+* **Returns:**
+ Результат операции удаления
+* **Return type:**
+ dict
+
+#### disable_ufw() → bool
+
+Выключает UFW.
+
+* **Returns:**
+ True если успешно выключен, False в противном случае
+* **Return type:**
+ bool
+
+#### enable_ufw() → bool
+
+Включает UFW.
+
+* **Returns:**
+ True если успешно включен, False в противном случае
+* **Return type:**
+ bool
+
+#### get_ufw_rules() → UFWRulesResponse
+
+Получает список правил UFW.
+
+* **Returns:**
+ Объект, содержащий статус UFW и список правил
+* **Return type:**
+ UFWRulesResponse
+
+#### get_ufw_status() → UFWStatus
+
+Проверяет статус UFW.
+
+* **Returns:**
+ Объект с информацией о статусе UFW
+* **Return type:**
+ UFWStatus
diff --git a/SRV/docs/_build/markdown/api/services/wifi_service.md b/SRV/docs/_build/markdown/api/services/wifi_service.md
new file mode 100644
index 0000000..0390805
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/services/wifi_service.md
@@ -0,0 +1,64 @@
+# Wifi Service
+
+### *class* services.wifi_service.WifiService(translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Сервис для сканирования текущих Wi-Fi точек доступа и заметок.
+
+Источники данных:
+: - pywifi для базового сканирования и получения списка AP.
+ - iw dev scan для уточнения частоты, сигнала и безопасности.
+ - MacLookup для определения вендора по BSSID.
+
+Политики:
+: - Выбор лучшего AP по SSID по уровню сигнала.
+ - Кэширование определения вендора.
+ - Сохранение SSID даже без заметок для будущих операций.
+
+#### add_note(ssid: str, note: str) → Tuple[bool, str | None]
+
+Добавляет заметку к сети Wi-Fi по SSID.
+
+#### delete_note(ssid: str) → Tuple[bool, str | None]
+
+Удаляет заметку сети Wi-Fi по SSID.
+
+#### get_vendor(mac: str) → str
+
+Определяет вендора по MAC-адресу с использованием кэширования.
+
+#### scan_wifi(timeout: int = 30) → WifiScanResult
+
+Сканирует текущие Wi-Fi точки доступа.
+
+#### update_note(ssid: str, note: str) → Tuple[bool, str | None]
+
+Обновляет заметку сети Wi-Fi по SSID.
+
+### *class* services.wifi_service.WifiStorage(storage_path: str = 'storage/wifi/wifi.json', translations: Translations = Depends(translations_dependency))
+
+Bases: `object`
+
+Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.
+
+Формат:
+
+```default
+{
+ "networks": [{"ssid": str, "note": Optional[str]}]
+}
+```
+
+Поведение:
+
+- Создаёт директорию хранения при инициализации, ведёт логирование путей.
+- Валидирует структуру при чтении и безопасно записывает JSON с Unicode и отступами.
+
+#### load_notes() → Dict[str, str | None]
+
+Загружает заметки по SSID из файла хранения.
+
+#### save_notes(notes: Dict[str, str | None]) → None
+
+Сохраняет заметки по SSID в файл.
diff --git a/SRV/docs/_build/markdown/api/storage.md b/SRV/docs/_build/markdown/api/storage.md
new file mode 100644
index 0000000..f0850e3
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/storage.md
@@ -0,0 +1,4 @@
+# Хранилища
+
+* [storage.json_storage](storage/json_storage.md)
+* [storage.session_storage](storage/session_storage.md)
diff --git a/SRV/docs/_build/markdown/api/storage/json_storage.md b/SRV/docs/_build/markdown/api/storage/json_storage.md
new file mode 100644
index 0000000..ebdf1c9
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/storage/json_storage.md
@@ -0,0 +1,73 @@
+# storage.json_storage
+
+### *class* storage.json_storage.JsonStorage(file_path: str | None = None)
+
+Bases: `object`
+
+Хранилище пользователей в JSON-файле.
+
+Назначение:
+
+- Обеспечивает простое файловое хранилище учетных записей без полноценной СУБД.
+- Подходит для встраиваемых окружений, где критична минимальная зависимость от внешних сервисов.
+
+Формат данных:
+
+```default
+{
+ "users": [
+ {"id": int, "username": str, "password_hash": str, "is_admin": bool},
+ ...
+ ],
+ "next_id": int
+}
+```
+
+Безопасность:
+
+- Пароли никогда не сохраняются в открытом виде: используется passlib.context.CryptContext со схемой bcrypt.
+- Файл создаётся с правами доступа `0o600` (чтение/запись только владельцу).
+
+Ограничения:
+
+- Нет блокировок для конкурентного доступа — в многопроцессной среде требуется внешняя синхронизация.
+- Значение пути по умолчанию `/etc/gonets/users.json` предполагает наличие прав на запись и предварительную настройку окружения.
+
+#### create_user(user: UserCreate) → UserResponse
+
+Создаёт нового пользователя с хешированием пароля.
+
+* **Parameters:**
+ **user** (*UserCreate*) – Объект с данными нового пользователя (username, password, is_admin).
+* **Returns:**
+ Объект с данными созданного пользователя (id, username, is_admin).
+* **Return type:**
+ UserResponse
+* **Raises:**
+ * **ValueError** – Если пользователь с таким именем уже существует.
+ * **Exception** – Если произошла ошибка при записи данных.
+
+#### get_user_by_username(username: str) → dict | None
+
+Получает данные пользователя по имени.
+
+* **Parameters:**
+ **username** (*str*) – Имя пользователя для поиска.
+* **Returns:**
+ Словарь с данными пользователя или None, если пользователь не найден.
+* **Return type:**
+ Optional[dict]
+* **Raises:**
+ **Exception** – Если произошла ошибка при чтении файла.
+
+#### verify_password(plain_password: str, hashed_password: str) → bool
+
+Проверяет соответствие введённого пароля хэшированному.
+
+* **Parameters:**
+ * **plain_password** (*str*) – Введённый пользователем пароль.
+ * **hashed_password** (*str*) – Хэшированный пароль из хранилища.
+* **Returns:**
+ True, если пароль верен, False в противном случае.
+* **Return type:**
+ bool
diff --git a/SRV/docs/_build/markdown/api/storage/session_storage.md b/SRV/docs/_build/markdown/api/storage/session_storage.md
new file mode 100644
index 0000000..87c8050
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/storage/session_storage.md
@@ -0,0 +1,98 @@
+# storage.session_storage
+
+### *class* storage.session_storage.SessionStorage(file_path: str | None = None)
+
+Bases: `object`
+
+Хранилище сессий пользователей в JSON-файле.
+
+Назначение:
+
+- Лёгкое файловое хранилище сессионных токенов, сопоставленных с `client_id` (например, IP клиента).
+- Предназначено для сред без БД и persistent-кэшей; обеспечивает простые операции и автоочистку истёкших сессий.
+
+Формат данных:
+
+```default
+{
+ "sessions": {
+ "": {
+ "username": str,
+ "token": str,
+ "client_id": str,
+ "created_at": ISO8601 str,
+ "expiry": ISO8601 str
+ },
+ ...
+ }
+}
+```
+
+Политика TTL:
+
+- Каждая сессия действует 30 минут; метод `clean_expired_sessions` удаляет записи с истёкшим сроком.
+
+Ограничения:
+
+- Отсутствуют файловые блокировки — в многопроцессной среде возможны гонки.
+- Путь по умолчанию `/etc/gonets/sessions.json` требует прав на запись.
+
+#### clean_expired_sessions()
+
+Удаляет все истекшие сессии из хранилища.
+
+* **Raises:**
+ **Exception** – Если произошла ошибка при чтении или записи данных.
+
+#### NOTE
+Проверяет срок действия каждой сессии и удаляет те, что истекли,
+записывая обновлённые данные.
+
+#### create_session(username: str, token: str, client_id: str) → str
+
+Создаёт новую сессию и возвращает уникальный session_id.
+
+* **Parameters:**
+ * **username** (*str*) – Имя пользователя, для которого создаётся сессия.
+ * **token** (*str*) – Токен аутентификации.
+ * **client_id** (*str*) – Идентификатор клиента.
+* **Returns:**
+ Уникальный идентификатор сессии (session_id).
+* **Return type:**
+ str
+* **Raises:**
+ **Exception** – Если произошла ошибка при создании или записи сессии.
+
+#### NOTE
+Удаляет истекшие сессии перед созданием новой и устанавливает срок
+действия 30 минут.
+
+#### delete_session(session_id: str)
+
+Удаляет сессию по указанному session_id.
+
+* **Parameters:**
+ **session_id** (*str*) – Уникальный идентификатор сессии.
+* **Raises:**
+ **Exception** – Если произошла ошибка при чтении или записи данных.
+
+#### NOTE
+Очищает истекшие сессии перед удалением.
+
+#### get_latest_session(client_id: str) → Dict | None
+
+Получает последнюю активную сессию для указанного client_id.
+
+* **Parameters:**
+ **client_id** (*str*) – Идентификатор клиента.
+* **Returns:**
+ Словарь с данными последней активной сессии, включая
+ : ”session_id”, или None, если сессия не найдена.
+* **Return type:**
+ Optional[Dict]
+* **Raises:**
+ **Exception** – Если произошла ошибка при чтении или обработке данных.
+
+#### NOTE
+Очищает истекшие сессии перед поиском и возвращает сессию с самым
+поздним временем создания.
diff --git a/SRV/docs/_build/markdown/api/utils.md b/SRV/docs/_build/markdown/api/utils.md
new file mode 100644
index 0000000..764079f
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/utils.md
@@ -0,0 +1,3 @@
+# Утилиты
+
+* [utils.i18n](utils/i18n.md)
diff --git a/SRV/docs/_build/markdown/api/utils/i18n.md b/SRV/docs/_build/markdown/api/utils/i18n.md
new file mode 100644
index 0000000..6cdff50
--- /dev/null
+++ b/SRV/docs/_build/markdown/api/utils/i18n.md
@@ -0,0 +1,23 @@
+# utils.i18n
+
+Утилиты локализации и зависимости переводов для FastAPI.
+
+Содержимое модуля:
+: - get_translations(): фабрика зависимости FastAPI, считывающая локаль из
+ query-параметра locale, cookie locale или заголовка Accept-Language
+ (по приоритету), с дефолтом ru. Возвращает функцию-зависимость, которая
+ подгружает babel.support.Translations из каталога SRV/locales.
+ - \_(): удобная обёртка для получения перевода строки с использованием
+ текущей локали (или дефолтного ru, если контекст запроса не передан).
+
+Особенности:
+: - При отсутствии словаря перевода для выбранной локали выполняется fallback на
+ русскую локаль, что гарантирует наличие строк интерфейса.
+ - Локаль определяется только по первому языку из Accept-Language (без
+ региональных суффиксов) для упрощения.
+ - Модуль ведёт отладочное логирование выбора и загрузки локали.
+
+### utils.i18n.get_translations() → callable
+
+Создаёт зависимость для загрузки переводов на основе заголовка Accept-Language
+или параметра запроса ‘locale’. По умолчанию используется русский язык.
diff --git a/SRV/docs/_build/markdown/index.md b/SRV/docs/_build/markdown/index.md
new file mode 100644
index 0000000..83c609c
--- /dev/null
+++ b/SRV/docs/_build/markdown/index.md
@@ -0,0 +1,45 @@
+# GONETS SRV Documentation
+
+Добро пожаловать в документацию серверной части GONETS (SRV).
+
+## Содержание
+
+## Разделы
+
+* [Сервисы](api/services.md)
+ * [ARP Service](api/services/arp_service.md)
+ * [Auth Service](api/services/auth_service.md)
+ * [Connect Service](api/services/connect_service.md)
+ * [Firewalld Service](api/services/firewalld_service.md)
+ * [IP Service](api/services/ip_service.md)
+ * [Network Service](api/services/network_service.md)
+ * [Settings Service](api/services/settings_service.md)
+ * [Subnet Service](api/services/subnet_service.md)
+ * [UFW Service](api/services/ufw_service.md)
+ * [Wifi Service](api/services/wifi_service.md)
+* [Модели](api/models.md)
+ * [models.auth](api/models/auth.md)
+ * [models.network](api/models/network.md)
+ * [models.request](api/models/request.md)
+ * [models.subnet](api/models/subnet.md)
+ * [models.ufw](api/models/ufw.md)
+ * [models.wifi](api/models/wifi.md)
+* [API эндпоинты](api/endpoints.md)
+ * [api.v1.auth](api/endpoints/auth.md)
+ * [api.v1.ip](api/endpoints/ip.md)
+ * [api.v1.network](api/endpoints/network.md)
+ * [api.v1.settings](api/endpoints/settings.md)
+ * [api.v1.subnet](api/endpoints/subnet.md)
+ * [api.v1.ufw](api/endpoints/ufw.md)
+ * [api.v1.websocket](api/endpoints/websocket.md)
+* [Конфигурация](api/config.md)
+ * [config.cors](api/config/cors.md)
+ * [config.init_config](api/config/init_config.md)
+ * [config.ports](api/config/ports.md)
+* [Core](api/core.md)
+ * [core.cli_executor](api/core/cli_executor.md)
+* [Хранилища](api/storage.md)
+ * [storage.json_storage](api/storage/json_storage.md)
+ * [storage.session_storage](api/storage/session_storage.md)
+* [Утилиты](api/utils.md)
+ * [utils.i18n](api/utils/i18n.md)
diff --git a/SRV/docs/api/config.rst b/SRV/docs/api/config.rst
new file mode 100644
index 0000000..3dfa707
--- /dev/null
+++ b/SRV/docs/api/config.rst
@@ -0,0 +1,9 @@
+Конфигурация
+============
+
+.. toctree::
+ :maxdepth: 1
+
+ config/cors
+ config/init_config
+ config/ports
diff --git a/SRV/docs/api/config/cors.rst b/SRV/docs/api/config/cors.rst
new file mode 100644
index 0000000..6dfd244
--- /dev/null
+++ b/SRV/docs/api/config/cors.rst
@@ -0,0 +1,7 @@
+config.cors
+===========
+
+.. automodule:: config.cors
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/config/init_config.rst b/SRV/docs/api/config/init_config.rst
new file mode 100644
index 0000000..3386987
--- /dev/null
+++ b/SRV/docs/api/config/init_config.rst
@@ -0,0 +1,7 @@
+config.init_config
+===================
+
+.. automodule:: config.init_config
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/config/ports.rst b/SRV/docs/api/config/ports.rst
new file mode 100644
index 0000000..b893ee3
--- /dev/null
+++ b/SRV/docs/api/config/ports.rst
@@ -0,0 +1,7 @@
+config.ports
+============
+
+.. automodule:: config.ports
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/core.rst b/SRV/docs/api/core.rst
new file mode 100644
index 0000000..f859c03
--- /dev/null
+++ b/SRV/docs/api/core.rst
@@ -0,0 +1,7 @@
+Core
+====
+
+.. toctree::
+ :maxdepth: 1
+
+ core/cli_executor
diff --git a/SRV/docs/api/core/cli_executor.rst b/SRV/docs/api/core/cli_executor.rst
new file mode 100644
index 0000000..a305ccb
--- /dev/null
+++ b/SRV/docs/api/core/cli_executor.rst
@@ -0,0 +1,7 @@
+core.cli_executor
+==================
+
+.. automodule:: core.cli_executor
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints.rst b/SRV/docs/api/endpoints.rst
new file mode 100644
index 0000000..ca6734e
--- /dev/null
+++ b/SRV/docs/api/endpoints.rst
@@ -0,0 +1,13 @@
+API эндпоинты
+=============
+
+.. toctree::
+ :maxdepth: 1
+
+ endpoints/auth
+ endpoints/ip
+ endpoints/network
+ endpoints/settings
+ endpoints/subnet
+ endpoints/ufw
+ endpoints/websocket
diff --git a/SRV/docs/api/endpoints/auth.rst b/SRV/docs/api/endpoints/auth.rst
new file mode 100644
index 0000000..df442bf
--- /dev/null
+++ b/SRV/docs/api/endpoints/auth.rst
@@ -0,0 +1,7 @@
+api.v1.auth
+===========
+
+.. automodule:: api.v1.auth
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/ip.rst b/SRV/docs/api/endpoints/ip.rst
new file mode 100644
index 0000000..a3199b5
--- /dev/null
+++ b/SRV/docs/api/endpoints/ip.rst
@@ -0,0 +1,7 @@
+api.v1.ip
+=========
+
+.. automodule:: api.v1.ip
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/network.rst b/SRV/docs/api/endpoints/network.rst
new file mode 100644
index 0000000..86ff9c4
--- /dev/null
+++ b/SRV/docs/api/endpoints/network.rst
@@ -0,0 +1,7 @@
+api.v1.network
+==============
+
+.. automodule:: api.v1.network
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/settings.rst b/SRV/docs/api/endpoints/settings.rst
new file mode 100644
index 0000000..7958dd6
--- /dev/null
+++ b/SRV/docs/api/endpoints/settings.rst
@@ -0,0 +1,7 @@
+api.v1.settings
+================
+
+.. automodule:: api.v1.settings
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/subnet.rst b/SRV/docs/api/endpoints/subnet.rst
new file mode 100644
index 0000000..05ddfac
--- /dev/null
+++ b/SRV/docs/api/endpoints/subnet.rst
@@ -0,0 +1,7 @@
+api.v1.subnet
+=============
+
+.. automodule:: api.v1.subnet
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/ufw.rst b/SRV/docs/api/endpoints/ufw.rst
new file mode 100644
index 0000000..cd5fc6e
--- /dev/null
+++ b/SRV/docs/api/endpoints/ufw.rst
@@ -0,0 +1,7 @@
+api.v1.ufw
+==========
+
+.. automodule:: api.v1.ufw
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/endpoints/websocket.rst b/SRV/docs/api/endpoints/websocket.rst
new file mode 100644
index 0000000..0b8116c
--- /dev/null
+++ b/SRV/docs/api/endpoints/websocket.rst
@@ -0,0 +1,7 @@
+api.v1.websocket
+================
+
+.. automodule:: api.v1.websocket
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models.rst b/SRV/docs/api/models.rst
new file mode 100644
index 0000000..49197ba
--- /dev/null
+++ b/SRV/docs/api/models.rst
@@ -0,0 +1,12 @@
+Модели
+======
+
+.. toctree::
+ :maxdepth: 1
+
+ models/auth
+ models/network
+ models/request
+ models/subnet
+ models/ufw
+ models/wifi
diff --git a/SRV/docs/api/models/auth.rst b/SRV/docs/api/models/auth.rst
new file mode 100644
index 0000000..2c266d2
--- /dev/null
+++ b/SRV/docs/api/models/auth.rst
@@ -0,0 +1,7 @@
+models.auth
+===========
+
+.. automodule:: models.auth
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models/network.rst b/SRV/docs/api/models/network.rst
new file mode 100644
index 0000000..2e88043
--- /dev/null
+++ b/SRV/docs/api/models/network.rst
@@ -0,0 +1,7 @@
+models.network
+==============
+
+.. automodule:: models.network
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models/request.rst b/SRV/docs/api/models/request.rst
new file mode 100644
index 0000000..33c43ba
--- /dev/null
+++ b/SRV/docs/api/models/request.rst
@@ -0,0 +1,7 @@
+models.request
+==============
+
+.. automodule:: models.request
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models/subnet.rst b/SRV/docs/api/models/subnet.rst
new file mode 100644
index 0000000..de0c59c
--- /dev/null
+++ b/SRV/docs/api/models/subnet.rst
@@ -0,0 +1,7 @@
+models.subnet
+=============
+
+.. automodule:: models.subnet
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models/ufw.rst b/SRV/docs/api/models/ufw.rst
new file mode 100644
index 0000000..881593a
--- /dev/null
+++ b/SRV/docs/api/models/ufw.rst
@@ -0,0 +1,7 @@
+models.ufw
+==========
+
+.. automodule:: models.ufw
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/models/wifi.rst b/SRV/docs/api/models/wifi.rst
new file mode 100644
index 0000000..399ba8a
--- /dev/null
+++ b/SRV/docs/api/models/wifi.rst
@@ -0,0 +1,7 @@
+models.wifi
+===========
+
+.. automodule:: models.wifi
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services.rst b/SRV/docs/api/services.rst
new file mode 100644
index 0000000..ebca7d0
--- /dev/null
+++ b/SRV/docs/api/services.rst
@@ -0,0 +1,21 @@
+Сервисы
+=======
+
+.. automodule:: services
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. toctree::
+ :maxdepth: 1
+
+ services/arp_service
+ services/auth_service
+ services/connect_service
+ services/firewalld_service
+ services/ip_service
+ services/network_service
+ services/settings_service
+ services/subnet_service
+ services/ufw_service
+ services/wifi_service
diff --git a/SRV/docs/api/services/arp_service.rst b/SRV/docs/api/services/arp_service.rst
new file mode 100644
index 0000000..c99d729
--- /dev/null
+++ b/SRV/docs/api/services/arp_service.rst
@@ -0,0 +1,7 @@
+ARP Service
+===========
+
+.. automodule:: services.arp_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/auth_service.rst b/SRV/docs/api/services/auth_service.rst
new file mode 100644
index 0000000..0cbe8ee
--- /dev/null
+++ b/SRV/docs/api/services/auth_service.rst
@@ -0,0 +1,7 @@
+Auth Service
+============
+
+.. automodule:: services.auth_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/connect_service.rst b/SRV/docs/api/services/connect_service.rst
new file mode 100644
index 0000000..237c613
--- /dev/null
+++ b/SRV/docs/api/services/connect_service.rst
@@ -0,0 +1,7 @@
+Connect Service
+===============
+
+.. automodule:: services.connect_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/firewalld_service.rst b/SRV/docs/api/services/firewalld_service.rst
new file mode 100644
index 0000000..9b6332f
--- /dev/null
+++ b/SRV/docs/api/services/firewalld_service.rst
@@ -0,0 +1,7 @@
+Firewalld Service
+=================
+
+.. automodule:: services.firewalld_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/ip_service.rst b/SRV/docs/api/services/ip_service.rst
new file mode 100644
index 0000000..b976a29
--- /dev/null
+++ b/SRV/docs/api/services/ip_service.rst
@@ -0,0 +1,7 @@
+IP Service
+==========
+
+.. automodule:: services.ip_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/network_service.rst b/SRV/docs/api/services/network_service.rst
new file mode 100644
index 0000000..2794801
--- /dev/null
+++ b/SRV/docs/api/services/network_service.rst
@@ -0,0 +1,7 @@
+Network Service
+===============
+
+.. automodule:: services.network_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/settings_service.rst b/SRV/docs/api/services/settings_service.rst
new file mode 100644
index 0000000..c459fca
--- /dev/null
+++ b/SRV/docs/api/services/settings_service.rst
@@ -0,0 +1,7 @@
+Settings Service
+================
+
+.. automodule:: services.settings_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/subnet_service.rst b/SRV/docs/api/services/subnet_service.rst
new file mode 100644
index 0000000..d059f8f
--- /dev/null
+++ b/SRV/docs/api/services/subnet_service.rst
@@ -0,0 +1,7 @@
+Subnet Service
+==============
+
+.. automodule:: services.subnet_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/ufw_service.rst b/SRV/docs/api/services/ufw_service.rst
new file mode 100644
index 0000000..14ddc77
--- /dev/null
+++ b/SRV/docs/api/services/ufw_service.rst
@@ -0,0 +1,7 @@
+UFW Service
+===========
+
+.. automodule:: services.ufw_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/services/wifi_service.rst b/SRV/docs/api/services/wifi_service.rst
new file mode 100644
index 0000000..787b7c5
--- /dev/null
+++ b/SRV/docs/api/services/wifi_service.rst
@@ -0,0 +1,7 @@
+Wifi Service
+============
+
+.. automodule:: services.wifi_service
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/storage.rst b/SRV/docs/api/storage.rst
new file mode 100644
index 0000000..0a04d57
--- /dev/null
+++ b/SRV/docs/api/storage.rst
@@ -0,0 +1,8 @@
+Хранилища
+=========
+
+.. toctree::
+ :maxdepth: 1
+
+ storage/json_storage
+ storage/session_storage
diff --git a/SRV/docs/api/storage/json_storage.rst b/SRV/docs/api/storage/json_storage.rst
new file mode 100644
index 0000000..0bb5039
--- /dev/null
+++ b/SRV/docs/api/storage/json_storage.rst
@@ -0,0 +1,7 @@
+storage.json_storage
+=====================
+
+.. automodule:: storage.json_storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/storage/session_storage.rst b/SRV/docs/api/storage/session_storage.rst
new file mode 100644
index 0000000..9ae2a9a
--- /dev/null
+++ b/SRV/docs/api/storage/session_storage.rst
@@ -0,0 +1,7 @@
+storage.session_storage
+=======================
+
+.. automodule:: storage.session_storage
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/api/utils.rst b/SRV/docs/api/utils.rst
new file mode 100644
index 0000000..c50b082
--- /dev/null
+++ b/SRV/docs/api/utils.rst
@@ -0,0 +1,7 @@
+Утилиты
+=======
+
+.. toctree::
+ :maxdepth: 1
+
+ utils/i18n
diff --git a/SRV/docs/api/utils/i18n.rst b/SRV/docs/api/utils/i18n.rst
new file mode 100644
index 0000000..74cd021
--- /dev/null
+++ b/SRV/docs/api/utils/i18n.rst
@@ -0,0 +1,7 @@
+utils.i18n
+==========
+
+.. automodule:: utils.i18n
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/SRV/docs/conf.py b/SRV/docs/conf.py
new file mode 100644
index 0000000..d447603
--- /dev/null
+++ b/SRV/docs/conf.py
@@ -0,0 +1,82 @@
+import os
+import sys
+from datetime import datetime
+
+# Для удобства импорта модулей из SRV
+sys.path.insert(0, os.path.abspath('..'))
+
+project = 'GONETS SRV'
+author = 'GONETS'
+copyright = f"{datetime.now().year}, {author}"
+release = '0.0.3'
+
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.viewcode',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.autodoc.typehints',
+]
+
+autosummary_generate = True
+napoleon_google_docstring = False
+napoleon_numpy_docstring = False
+napoleon_use_param = True
+napoleon_use_rtype = True
+napoleon_google_docstring = True
+napoleon_numpy_docstring = False
+
+html_theme = 'furo'
+
+# Исходники структуры документации
+source_suffix = {
+ '.rst': 'restructuredtext',
+}
+
+master_doc = 'index'
+
+html_static_path = ['_static']
+
+# Создание по умолчанию _static если не существует
+static_dir = os.path.join(os.path.dirname(__file__), '_static')
+os.makedirs(static_dir, exist_ok=True)
+
+# Временная директория данных для сборки документации
+data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '_build', '.sphinx_tmp'))
+os.makedirs(data_dir, exist_ok=True)
+
+os.environ.setdefault("GONETS_DATA_DIR", data_dir)
+os.environ.setdefault("GONETS_USERS_PATH", os.path.join(data_dir, "users.json"))
+os.environ.setdefault("GONETS_SESSIONS_PATH", os.path.join(data_dir, "sessions.json"))
+
+config_path = os.path.join(data_dir, "config.ini")
+if not os.path.exists(config_path):
+ with open(config_path, "w", encoding="utf-8") as config_file:
+ config_file.write("[JWT]\nsecret_key = sphinx-docs-secret\n")
+os.environ.setdefault("GONETS_CONFIG_PATH", config_path)
+
+# Мокирование тяжёлых/опциональных модулей для RTD/локальной сборки документации
+autodoc_mock_imports = [
+ 'gi', 'pygobject',
+ 'pywifi', 'sdbus', 'sdbus_async', 'sdbus_async.networkmanager',
+ 'dbus', 'dbus.mainloop', 'dbus.mainloop.glib',
+ 'pydbus', 'mac_vendor_lookup', 'scapy', 'scapy.layers.l2', 'scapy.sendrecv',
+ # мокирование проблемных локальных модулей при сборке документации
+ 'config.init_config',
+]
+
+autosummary_mock_imports = autodoc_mock_imports
+
+suppress_warnings = [
+ 'autodoc.mocked_object',
+]
+
+os.environ.setdefault('SPHINX_BUILD', '1')
+
+autodoc_default_options = {
+ 'members': True,
+ 'undoc-members': True,
+ 'private-members': False,
+ 'show-inheritance': True,
+ 'no-index': True,
+}
diff --git a/SRV/docs/index.rst b/SRV/docs/index.rst
new file mode 100644
index 0000000..4887d9d
--- /dev/null
+++ b/SRV/docs/index.rst
@@ -0,0 +1,19 @@
+GONETS SRV Documentation
+=========================
+
+Добро пожаловать в документацию серверной части GONETS (SRV).
+
+Содержание
+----------
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Разделы
+
+ api/services
+ api/models
+ api/endpoints
+ api/config
+ api/core
+ api/storage
+ api/utils
diff --git a/SRV/gonets.service b/SRV/gonets.service
index ef64efc..7e66ef8 100755
--- a/SRV/gonets.service
+++ b/SRV/gonets.service
@@ -6,7 +6,7 @@ After=network.target
Type=simple
User=root
WorkingDirectory=/usr/share/gonets
-ExecStart=/usr/bin/python3 /usr/share/gonets/main.py
+ExecStart=/usr/bin/python3 /usr/share/gonets/SRV/main.py
Restart=always
RestartSec=10
diff --git a/SRV/locales/en/LC_MESSAGES/messages.mo b/SRV/locales/en/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000..01b985a
Binary files /dev/null and b/SRV/locales/en/LC_MESSAGES/messages.mo differ
diff --git a/SRV/locales/en/LC_MESSAGES/messages.po b/SRV/locales/en/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..395e5ee
--- /dev/null
+++ b/SRV/locales/en/LC_MESSAGES/messages.po
@@ -0,0 +1,508 @@
+# English translations for PROJECT.
+# Copyright (C) 2025 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2025-07-17 10:30+0500\n"
+"PO-Revision-Date: 2025-07-17 10:30+0500\n"
+"Last-Translator: FULL NAME \n"
+"Language: en\n"
+"Language-Team: en \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
+#, python-brace-format
+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:39
+#, python-brace-format
+msgid "Данные узлов подсети {subnet} успешно очищены"
+msgstr "Subnet node data for {subnet} successfully cleared"
+
+#: api/v1/settings.py:41
+msgid "Данные всех подсетей успешно очищены"
+msgstr "All subnet data successfully cleared"
+
+#: api/v1/settings.py:43
+msgid "Не удалось очистить данные узлов подсети"
+msgstr "Failed to clear subnet node data"
+
+#: api/v1/settings.py:46 api/v1/settings.py:71 api/v1/settings.py:96
+#, python-brace-format
+msgid "Ошибка очистки: {error}"
+msgstr "Clear error: {error}"
+
+#: api/v1/settings.py:66
+msgid "Данные точек доступа Wi-Fi успешно очищены"
+msgstr "Wi-Fi access point data successfully cleared"
+
+#: api/v1/settings.py:68
+msgid "Не удалось очистить данные точек доступа Wi-Fi"
+msgstr "Failed to clear Wi-Fi access point data"
+
+#: api/v1/settings.py:91
+msgid "Все сетевые данные успешно очищены"
+msgstr "All network data successfully cleared"
+
+#: api/v1/settings.py:93
+msgid "Не удалось очистить все сетевые данные"
+msgstr "Failed to clear all network data"
+
+#: api/v1/settings.py:119
+#, python-brace-format
+msgid "Файл для подсети {subnet} не найден"
+msgstr "File for subnet {subnet} not found"
+
+#: api/v1/settings.py:143
+msgid "Файл не найден"
+msgstr "File not found"
+
+#: api/v1/settings.py:168
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается."
+msgstr "Language '{locale}' is not supported."
+
+#: api/v1/settings.py:174
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается (нет файлов переводов)."
+msgstr "Language '{locale}' is not supported (no translation files)."
+
+#: api/v1/settings.py:177
+#, python-brace-format
+msgid "Язык успешно установлен: {locale}"
+msgstr "Language successfully set: {locale}"
+
+#: api/v1/settings.py:206
+msgid "Не удалось определить язык в системе"
+msgstr "Failed to determine system language"
+
+#: api/v1/settings.py:209
+#, python-brace-format
+msgid "Ошибка при определении языка в системе {error}"
+msgstr "Error determining system language: {error}"
+
+#: api/v1/subnet.py:44
+#, python-brace-format
+msgid "Ошибка сканирования подсети: {error}"
+msgstr "Subnet scan error: {error}"
+
+#: api/v1/subnet.py:67
+#, python-brace-format
+msgid "Ошибка сканирования Wi-Fi: {error}"
+msgstr "Wi-Fi scan error: {error}"
+
+#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159
+#, python-brace-format
+msgid "Устройство с MAC {mac} не найдено"
+msgstr "Device with MAC {mac} not found"
+
+#: api/v1/subnet.py:94
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} уже существует"
+msgstr "Note for device with MAC {mac} already exists"
+
+#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163
+#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264
+#: services/wifi_service.py:319 services/wifi_service.py:337
+#: services/wifi_service.py:355
+#, python-brace-format
+msgid "Внутренняя ошибка сервера: {error}"
+msgstr "Internal server error: {error}"
+
+#: api/v1/subnet.py:97
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для устройства {mac}"
+msgstr "Note '{note}' added for device {mac}"
+
+#: api/v1/subnet.py:101 api/v1/subnet.py:202
+#, python-brace-format
+msgid "Ошибка добавления заметки: {error}"
+msgstr "Error adding note: {error}"
+
+#: api/v1/subnet.py:128 api/v1/subnet.py:161
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} не существует"
+msgstr "Note for device with MAC {mac} does not exist"
+
+#: api/v1/subnet.py:131
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для устройства {mac}"
+msgstr "Note '{note}' updated for device {mac}"
+
+#: api/v1/subnet.py:135 api/v1/subnet.py:236
+#, python-brace-format
+msgid "Ошибка обновления заметки: {error}"
+msgstr "Error updating note: {error}"
+
+#: api/v1/subnet.py:164
+#, python-brace-format
+msgid "Заметка удалена для устройства {mac}"
+msgstr "Note deleted for device {mac}"
+
+#: api/v1/subnet.py:168 api/v1/subnet.py:269
+#, python-brace-format
+msgid "Ошибка удаления заметки: {error}"
+msgstr "Error deleting note: {error}"
+
+#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260
+#: services/wifi_service.py:327 services/wifi_service.py:345
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена"
+msgstr "Network with SSID {ssid} not found"
+
+#: api/v1/subnet.py:195 services/wifi_service.py:312
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} уже существует"
+msgstr "Note for network with SSID {ssid} already exists"
+
+#: api/v1/subnet.py:198
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
+msgstr "Note '{note}' added for network with SSID {ssid}"
+
+#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330
+#: services/wifi_service.py:348
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} не существует"
+msgstr "Note for network with SSID {ssid} does not exist"
+
+#: api/v1/subnet.py:232
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
+msgstr "Note '{note}' updated for network with SSID {ssid}"
+
+#: api/v1/subnet.py:265
+#, python-brace-format
+msgid "Заметка удалена для сети с SSID {ssid}"
+msgstr "Note deleted for network with SSID {ssid}"
+
+#: api/v1/subnet.py:293
+#, python-brace-format
+msgid "Успешно подключено к {ssid}"
+msgstr "Successfully connected to {ssid}"
+
+#: api/v1/subnet.py:297
+#, python-brace-format
+msgid "Ошибка подключения к Wi-Fi: {error}"
+msgstr "Wi-Fi connection error: {error}"
+
+#: api/v1/subnet.py:320
+#, python-brace-format
+msgid "Успешно отключено от {ssid}"
+msgstr "Successfully disconnected from {ssid}"
+
+#: api/v1/subnet.py:324
+#, python-brace-format
+msgid "Ошибка отключения от Wi-Fi: {error}"
+msgstr "Wi-Fi disconnection error: {error}"
+
+#: api/v1/subnet.py:348
+msgid "connected"
+msgstr "connected"
+
+#: api/v1/subnet.py:352
+#, python-brace-format
+msgid "Ошибка проверки статуса Wi-Fi: {error}"
+msgstr "Wi-Fi status check error: {error}"
+
+#: api/v1/websocket.py:145
+msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
+msgstr "Error: 'activation' key missing in received network data"
+
+#: services/arp_service.py:102
+msgid "Доступные устройства в сети:"
+msgstr "Available devices on the network:"
+
+#: services/arp_service.py:103
+msgid "IP"
+msgstr "IP"
+
+#: services/arp_service.py:103
+msgid "MAC"
+msgstr "MAC"
+
+#: services/auth_service.py:72
+msgid "Ошибка регистрации"
+msgstr "Registration error"
+
+#: services/auth_service.py:80 services/auth_service.py:84
+msgid "Неверный логин или пароль"
+msgstr "Invalid username or password"
+
+#: services/auth_service.py:100
+msgid "Ошибка авторизации"
+msgstr "Authorization error"
+
+#: services/auth_service.py:108
+msgid "Неверный токен"
+msgstr "Invalid token"
+
+#: services/auth_service.py:113 services/auth_service.py:128
+msgid "Пользователь не найден"
+msgstr "User not found"
+
+#: services/auth_service.py:122
+msgid "Сессия недействительна"
+msgstr "Session is invalid"
+
+#: services/auth_service.py:144
+msgid "Успешный выход из системы"
+msgstr "Successfully logged out"
+
+#: services/auth_service.py:148
+msgid "Ошибка при выходе из системы"
+msgstr "Logout error"
+
+#: services/connect_service.py:70 services/connect_service.py:117
+#: services/connect_service.py:154 services/wifi_service.py:227
+msgid "Wi-Fi-интерфейс не доступен"
+msgstr "Wi-Fi interface not available"
+
+#: services/connect_service.py:74
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена в списке доступных сетей"
+msgstr "Network with SSID {ssid} not found in available networks"
+
+#: services/connect_service.py:81 services/connect_service.py:92
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неверный пароль"
+msgstr "Connection error to {ssid}: Incorrect password"
+
+#: services/connect_service.py:83 services/connect_service.py:94
+#, python-brace-format
+msgid ""
+"Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно "
+"указана"
+msgstr "Connection error to {ssid}: Network with SSID {ssid} unavailable or incorrectly specified"
+
+#: services/connect_service.py:85 services/connect_service.py:95
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: {error}"
+msgstr "Connection error to {ssid}: {error}"
+
+#: services/connect_service.py:98
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неизвестная ошибка - {error}"
+msgstr "Connection error to {ssid}: Unknown error - {error}"
+
+#: services/connect_service.py:122
+#, python-brace-format
+msgid "Нет активного подключения для SSID {ssid}"
+msgstr "No active connection for SSID {ssid}"
+
+#: services/connect_service.py:133
+#, python-brace-format
+msgid "Активное подключение с SSID {ssid} не найдено"
+msgstr "Active connection with SSID {ssid} not found"
+
+#: services/connect_service.py:136 services/connect_service.py:139
+#, python-brace-format
+msgid "Ошибка отключения от {ssid}: {error}"
+msgstr "Disconnection error from {ssid}: {error}"
+
+#: services/connect_service.py:160
+msgid "Не подключено к Wi-Fi"
+msgstr "Not connected to Wi-Fi"
+
+#: services/connect_service.py:163
+#, python-brace-format
+msgid "Ошибка проверки статуса: {error}"
+msgstr "Status check error: {error}"
+
+#: services/ip_service.py:69
+#, python-brace-format
+msgid "IP-адрес {ip} уже занят."
+msgstr "IP address {ip} is already in use."
+
+#: services/ip_service.py:73
+msgid "Настройки сетевого профиля доступны для использования."
+msgstr "Network profile settings are available for use."
+
+#: services/ip_service.py:102
+#, python-brace-format
+msgid "Подключение {connection_id} не найдено"
+msgstr "Connection {connection_id} not found"
+
+#: services/ip_service.py:130
+#, python-brace-format
+msgid "Устройство для интерфейса {interface} не найдено"
+msgstr "Device for interface {interface} not found"
+
+#: services/network_service.py:232
+#, python-brace-format
+msgid "Не удалось выполнить команду hostname: {error}"
+msgstr "Failed to execute hostname command: {error}"
+
+#: services/network_service.py:234
+#, python-brace-format
+msgid "Ошибка обработки вывода hostname: {error}"
+msgstr "Error processing hostname output: {error}"
+
+#: services/settings_service.py:55
+#, python-brace-format
+msgid "Ошибка очистки узлов подсети: {error}"
+msgstr "Subnet node clear error: {error}"
+
+#: services/settings_service.py:76
+#, python-brace-format
+msgid "Ошибка очистки точек доступа Wi-Fi: {error}"
+msgstr "Wi-Fi access point clear error: {error}"
+
+#: services/settings_service.py:104
+#, python-brace-format
+msgid "Ошибка очистки всех сетевых данных: {error}"
+msgstr "All network data clear error: {error}"
+
+#: services/subnet_service.py:151 services/wifi_service.py:115
+msgid "Неизвестный"
+msgstr "Unknown"
+
+#: services/subnet_service.py:327
+#, python-brace-format
+msgid "Ошибка при сохранении устройств: {error}"
+msgstr "Error saving devices: {error}"
+
+#: services/subnet_service.py:441
+msgid "Недопустимая подсеть по умолчанию"
+msgstr "Invalid default subnet"
+
+#: services/subnet_service.py:461
+msgid "В данной подсети не обнаружено устройств"
+msgstr "No devices found in this subnet"
+
+#: services/subnet_service.py:518
+#, python-brace-format
+msgid "Ошибка сканирования: {error}"
+msgstr "Scan error: {error}"
+
+#: services/wifi_service.py:61
+#, python-brace-format
+msgid "Ошибка при сохранении заметок: {error}"
+msgstr "Error saving notes: {error}"
+
+#: services/wifi_service.py:81
+msgid "Не найдено сетевых интерфейсов"
+msgstr "No network interfaces found"
+
+#: services/wifi_service.py:102
+msgid "Не найден активный Wi-Fi-интерфейс"
+msgstr "No active Wi-Fi interface found"
+
+#: services/wifi_service.py:122
+msgid "WPA3"
+msgstr "WPA3"
+
+#: services/wifi_service.py:124
+msgid "WPA2"
+msgstr "WPA2"
+
+#: services/wifi_service.py:126
+msgid "WPA"
+msgstr "WPA"
+
+#: services/wifi_service.py:128
+msgid "WEP"
+msgstr "WEP"
+
+#: services/wifi_service.py:129
+msgid "Открытая"
+msgstr "Open"
+
+#: services/wifi_service.py:152
+msgid "2.4 ГГц"
+msgstr "2.4 GHz"
+
+#: services/wifi_service.py:154
+msgid "5 ГГц"
+msgstr "5 GHz"
+
+#: services/wifi_service.py:165
+msgid "Интерфейс не определён"
+msgstr "Interface not defined"
+
+#: services/wifi_service.py:215
+msgid "Таймаут при выполнении iw scan"
+msgstr "Timeout during iw scan"
\ No newline at end of file
diff --git a/SRV/locales/messages.pot b/SRV/locales/messages.pot
new file mode 100644
index 0000000..be111b9
--- /dev/null
+++ b/SRV/locales/messages.pot
@@ -0,0 +1,507 @@
+# Translations template for PROJECT.
+# Copyright (C) 2025 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2025.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2025-07-17 10:30+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.17.0\n"
+
+#: api/v1/auth.py:44
+msgid "Не удалось зарегистрировать пользователя"
+msgstr ""
+
+#: api/v1/auth.py:75
+msgid "Не удалось авторизовать пользователя"
+msgstr ""
+
+#: api/v1/auth.py:103
+msgid "bearer"
+msgstr ""
+
+#: api/v1/auth.py:109
+msgid "Не удалось получить данные пользователя"
+msgstr ""
+
+#: api/v1/auth.py:136
+msgid "Не удалось выйти из системы"
+msgstr ""
+
+#: api/v1/auth.py:162
+msgid "Файл users.json не существует"
+msgstr ""
+
+#: api/v1/auth.py:172
+msgid "Файл users.json существует и содержит пользователей"
+msgstr ""
+
+#: api/v1/auth.py:179
+msgid "Файл users.json существует, но не содержит пользователей"
+msgstr ""
+
+#: api/v1/auth.py:183
+msgid "Ошибка при проверке файла"
+msgstr ""
+
+#: api/v1/ip.py:36 api/v1/ip.py:73 api/v1/ip.py:110
+#, python-brace-format
+msgid "Ошибка проверки IP конфигурации: {error}"
+msgstr ""
+
+#: api/v1/ip.py:64 api/v1/ip.py:101
+#, python-brace-format
+msgid "Настройки сетевого профиля для {connection_id} применены"
+msgstr ""
+
+#: api/v1/network.py:62
+#, python-brace-format
+msgid "Неверное имя контейнера: {container_name}"
+msgstr ""
+
+#: api/v1/network.py:129
+msgid "Не удалось получить сетевые данные"
+msgstr ""
+
+#: api/v1/network.py:182
+msgid ""
+"Отсутствуют обязательные поля: container_id, container_x, container_y, "
+"isOpen"
+msgstr ""
+
+#: api/v1/network.py:194
+msgid "Неверный тип данных в запросе"
+msgstr ""
+
+#: api/v1/network.py:199
+msgid "Позиция и состояние контейнера обновлены"
+msgstr ""
+
+#: api/v1/network.py:203
+#, python-brace-format
+msgid "Не удалось обновить положение контейнера: {error}"
+msgstr ""
+
+#: api/v1/network.py:225
+msgid "Не удалось получить имя хоста"
+msgstr ""
+
+#: api/v1/settings.py:39
+#, python-brace-format
+msgid "Данные узлов подсети {subnet} успешно очищены"
+msgstr ""
+
+#: api/v1/settings.py:41
+msgid "Данные всех подсетей успешно очищены"
+msgstr ""
+
+#: api/v1/settings.py:43
+msgid "Не удалось очистить данные узлов подсети"
+msgstr ""
+
+#: api/v1/settings.py:46 api/v1/settings.py:71 api/v1/settings.py:96
+#, python-brace-format
+msgid "Ошибка очистки: {error}"
+msgstr ""
+
+#: api/v1/settings.py:66
+msgid "Данные точек доступа Wi-Fi успешно очищены"
+msgstr ""
+
+#: api/v1/settings.py:68
+msgid "Не удалось очистить данные точек доступа Wi-Fi"
+msgstr ""
+
+#: api/v1/settings.py:91
+msgid "Все сетевые данные успешно очищены"
+msgstr ""
+
+#: api/v1/settings.py:93
+msgid "Не удалось очистить все сетевые данные"
+msgstr ""
+
+#: api/v1/settings.py:119
+#, python-brace-format
+msgid "Файл для подсети {subnet} не найден"
+msgstr ""
+
+#: api/v1/settings.py:143
+msgid "Файл не найден"
+msgstr ""
+
+#: api/v1/settings.py:168
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается."
+msgstr ""
+
+#: api/v1/settings.py:174
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается (нет файлов переводов)."
+msgstr ""
+
+#: api/v1/settings.py:177
+#, python-brace-format
+msgid "Язык успешно установлен: {locale}"
+msgstr ""
+
+#: api/v1/settings.py:206
+msgid "Не удалось определить язык в системе"
+msgstr ""
+
+#: api/v1/settings.py:209
+#, python-brace-format
+msgid "Ошибка при определении языка в системе {error}"
+msgstr ""
+
+#: api/v1/subnet.py:44
+#, python-brace-format
+msgid "Ошибка сканирования подсети: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:67
+#, python-brace-format
+msgid "Ошибка сканирования Wi-Fi: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159
+#, python-brace-format
+msgid "Устройство с MAC {mac} не найдено"
+msgstr ""
+
+#: api/v1/subnet.py:94
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} уже существует"
+msgstr ""
+
+#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163
+#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264
+#: services/wifi_service.py:319 services/wifi_service.py:337
+#: services/wifi_service.py:355
+#, python-brace-format
+msgid "Внутренняя ошибка сервера: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:97
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для устройства {mac}"
+msgstr ""
+
+#: api/v1/subnet.py:101 api/v1/subnet.py:202
+#, python-brace-format
+msgid "Ошибка добавления заметки: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:128 api/v1/subnet.py:161
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} не существует"
+msgstr ""
+
+#: api/v1/subnet.py:131
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для устройства {mac}"
+msgstr ""
+
+#: api/v1/subnet.py:135 api/v1/subnet.py:236
+#, python-brace-format
+msgid "Ошибка обновления заметки: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:164
+#, python-brace-format
+msgid "Заметка удалена для устройства {mac}"
+msgstr ""
+
+#: api/v1/subnet.py:168 api/v1/subnet.py:269
+#, python-brace-format
+msgid "Ошибка удаления заметки: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260
+#: services/wifi_service.py:327 services/wifi_service.py:345
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена"
+msgstr ""
+
+#: api/v1/subnet.py:195 services/wifi_service.py:312
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} уже существует"
+msgstr ""
+
+#: api/v1/subnet.py:198
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
+msgstr ""
+
+#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330
+#: services/wifi_service.py:348
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} не существует"
+msgstr ""
+
+#: api/v1/subnet.py:232
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
+msgstr ""
+
+#: api/v1/subnet.py:265
+#, python-brace-format
+msgid "Заметка удалена для сети с SSID {ssid}"
+msgstr ""
+
+#: api/v1/subnet.py:293
+#, python-brace-format
+msgid "Успешно подключено к {ssid}"
+msgstr ""
+
+#: api/v1/subnet.py:297
+#, python-brace-format
+msgid "Ошибка подключения к Wi-Fi: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:320
+#, python-brace-format
+msgid "Успешно отключено от {ssid}"
+msgstr ""
+
+#: api/v1/subnet.py:324
+#, python-brace-format
+msgid "Ошибка отключения от Wi-Fi: {error}"
+msgstr ""
+
+#: api/v1/subnet.py:348
+msgid "connected"
+msgstr ""
+
+#: api/v1/subnet.py:352
+#, python-brace-format
+msgid "Ошибка проверки статуса Wi-Fi: {error}"
+msgstr ""
+
+#: api/v1/websocket.py:145
+msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
+msgstr ""
+
+#: services/arp_service.py:102
+msgid "Доступные устройства в сети:"
+msgstr ""
+
+#: services/arp_service.py:103
+msgid "IP"
+msgstr ""
+
+#: services/arp_service.py:103
+msgid "MAC"
+msgstr ""
+
+#: services/auth_service.py:72
+msgid "Ошибка регистрации"
+msgstr ""
+
+#: services/auth_service.py:80 services/auth_service.py:84
+msgid "Неверный логин или пароль"
+msgstr ""
+
+#: services/auth_service.py:100
+msgid "Ошибка авторизации"
+msgstr ""
+
+#: services/auth_service.py:108
+msgid "Неверный токен"
+msgstr ""
+
+#: services/auth_service.py:113 services/auth_service.py:128
+msgid "Пользователь не найден"
+msgstr ""
+
+#: services/auth_service.py:122
+msgid "Сессия недействительна"
+msgstr ""
+
+#: services/auth_service.py:144
+msgid "Успешный выход из системы"
+msgstr ""
+
+#: services/auth_service.py:148
+msgid "Ошибка при выходе из системы"
+msgstr ""
+
+#: services/connect_service.py:70 services/connect_service.py:117
+#: services/connect_service.py:154 services/wifi_service.py:227
+msgid "Wi-Fi-интерфейс не доступен"
+msgstr ""
+
+#: services/connect_service.py:74
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена в списке доступных сетей"
+msgstr ""
+
+#: services/connect_service.py:81 services/connect_service.py:92
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неверный пароль"
+msgstr ""
+
+#: services/connect_service.py:83 services/connect_service.py:94
+#, python-brace-format
+msgid ""
+"Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно "
+"указана"
+msgstr ""
+
+#: services/connect_service.py:85 services/connect_service.py:95
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: {error}"
+msgstr ""
+
+#: services/connect_service.py:98
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неизвестная ошибка - {error}"
+msgstr ""
+
+#: services/connect_service.py:122
+#, python-brace-format
+msgid "Нет активного подключения для SSID {ssid}"
+msgstr ""
+
+#: services/connect_service.py:133
+#, python-brace-format
+msgid "Активное подключение с SSID {ssid} не найдено"
+msgstr ""
+
+#: services/connect_service.py:136 services/connect_service.py:139
+#, python-brace-format
+msgid "Ошибка отключения от {ssid}: {error}"
+msgstr ""
+
+#: services/connect_service.py:160
+msgid "Не подключено к Wi-Fi"
+msgstr ""
+
+#: services/connect_service.py:163
+#, python-brace-format
+msgid "Ошибка проверки статуса: {error}"
+msgstr ""
+
+#: services/ip_service.py:69
+#, python-brace-format
+msgid "IP-адрес {ip} уже занят."
+msgstr ""
+
+#: services/ip_service.py:73
+msgid "Настройки сетевого профиля доступны для использования."
+msgstr ""
+
+#: services/ip_service.py:102
+#, python-brace-format
+msgid "Подключение {connection_id} не найдено"
+msgstr ""
+
+#: services/ip_service.py:130
+#, python-brace-format
+msgid "Устройство для интерфейса {interface} не найдено"
+msgstr ""
+
+#: services/network_service.py:232
+#, python-brace-format
+msgid "Не удалось выполнить команду hostname: {error}"
+msgstr ""
+
+#: services/network_service.py:234
+#, python-brace-format
+msgid "Ошибка обработки вывода hostname: {error}"
+msgstr ""
+
+#: services/settings_service.py:55
+#, python-brace-format
+msgid "Ошибка очистки узлов подсети: {error}"
+msgstr ""
+
+#: services/settings_service.py:76
+#, python-brace-format
+msgid "Ошибка очистки точек доступа Wi-Fi: {error}"
+msgstr ""
+
+#: services/settings_service.py:104
+#, python-brace-format
+msgid "Ошибка очистки всех сетевых данных: {error}"
+msgstr ""
+
+#: services/subnet_service.py:151 services/wifi_service.py:115
+msgid "Неизвестный"
+msgstr ""
+
+#: services/subnet_service.py:327
+#, python-brace-format
+msgid "Ошибка при сохранении устройств: {error}"
+msgstr ""
+
+#: services/subnet_service.py:441
+msgid "Недопустимая подсеть по умолчанию"
+msgstr ""
+
+#: services/subnet_service.py:461
+msgid "В данной подсети не обнаружено устройств"
+msgstr ""
+
+#: services/subnet_service.py:518
+#, python-brace-format
+msgid "Ошибка сканирования: {error}"
+msgstr ""
+
+#: services/wifi_service.py:61
+#, python-brace-format
+msgid "Ошибка при сохранении заметок: {error}"
+msgstr ""
+
+#: services/wifi_service.py:81
+msgid "Не найдено сетевых интерфейсов"
+msgstr ""
+
+#: services/wifi_service.py:102
+msgid "Не найден активный Wi-Fi-интерфейс"
+msgstr ""
+
+#: services/wifi_service.py:122
+msgid "WPA3"
+msgstr ""
+
+#: services/wifi_service.py:124
+msgid "WPA2"
+msgstr ""
+
+#: services/wifi_service.py:126
+msgid "WPA"
+msgstr ""
+
+#: services/wifi_service.py:128
+msgid "WEP"
+msgstr ""
+
+#: services/wifi_service.py:129
+msgid "Открытая"
+msgstr ""
+
+#: services/wifi_service.py:152
+msgid "2.4 ГГц"
+msgstr ""
+
+#: services/wifi_service.py:154
+msgid "5 ГГц"
+msgstr ""
+
+#: services/wifi_service.py:165
+msgid "Интерфейс не определён"
+msgstr ""
+
+#: services/wifi_service.py:215
+msgid "Таймаут при выполнении iw scan"
+msgstr ""
+
diff --git a/SRV/locales/ru/LC_MESSAGES/messages.mo b/SRV/locales/ru/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000..b11e538
Binary files /dev/null and b/SRV/locales/ru/LC_MESSAGES/messages.mo differ
diff --git a/SRV/locales/ru/LC_MESSAGES/messages.po b/SRV/locales/ru/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..45434ba
--- /dev/null
+++ b/SRV/locales/ru/LC_MESSAGES/messages.po
@@ -0,0 +1,504 @@
+# Russian translations for PROJECT.
+# Copyright (C) 2025 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR , 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2025-07-17 10:30+0500\n"
+"PO-Revision-Date: 2025-07-17 10:30+0500\n"
+"Last-Translator: FULL NAME \n"
+"Language: ru\n"
+"Language-Team: ru \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:39
+#, python-brace-format
+msgid "Данные узлов подсети {subnet} успешно очищены"
+msgstr "Данные узлов подсети {subnet} успешно очищены"
+
+#: api/v1/settings.py:41
+msgid "Данные всех подсетей успешно очищены"
+msgstr "Данные всех подсетей успешно очищены"
+
+#: api/v1/settings.py:43
+msgid "Не удалось очистить данные узлов подсети"
+msgstr "Не удалось очистить данные узлов подсети"
+
+#: api/v1/settings.py:46 api/v1/settings.py:71 api/v1/settings.py:96
+#, python-brace-format
+msgid "Ошибка очистки: {error}"
+msgstr "Ошибка очистки: {error}"
+
+#: api/v1/settings.py:66
+msgid "Данные точек доступа Wi-Fi успешно очищены"
+msgstr "Данные точек доступа Wi-Fi успешно очищены"
+
+#: api/v1/settings.py:68
+msgid "Не удалось очистить данные точек доступа Wi-Fi"
+msgstr "Не удалось очистить данные точек доступа Wi-Fi"
+
+#: api/v1/settings.py:91
+msgid "Все сетевые данные успешно очищены"
+msgstr "Все сетевые данные успешно очищены"
+
+#: api/v1/settings.py:93
+msgid "Не удалось очистить все сетевые данные"
+msgstr "Не удалось очистить все сетевые данные"
+
+#: api/v1/settings.py:119
+#, python-brace-format
+msgid "Файл для подсети {subnet} не найден"
+msgstr "Файл для подсети {subnet} не найден"
+
+#: api/v1/settings.py:143
+msgid "Файл не найден"
+msgstr "Файл не найден"
+
+#: api/v1/settings.py:168
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается."
+msgstr "Язык '{locale}' не поддерживается."
+
+#: api/v1/settings.py:174
+#, python-brace-format
+msgid "Язык '{locale}' не поддерживается (нет файлов переводов)."
+msgstr "Язык '{locale}' не поддерживается (нет файлов переводов)."
+
+#: api/v1/settings.py:177
+#, python-brace-format
+msgid "Язык успешно установлен: {locale}"
+msgstr "Язык успешно установлен: {locale}"
+
+#: api/v1/settings.py:206
+msgid "Не удалось определить язык в системе"
+msgstr "Не удалось определить язык в системе"
+
+#: api/v1/settings.py:209
+#, python-brace-format
+msgid "Ошибка при определении языка в системе {error}"
+msgstr "Ошибка при определении языка в системе {error}"
+
+#: api/v1/subnet.py:44
+#, python-brace-format
+msgid "Ошибка сканирования подсети: {error}"
+msgstr "Ошибка сканирования подсети: {error}"
+
+#: api/v1/subnet.py:67
+#, python-brace-format
+msgid "Ошибка сканирования Wi-Fi: {error}"
+msgstr "Ошибка сканирования Wi-Fi: {error}"
+
+#: api/v1/subnet.py:92 api/v1/subnet.py:126 api/v1/subnet.py:159
+#, python-brace-format
+msgid "Устройство с MAC {mac} не найдено"
+msgstr "Устройство с MAC {mac} не найдено"
+
+#: api/v1/subnet.py:94
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} уже существует"
+msgstr "Заметка для устройства с MAC {mac} уже существует"
+
+#: api/v1/subnet.py:96 api/v1/subnet.py:130 api/v1/subnet.py:163
+#: api/v1/subnet.py:197 api/v1/subnet.py:231 api/v1/subnet.py:264
+#: services/wifi_service.py:319 services/wifi_service.py:337
+#: services/wifi_service.py:355
+#, python-brace-format
+msgid "Внутренняя ошибка сервера: {error}"
+msgstr "Внутренняя ошибка сервера: {error}"
+
+#: api/v1/subnet.py:97
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для устройства {mac}"
+msgstr "Заметка '{note}' добавлена для устройства {mac}"
+
+#: api/v1/subnet.py:101 api/v1/subnet.py:202
+#, python-brace-format
+msgid "Ошибка добавления заметки: {error}"
+msgstr "Ошибка добавления заметки: {error}"
+
+#: api/v1/subnet.py:128 api/v1/subnet.py:161
+#, python-brace-format
+msgid "Заметка для устройства с MAC {mac} не существует"
+msgstr "Заметка для устройства с MAC {mac} не существует"
+
+#: api/v1/subnet.py:131
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для устройства {mac}"
+msgstr "Заметка '{note}' обновлена для устройства {mac}"
+
+#: api/v1/subnet.py:135 api/v1/subnet.py:236
+#, python-brace-format
+msgid "Ошибка обновления заметки: {error}"
+msgstr "Ошибка обновления заметки: {error}"
+
+#: api/v1/subnet.py:164
+#, python-brace-format
+msgid "Заметка удалена для устройства {mac}"
+msgstr "Заметка удалена для устройства {mac}"
+
+#: api/v1/subnet.py:168 api/v1/subnet.py:269
+#, python-brace-format
+msgid "Ошибка удаления заметки: {error}"
+msgstr "Ошибка удаления заметки: {error}"
+
+#: api/v1/subnet.py:193 api/v1/subnet.py:227 api/v1/subnet.py:260
+#: services/wifi_service.py:327 services/wifi_service.py:345
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена"
+msgstr "Сеть с SSID {ssid} не найдена"
+
+#: api/v1/subnet.py:195 services/wifi_service.py:312
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} уже существует"
+msgstr "Заметка для сети с SSID {ssid} уже существует"
+
+#: api/v1/subnet.py:198
+#, python-brace-format
+msgid "Заметка '{note}' добавлена для сети с SSID {ssid}"
+msgstr "Заметка '{note}' добавлена для сети с SSID {ssid}"
+
+#: api/v1/subnet.py:229 api/v1/subnet.py:262 services/wifi_service.py:330
+#: services/wifi_service.py:348
+#, python-brace-format
+msgid "Заметка для сети с SSID {ssid} не существует"
+msgstr "Заметка для сети с SSID {ssid} не существует"
+
+#: api/v1/subnet.py:232
+#, python-brace-format
+msgid "Заметка '{note}' обновлена для сети с SSID {ssid}"
+msgstr "Заметка '{note}' обновлена для сети с SSID {ssid}"
+
+#: api/v1/subnet.py:265
+#, python-brace-format
+msgid "Заметка удалена для сети с SSID {ssid}"
+msgstr "Заметка удалена для сети с SSID {ssid}"
+
+#: api/v1/subnet.py:293
+#, python-brace-format
+msgid "Успешно подключено к {ssid}"
+msgstr "Успешно подключено к {ssid}"
+
+#: api/v1/subnet.py:297
+#, python-brace-format
+msgid "Ошибка подключения к Wi-Fi: {error}"
+msgstr "Ошибка подключения к Wi-Fi: {error}"
+
+#: api/v1/subnet.py:320
+#, python-brace-format
+msgid "Успешно отключено от {ssid}"
+msgstr "Успешно отключено от {ssid}"
+
+#: api/v1/subnet.py:324
+#, python-brace-format
+msgid "Ошибка отключения от Wi-Fi: {error}"
+msgstr "Ошибка отключения от Wi-Fi: {error}"
+
+#: api/v1/subnet.py:348
+msgid "connected"
+msgstr "connected"
+
+#: api/v1/subnet.py:352
+#, python-brace-format
+msgid "Ошибка проверки статуса Wi-Fi: {error}"
+msgstr "Ошибка проверки статуса Wi-Fi: {error}"
+
+#: api/v1/websocket.py:145
+msgid "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
+msgstr "Ошибка: Ключ 'activation' отсутствует в полученных данных сети"
+
+#: services/arp_service.py:102
+msgid "Доступные устройства в сети:"
+msgstr "Доступные устройства в сети:"
+
+#: services/arp_service.py:103
+msgid "IP"
+msgstr "IP"
+
+#: services/arp_service.py:103
+msgid "MAC"
+msgstr "MAC"
+
+#: services/auth_service.py:72
+msgid "Ошибка регистрации"
+msgstr "Ошибка регистрации"
+
+#: services/auth_service.py:80 services/auth_service.py:84
+msgid "Неверный логин или пароль"
+msgstr "Неверный логин или пароль"
+
+#: services/auth_service.py:100
+msgid "Ошибка авторизации"
+msgstr "Ошибка авторизации"
+
+#: services/auth_service.py:108
+msgid "Неверный токен"
+msgstr "Неверный токен"
+
+#: services/auth_service.py:113 services/auth_service.py:128
+msgid "Пользователь не найден"
+msgstr "Пользователь не найден"
+
+#: services/auth_service.py:122
+msgid "Сессия недействительна"
+msgstr "Сессия недействительна"
+
+#: services/auth_service.py:144
+msgid "Успешный выход из системы"
+msgstr "Успешный выход из системы"
+
+#: services/auth_service.py:148
+msgid "Ошибка при выходе из системы"
+msgstr "Ошибка при выходе из системы"
+
+#: services/connect_service.py:70 services/connect_service.py:117
+#: services/connect_service.py:154 services/wifi_service.py:227
+msgid "Wi-Fi-интерфейс не доступен"
+msgstr "Wi-Fi-интерфейс не доступен"
+
+#: services/connect_service.py:74
+#, python-brace-format
+msgid "Сеть с SSID {ssid} не найдена в списке доступных сетей"
+msgstr "Сеть с SSID {ssid} не найдена в списке доступных сетей"
+
+#: services/connect_service.py:81 services/connect_service.py:92
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неверный пароль"
+msgstr "Ошибка подключения к {ssid}: Неверный пароль"
+
+#: services/connect_service.py:83 services/connect_service.py:94
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана"
+msgstr "Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана"
+
+#: services/connect_service.py:85 services/connect_service.py:95
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: {error}"
+msgstr "Ошибка подключения к {ssid}: {error}"
+
+#: services/connect_service.py:98
+#, python-brace-format
+msgid "Ошибка подключения к {ssid}: Неизвестная ошибка - {error}"
+msgstr "Ошибка подключения к {ssid}: Неизвестная ошибка - {error}"
+
+#: services/connect_service.py:122
+#, python-brace-format
+msgid "Нет активного подключения для SSID {ssid}"
+msgstr "Нет активного подключения для SSID {ssid}"
+
+#: services/connect_service.py:133
+#, python-brace-format
+msgid "Активное подключение с SSID {ssid} не найдено"
+msgstr "Активное подключение с SSID {ssid} не найдено"
+
+#: services/connect_service.py:136 services/connect_service.py:139
+#, python-brace-format
+msgid "Ошибка отключения от {ssid}: {error}"
+msgstr "Ошибка отключения от {ssid}: {error}"
+
+#: services/connect_service.py:160
+msgid "Не подключено к Wi-Fi"
+msgstr "Не подключено к Wi-Fi"
+
+#: services/connect_service.py:163
+#, python-brace-format
+msgid "Ошибка проверки статуса: {error}"
+msgstr "Ошибка проверки статуса: {error}"
+
+#: services/ip_service.py:69
+#, python-brace-format
+msgid "IP-адрес {ip} уже занят."
+msgstr "IP-адрес {ip} уже занят."
+
+#: services/ip_service.py:73
+msgid "Настройки сетевого профиля доступны для использования."
+msgstr "Настройки сетевого профиля доступны для использования."
+
+#: services/ip_service.py:102
+#, python-brace-format
+msgid "Подключение {connection_id} не найдено"
+msgstr "Подключение {connection_id} не найдено"
+
+#: services/ip_service.py:130
+#, python-brace-format
+msgid "Устройство для интерфейса {interface} не найдено"
+msgstr "Устройство для интерфейса {interface} не найдено"
+
+#: services/network_service.py:232
+#, python-brace-format
+msgid "Не удалось выполнить команду hostname: {error}"
+msgstr "Не удалось выполнить команду hostname: {error}"
+
+#: services/network_service.py:234
+#, python-brace-format
+msgid "Ошибка обработки вывода hostname: {error}"
+msgstr "Ошибка обработки вывода hostname: {error}"
+
+#: services/settings_service.py:55
+#, python-brace-format
+msgid "Ошибка очистки узлов подсети: {error}"
+msgstr "Ошибка очистки узлов подсети: {error}"
+
+#: services/settings_service.py:76
+#, python-brace-format
+msgid "Ошибка очистки точек доступа Wi-Fi: {error}"
+msgstr "Ошибка очистки точек доступа Wi-Fi: {error}"
+
+#: services/settings_service.py:104
+#, python-brace-format
+msgid "Ошибка очистки всех сетевых данных: {error}"
+msgstr "Ошибка очистки всех сетевых данных: {error}"
+
+#: services/subnet_service.py:151 services/wifi_service.py:115
+msgid "Неизвестный"
+msgstr "Неизвестный"
+
+#: services/subnet_service.py:327
+#, python-brace-format
+msgid "Ошибка при сохранении устройств: {error}"
+msgstr "Ошибка при сохранении устройств: {error}"
+
+#: services/subnet_service.py:441
+msgid "Недопустимая подсеть по умолчанию"
+msgstr "Недопустимая подсеть по умолчанию"
+
+#: services/subnet_service.py:461
+msgid "В данной подсети не обнаружено устройств"
+msgstr "В данной подсети не обнаружено устройств"
+
+#: services/subnet_service.py:518
+#, python-brace-format
+msgid "Ошибка сканирования: {error}"
+msgstr "Ошибка сканирования: {error}"
+
+#: services/wifi_service.py:61
+#, python-brace-format
+msgid "Ошибка при сохранении заметок: {error}"
+msgstr "Ошибка при сохранении заметок: {error}"
+
+#: services/wifi_service.py:81
+msgid "Не найдено сетевых интерфейсов"
+msgstr "Не найдено сетевых интерфейсов"
+
+#: services/wifi_service.py:102
+msgid "Не найден активный Wi-Fi-интерфейс"
+msgstr "Не найден активный Wi-Fi-интерфейс"
+
+#: services/wifi_service.py:122
+msgid "WPA3"
+msgstr "WPA3"
+
+#: services/wifi_service.py:124
+msgid "WPA2"
+msgstr "WPA2"
+
+#: services/wifi_service.py:126
+msgid "WPA"
+msgstr "WPA"
+
+#: services/wifi_service.py:128
+msgid "WEP"
+msgstr "WEP"
+
+#: services/wifi_service.py:129
+msgid "Открытая"
+msgstr "Открытая"
+
+#: services/wifi_service.py:152
+msgid "2.4 ГГц"
+msgstr "2.4 ГГц"
+
+#: services/wifi_service.py:154
+msgid "5 ГГц"
+msgstr "5 ГГц"
+
+#: services/wifi_service.py:165
+msgid "Интерфейс не определён"
+msgstr "Интерфейс не определён"
+
+#: services/wifi_service.py:215
+msgid "Таймаут при выполнении iw scan"
+msgstr "Таймаут при выполнении iw scan"
\ No newline at end of file
diff --git a/SRV/logs/app.log b/SRV/logs/app.log
new file mode 100644
index 0000000..a59ba88
--- /dev/null
+++ b/SRV/logs/app.log
@@ -0,0 +1,429 @@
+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: wait_for=>
+2025-11-17 17:45:00,050 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 13:56:19,040 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 14:10:18,471 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 14:10:18,471 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:01:37,990 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:01:37,990 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:01:38,310 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:01:38,310 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:01:38,310 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:01:38,310 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:01:38,310 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:01:39,158 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:01:39,159 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:01:39,160 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:01:39,162 - services.subnet_service - INFO - Auto-detected subnet: 10.0.0.0/8 (IP: 10.1.1.31, Mask: 255.0.0.0 -> /8)
+2025-11-25 16:01:39,176 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:01:39,177 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:01:39,177 - services.subnet_service - INFO - Scanning subnet: 10.0.0.0/8 with interface: wlp0s20f3
+2025-11-25 16:02:45,243 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:02:45,244 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:02:51,958 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:02:51,958 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:02:51,959 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:02:51,959 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:02:51,960 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:03:01,569 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:03:01,570 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:03:01,571 - api.v1.subnet - INFO - Requested subnet scan for: 10.1.1.0
+2025-11-25 16:03:01,571 - services.subnet_service - WARNING - Local device not found in subnet 10.1.1.0
+2025-11-25 16:03:01,571 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0 with interface: wlp0s20f3
+2025-11-25 16:03:03,649 - services.subnet_service - INFO - Discovered devices: 0
+2025-11-25 16:03:03,662 - services.subnet_service - WARNING - No devices found in subnet 10.1.1.0
+2025-11-25 16:03:03,662 - api.v1.subnet - WARNING - No devices found in subnet 10.1.1.0
+2025-11-25 16:03:05,021 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:03:05,021 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:03:05,022 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:03:05,023 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:03:05,023 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:03:06,420 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:03:06,420 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:03:06,421 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:03:06,421 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:03:06,422 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:03:15,472 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:03:15,473 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:03:15,474 - api.v1.subnet - INFO - Requested subnet scan for: 10.1.1.0/24
+2025-11-25 16:03:15,487 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:03:15,487 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:03:15,488 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0/24 with interface: wlp0s20f3
+2025-11-25 16:03:17,674 - services.subnet_service - INFO - Discovered devices: 19
+2025-11-25 16:03:17,683 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:83:c9:22:76:14: this event loop is already running.
+2025-11-25 16:03:17,684 - services.subnet_service - WARNING - Failed to determine vendor for MAC 94:c6:91:99:d1:61: this event loop is already running.
+2025-11-25 16:03:17,684 - services.subnet_service - WARNING - Failed to determine vendor for MAC 20:11:a2:68:02:0f: The vendor for MAC b'2011A268020F' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:03:17,685 - services.subnet_service - WARNING - Failed to determine vendor for MAC b4:4c:3b:33:c1:fa: this event loop is already running.
+2025-11-25 16:03:17,692 - services.subnet_service - WARNING - Failed to determine vendor for MAC 5e:28:1f:e5:7d:18: The vendor for MAC b'5E281FE57D18' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:03:17,693 - services.subnet_service - WARNING - Failed to determine vendor for MAC 02:7d:d8:c1:30:04: The vendor for MAC b'027DD8C13004' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:03:17,693 - services.subnet_service - WARNING - Failed to determine vendor for MAC e2:ec:26:f8:6b:25: this event loop is already running.
+2025-11-25 16:03:17,693 - services.subnet_service - WARNING - Failed to determine vendor for MAC 38:1f:8d:d2:77:e5: this event loop is already running.
+2025-11-25 16:03:17,694 - services.subnet_service - WARNING - Failed to determine vendor for MAC be:61:82:6d:9d:e4: this event loop is already running.
+2025-11-25 16:03:17,693 - services.subnet_service - WARNING - Failed to determine vendor for MAC 38:1f:8d:d4:50:d5: this event loop is already running.
+2025-11-25 16:03:17,694 - services.subnet_service - WARNING - Failed to determine vendor for MAC 38:1f:8d:d2:02:b5: this event loop is already running.
+2025-11-25 16:03:17,694 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'E2EC26F86B25')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'E2EC26'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'E2EC26F86B25' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:03:17,701 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'BE61826D9DE4')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'BE6182'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'BE61826D9DE4' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:03:17,706 - services.subnet_service - INFO - Saved 20 devices to storage/subnets/10.1.1.0_24.json
+2025-11-25 16:03:17,706 - services.subnet_service - INFO - Subnet scan 10.1.1.0/24 completed in 2.23 seconds, found 20 devices (active: 20)
+2025-11-25 16:03:27,512 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:03:27,512 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:03:27,513 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:03:27,513 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:03:27,514 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:03:29,035 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:03:29,035 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:03:29,035 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:03:29,037 - services.subnet_service - INFO - Auto-detected subnet: 10.0.0.0/8 (IP: 10.1.1.31, Mask: 255.0.0.0 -> /8)
+2025-11-25 16:03:29,049 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:03:29,049 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:03:29,049 - services.subnet_service - INFO - Scanning subnet: 10.0.0.0/8 with interface: wlp0s20f3
+2025-11-25 16:06:03,717 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:06:03,718 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:06:03,718 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:06:03,719 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:06:03,719 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:06:05,071 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:06:05,072 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:06:05,073 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:06:05,075 - services.subnet_service - INFO - Auto-detected subnet: 10.0.0.0/8 (IP: 10.1.1.31, Mask: 255.0.0.0 -> /8)
+2025-11-25 16:06:05,087 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:06:05,087 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:06:05,087 - services.subnet_service - INFO - Scanning subnet: 10.0.0.0/8 with interface: wlp0s20f3
+2025-11-25 16:06:17,889 - api.v1.websocket - INFO - Client disconnected
+2025-11-25 16:07:07,658 - api.v1.websocket - INFO - Client disconnected
+2025-11-25 16:07:12,680 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:07:12,680 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:07:12,680 - api.v1.subnet - INFO - Requested subnet scan for: 10.1.1.0
+2025-11-25 16:07:12,681 - services.subnet_service - WARNING - Local device not found in subnet 10.1.1.0
+2025-11-25 16:07:12,681 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0 with interface: wlp0s20f3
+2025-11-25 16:07:14,770 - services.subnet_service - INFO - Discovered devices: 0
+2025-11-25 16:07:14,777 - services.subnet_service - WARNING - No devices found in subnet 10.1.1.0
+2025-11-25 16:07:14,778 - api.v1.subnet - WARNING - No devices found in subnet 10.1.1.0
+2025-11-25 16:07:22,413 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:07:22,414 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:07:22,415 - api.v1.subnet - INFO - Requested subnet scan for: 10.1.1.0/24
+2025-11-25 16:07:22,429 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:07:22,429 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:07:22,430 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0/24 with interface: wlp0s20f3
+2025-11-25 16:07:24,629 - services.subnet_service - INFO - Discovered devices: 0
+2025-11-25 16:07:24,637 - services.subnet_service - INFO - Saved 20 devices to storage/subnets/10.1.1.0_24.json
+2025-11-25 16:07:24,637 - services.subnet_service - INFO - Subnet scan 10.1.1.0/24 completed in 2.22 seconds, found 20 devices (active: 1)
+2025-11-25 16:07:55,744 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:07:55,745 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:07:55,746 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:07:55,748 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:07:55,749 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:07:57,100 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:07:57,102 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:07:57,102 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:07:57,105 - services.subnet_service - INFO - Auto-detected subnet: 10.0.0.0/8 (IP: 10.1.1.31, Mask: 255.0.0.0 -> /8)
+2025-11-25 16:07:57,125 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:07:57,125 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:07:57,125 - services.subnet_service - INFO - Scanning subnet: 10.0.0.0/8 with interface: wlp0s20f3
+2025-11-25 16:08:01,742 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:08:01,743 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:08:01,745 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:08:01,749 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:08:01,751 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:08:06,690 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:08:06,702 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:08:06,744 - api.v1.subnet - INFO - Requested subnet scan for: 10.1.1.0/24
+2025-11-25 16:08:06,772 - services.subnet_service - WARNING - Invalid last_seen format for device dc:91:66:59:0c:1c
+2025-11-25 16:08:06,785 - services.subnet_service - WARNING - Invalid last_seen format for device c0:83:c9:22:76:14
+2025-11-25 16:08:06,806 - services.subnet_service - WARNING - Invalid last_seen format for device c0:83:c9:22:76:27
+2025-11-25 16:08:06,813 - services.subnet_service - WARNING - Invalid last_seen format for device 54:16:51:32:d0:df
+2025-11-25 16:08:06,813 - services.subnet_service - WARNING - Invalid last_seen format for device 20:11:a2:68:02:0f
+2025-11-25 16:08:06,813 - services.subnet_service - WARNING - Invalid last_seen format for device 38:1f:8d:d2:77:e5
+2025-11-25 16:08:06,830 - services.subnet_service - WARNING - Invalid last_seen format for device 38:1f:8d:d4:50:d5
+2025-11-25 16:08:06,842 - services.subnet_service - WARNING - Invalid last_seen format for device e2:ec:26:f8:6b:25
+2025-11-25 16:08:06,847 - services.subnet_service - WARNING - Invalid last_seen format for device 5e:28:1f:e5:7d:18
+2025-11-25 16:08:06,859 - services.subnet_service - WARNING - Invalid last_seen format for device 94:c6:91:99:d1:61
+2025-11-25 16:08:06,888 - services.subnet_service - WARNING - Invalid last_seen format for device 02:7d:d8:c1:30:04
+2025-11-25 16:08:06,895 - services.subnet_service - WARNING - Invalid last_seen format for device be:61:82:6d:9d:e4
+2025-11-25 16:08:06,908 - services.subnet_service - WARNING - Invalid last_seen format for device cc:4b:73:e4:17:7e
+2025-11-25 16:08:06,914 - services.subnet_service - WARNING - Invalid last_seen format for device c0:39:5a:1a:9c:c8
+2025-11-25 16:08:06,915 - services.subnet_service - WARNING - Invalid last_seen format for device c0:39:5a:1a:9d:02
+2025-11-25 16:08:06,921 - services.subnet_service - WARNING - Invalid last_seen format for device c0:39:5a:1a:a3:a4
+2025-11-25 16:08:06,928 - services.subnet_service - WARNING - Invalid last_seen format for device c0:39:5a:1a:a3:c0
+2025-11-25 16:08:06,934 - services.subnet_service - WARNING - Invalid last_seen format for device b4:4c:3b:33:c1:fa
+2025-11-25 16:08:06,941 - services.subnet_service - WARNING - Invalid last_seen format for device 38:1f:8d:d2:02:b5
+2025-11-25 16:08:07,042 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:08:07,043 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:08:07,048 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0/24 with interface: wlp0s20f3
+2025-11-25 16:08:13,093 - services.subnet_service - INFO - Discovered devices: 15
+2025-11-25 16:08:13,227 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:83:c9:22:76:14: this event loop is already running.
+2025-11-25 16:08:13,244 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:83:c9:22:76:27: this event loop is already running.
+2025-11-25 16:08:13,256 - services.subnet_service - WARNING - Failed to determine vendor for MAC 20:11:a2:68:02:0f: The vendor for MAC b'2011A268020F' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:08:13,301 - services.subnet_service - WARNING - Failed to determine vendor for MAC 52:c4:40:b1:21:4c: this event loop is already running.
+2025-11-25 16:08:13,302 - services.subnet_service - WARNING - Failed to determine vendor for MAC fa:6d:85:71:b4:6f: this event loop is already running.
+2025-11-25 16:08:13,311 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'52C440B1214C')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'52C440'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'52C440B1214C' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:08:13,312 - services.subnet_service - WARNING - Failed to determine vendor for MAC 94:c6:91:99:d1:61: this event loop is already running.
+2025-11-25 16:08:13,323 - services.subnet_service - WARNING - Failed to determine vendor for MAC be:61:82:6d:9d:e4: this event loop is already running.
+2025-11-25 16:08:13,345 - services.subnet_service - WARNING - Failed to determine vendor for MAC 02:7d:d8:c1:30:04: this event loop is already running.
+2025-11-25 16:08:13,356 - services.subnet_service - WARNING - Failed to determine vendor for MAC cc:4b:73:e4:17:7e: this event loop is already running.
+2025-11-25 16:08:13,363 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:39:5a:1a:9c:c8: this event loop is already running.
+2025-11-25 16:08:13,363 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:39:5a:1a:9d:02: this event loop is already running.
+2025-11-25 16:08:13,391 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:39:5a:1a:a3:a4: this event loop is already running.
+2025-11-25 16:08:13,527 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'FA6D8571B46F')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'FA6D85'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'FA6D8571B46F' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:08:13,798 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'BE61826D9DE4')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'BE6182'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'BE61826D9DE4' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:08:13,832 - asyncio - ERROR - Task exception was never retrieved
+future: exception=VendorNotFoundError(b'027DD8C13004')>
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 104, in lookup
+ return self.prefixes[mac[:6]].decode("utf8")
+ ~~~~~~~~~~~~~^^^^^^^^^
+KeyError: b'027DD8'
+
+During handling of the above exception, another exception occurred:
+
+Traceback (most recent call last):
+ File "/root/.cache/pypoetry/virtualenvs/gonets-MdAedEct-py3.11/lib/python3.11/site-packages/mac_vendor_lookup.py", line 106, in lookup
+ raise VendorNotFoundError(mac)
+mac_vendor_lookup.VendorNotFoundError: The vendor for MAC b'027DD8C13004' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:08:14,251 - services.subnet_service - INFO - Saved 16 devices to storage/subnets/10.1.1.0_24.json
+2025-11-25 16:08:14,273 - services.subnet_service - INFO - Subnet scan 10.1.1.0/24 completed in 7.52 seconds, found 16 devices (active: 16)
+2025-11-25 16:10:39,551 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:10:39,553 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:10:39,556 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:10:39,559 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:10:39,561 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:10:40,737 - api.v1.websocket - INFO - Client disconnected
+2025-11-25 16:10:46,126 - api.v1.websocket - INFO - Client disconnected
+2025-11-25 16:10:47,610 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:10:47,627 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:10:47,644 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:10:47,703 - services.subnet_service - INFO - Auto-detected subnet: 10.0.0.0/8 (IP: 10.1.1.31, Mask: 255.0.0.0 -> /8)
+2025-11-25 16:10:47,965 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:10:47,971 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:10:47,987 - services.subnet_service - INFO - Scanning subnet: 10.0.0.0/8 with interface: wlp0s20f3
+2025-11-25 16:11:12,067 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:11:12,068 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:11:12,072 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:11:12,084 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:11:12,086 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:23:32,518 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-25 16:23:37,298 - storage.session_storage - INFO - Сессия для client_id 127.0.0.1 не найдена
+2025-11-25 16:23:37,298 - services.auth_service - ERROR - Session for client_id 127.0.0.1 not found or expired
+2025-11-25 16:23:37,299 - storage.json_storage - INFO - Проверка существования файла /etc/gonets/users.json
+2025-11-25 16:23:37,299 - storage.json_storage - INFO - Файл /etc/gonets/users.json уже существует
+2025-11-25 16:23:37,299 - api.v1.auth - INFO - Файл /etc/gonets/users.json существует и содержит пользователей
+2025-11-25 16:23:48,170 - services.subnet_service - INFO - Default interface from gateway: wlp0s20f3
+2025-11-25 16:23:48,170 - services.subnet_service - INFO - Initialized SubnetService with interface: wlp0s20f3
+2025-11-25 16:23:48,171 - api.v1.subnet - INFO - Requested subnet scan for: auto-detected
+2025-11-25 16:23:48,173 - services.subnet_service - INFO - Auto-detected subnet: 10.1.1.0/24 (IP: 10.1.1.31, Mask: 255.255.255.0 -> /24)
+2025-11-25 16:23:48,189 - services.subnet_service - INFO - Local device detected: IP=10.1.1.31, MAC=dc:97:ba:3a:33:0b, Interface=wlp0s20f3
+2025-11-25 16:23:48,189 - services.subnet_service - INFO - Detected local device: 10.1.1.31, MAC: dc:97:ba:3a:33:0b
+2025-11-25 16:23:48,189 - services.subnet_service - INFO - Scanning subnet: 10.1.1.0/24 with interface: wlp0s20f3
+2025-11-25 16:23:50,337 - services.subnet_service - INFO - Discovered devices: 18
+2025-11-25 16:23:50,434 - services.subnet_service - WARNING - Failed to determine vendor for MAC c0:83:c9:22:76:14: this event loop is already running.
+2025-11-25 16:23:50,436 - services.subnet_service - WARNING - Failed to determine vendor for MAC 20:11:a2:68:02:0f: The vendor for MAC b'2011A268020F' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:23:50,517 - services.subnet_service - WARNING - Failed to determine vendor for MAC be:61:82:6d:9d:e4: The vendor for MAC b'BE61826D9DE4' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:23:50,518 - services.subnet_service - WARNING - Failed to determine vendor for MAC 02:7d:d8:c1:30:04: The vendor for MAC b'027DD8C13004' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:23:50,594 - services.subnet_service - WARNING - Failed to determine vendor for MAC cc:4b:73:e4:17:7e: this event loop is already running.
+2025-11-25 16:23:50,594 - services.subnet_service - WARNING - Failed to determine vendor for MAC fa:6d:85:71:b4:6f: The vendor for MAC b'FA6D8571B46F' could not be found. Either it's not registered or the local list is out of date. Try MacLookup().update_vendors()
+2025-11-25 16:23:50,596 - services.subnet_service - INFO - Saved 20 devices to storage/subnets/10.1.1.0_24.json
+2025-11-25 16:23:50,596 - services.subnet_service - INFO - Subnet scan 10.1.1.0/24 completed in 2.42 seconds, found 20 devices (active: 19)
+2025-11-27 12:44:07,084 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
+2025-11-27 12:44:07,084 - asyncio - ERROR - Task was destroyed but it is pending!
+task: wait_for=>
diff --git a/SRV/main.py b/SRV/main.py
index 3511099..daaf8d7 100644
--- a/SRV/main.py
+++ b/SRV/main.py
@@ -1,8 +1,52 @@
+"""
+Инициализация FastAPI-приложения GONETS.
+
+Содержимое модуля:
+ - Создание экземпляра приложения FastAPI с метаданными (title, description, version).
+ - Подключение CORS-политики через `CORSMiddlewareConfig` с заранее заданными
+ доверенными источниками (localhost и стандартные порты фронтенда).
+ - Монтаж корневого роутера `api.router`, который инкапсулирует все `v1`-эндпоинты
+ (auth, subnet, ip, network, ufw, websocket, settings).
+ - Инициализация `NetworkService` на уровне приложения для подготовки сетевой
+ подсистемы (например, ленивые проверки окружения/зависимостей).
+ - Точка входа для локального запуска через `uvicorn` (режим разработки с reload).
+
+Замечания по развертыванию:
+ - В прод-среде приложение запускается через `systemd` юнит `gonets.service`,
+ где в качестве команды используется `python3 SRV/main.py`.
+ - Для TLS можно использовать закомментированную строку с указанием
+ `ssl_keyfile` и `ssl_certfile` (сертификаты ожидаются в `/etc/gonets/certs`).
+
+Безопасность и окружение:
+ - Перекрестные запросы (CORS) ограничены списком localhost-источников, что
+ соответствует типичной локальной разработке (Vite/DevServer и т.д.).
+ - Все пути и сервисы, требующие повышенных прав (например, системные команды),
+ должны выполняться в соответствующем окружении с необходимыми разрешениями.
+"""
from fastapi import FastAPI
import uvicorn
from api.router import router
from config.cors import CORSMiddlewareConfig
from services.network_service import NetworkService
+import logging
+from pathlib import Path
+import os
+
+# Логируем в файл 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(
title="GONETS",
@@ -18,5 +62,8 @@ app.include_router(router)
network_service = NetworkService()
if __name__ == "__main__":
- uvicorn.run("main:app", host='0.0.0.0', port=8001, reload=True, ssl_keyfile="/etc/gonets/certs/gonets.key", ssl_certfile="/etc/gonets/certs/gonets.crt")
- #uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True)
+ # reload режет Nuitka onefile (reloader ищет модуль main на ФС),
+ # поэтому по умолчанию выключаем; включается переменной GONETS_RELOAD=1 для локалки.
+ reload_enabled = os.getenv("GONETS_RELOAD", "").lower() in {"1", "true", "yes"}
+ target = "main:app" if reload_enabled else app
+ uvicorn.run(target, host="0.0.0.0", port=8001, reload=reload_enabled, ssl_keyfile="/etc/gonets/certs/gonets.key", ssl_certfile="/etc/gonets/certs/gonets.crt")
diff --git a/SRV/models/auth.py b/SRV/models/auth.py
index 194c067..2a58f99 100644
--- a/SRV/models/auth.py
+++ b/SRV/models/auth.py
@@ -3,13 +3,13 @@ from typing import Optional
class UserCreate(BaseModel):
- """
- Модель созданного пользователя
+ """Модель созданного пользователя.
Атрибуты:
- username (str): Имя пользователя
- password (str): Пароль пользователя
- is_admin (bool): Административный доступ
+
+ - ``username`` (str): Имя пользователя.
+ - ``password`` (str): Пароль пользователя.
+ - ``is_admin`` (bool): Флаг административного доступа.
"""
username: str
@@ -18,12 +18,12 @@ class UserCreate(BaseModel):
class UserLogin(BaseModel):
- """
- Модель авторизации пользователя
+ """Модель авторизации пользователя.
Атрибуты:
- username (str): Имя пользователя
- password (str): Пароль пользователя
+
+ - ``username`` (str): Имя пользователя.
+ - ``password`` (str): Пароль пользователя.
"""
username: str
@@ -31,13 +31,13 @@ class UserLogin(BaseModel):
class UserResponse(BaseModel):
- """
- Модель проверки наличия пользователя
+ """Модель, используемая при возврате сведений о пользователе.
Атрибуты:
- id (int): Идентификатор
- username (str): Имя пользователя
- is_admin (bool): Административный доступ
+
+ - ``id`` (int): Идентификатор пользователя.
+ - ``username`` (str): Имя пользователя.
+ - ``is_admin`` (bool): Флаг административного доступа.
"""
id: int
@@ -46,14 +46,14 @@ class UserResponse(BaseModel):
class Token(BaseModel):
- """
- Токен пользователя
+ """Модель JWT-токена.
Атрибуты:
- access_token (str): Токен пользователя (захэширован)
- token_type (str): Тип токена
- session_id (Optional[str]): Идентификатор сессии
- user (Optional[UserResponse]): Пользователь на основе проверки наличия пользователя
+
+ - ``access_token`` (str): Значение токена (в формате JWT).
+ - ``token_type`` (str): Тип токена (обычно ``bearer``).
+ - ``session_id`` (Optional[str]): Идентификатор сессии.
+ - ``user`` (Optional[UserResponse]): Данные пользователя, если включены в ответ.
"""
access_token: str
diff --git a/SRV/models/network.py b/SRV/models/network.py
index b9314d1..bebbb81 100644
--- a/SRV/models/network.py
+++ b/SRV/models/network.py
@@ -3,14 +3,14 @@ from pydantic import BaseModel
class IPv4Route(BaseModel):
- """
- Модель маршрута IPv4
+ """Модель маршрута IPv4.
Атрибуты:
- destination (str): IP-адрес назначения
- prefix (int): Префикс подсети в формате CIDR
- next_hop (str): Адрес следующего узла (маршрутизатор)
- metric (int): Метрика маршрута
+
+ - ``destination`` (str): IP-адрес назначения.
+ - ``prefix`` (int): Префикс подсети в формате CIDR.
+ - ``next_hop`` (str): Адрес следующего узла (маршрутизатор).
+ - ``metric`` (int): Метрика маршрута.
"""
destination: str
@@ -20,14 +20,14 @@ class IPv4Route(BaseModel):
class IPv6Route(BaseModel):
- """
- Модель маршрута IPv6
+ """Модель маршрута IPv6.
Атрибуты:
- destination (str): IP-адрес назначения
- prefix (int): Префикс подсети (CIDR)
- next_hop (str): Адрес следующего узла (маршрутизатор)
- metric (int): Метрика маршрута
+
+ - ``destination`` (str): IP-адрес назначения.
+ - ``prefix`` (int): Префикс подсети в формате CIDR.
+ - ``next_hop`` (str): Адрес следующего узла.
+ - ``metric`` (int): Метрика маршрута.
"""
destination: str
@@ -37,15 +37,15 @@ class IPv6Route(BaseModel):
class IndividualIPConfig(BaseModel):
- """
- Модель индивидуальной конфигурации IP-адреса
+ """Модель индивидуальной конфигурации IP-адреса.
Атрибуты:
- address (str): IP-адрес интерфейса
- routes (List[Union[IPv4Route, IPv6Route]]): Список маршрутов (IPv4/IPv6)
- method (Optional[str]): Метод назначения IP
- gateway (str): Основной шлюз по умолчанию
- dns (List[str]): Список DNS-серверов
+
+ - ``address`` (str): IP-адрес интерфейса.
+ - ``routes`` (List[Union[IPv4Route, IPv6Route]]): Список маршрутов.
+ - ``method`` (Optional[str]): Метод назначения IP.
+ - ``gateway`` (str): Шлюз по умолчанию.
+ - ``dns`` (List[str]): Список DNS-серверов.
"""
address: str
@@ -56,31 +56,31 @@ class IndividualIPConfig(BaseModel):
class IPConfig(BaseModel):
- """
- Модель конфигурации IP (IPv4/IPv6)
+ """Групповая конфигурация IP-адресов.
Атрибуты:
- configurations (List[IndividualIPConfig]): Список индивидуальных IP-конфигураций
+
+ - ``configurations`` (List[IndividualIPConfig]): Индивидуальные IP-конфигурации.
"""
configurations: List[IndividualIPConfig]
class NetworkInterface(BaseModel):
- """
- Модель базового сетевого интерфейса
+ """Модель базового сетевого интерфейса.
Атрибуты:
- name (str): Название интерфейса
- type (str): Тип интерфейса
- mac_address (str): MAC-адрес устройства
- device_state (str): Состояние устройства
- active_connection (str): Название активного соединения
- device_mtu (str): MTU устройства
- ipv4 (IPConfig): Конфигурация IPv4
- ipv6 (IPConfig): Конфигурация IPv6
- container_x (int): Положение по X на холсте
- container_y (int): Положение по Y на холсте
+
+ - ``name`` (str): Название интерфейса.
+ - ``type`` (str): Тип устройства.
+ - ``mac_address`` (str): MAC-адрес.
+ - ``device_state`` (str): Текущее состояние.
+ - ``active_connection`` (str): Название активного соединения.
+ - ``device_mtu`` (str): MTU устройства.
+ - ``ipv4`` (IPConfig): IPv4-конфигурация.
+ - ``ipv6`` (IPConfig): IPv6-конфигурация.
+ - ``container_x`` (int): Положение по оси X на холсте.
+ - ``container_y`` (int): Положение по оси Y на холсте.
"""
name: str
@@ -96,16 +96,16 @@ class NetworkInterface(BaseModel):
class WiFiDevice(NetworkInterface):
- """
- Модель Wi-Fi устройства (наследуется от NetworkInterface)
+ """Расширенная модель Wi-Fi устройства.
Атрибуты:
- ssid (Optional[str]): Название Wi-Fi сети
- bssid (Optional[str]): MAC-адрес точки доступа
- bitrate (Optional[int]): Скорость соединения
- strength (Optional[int]): Уровень сигнала
- frequency (Optional[float]): Частота
- mode (Union[str, int]): Режим работы
+
+ - ``ssid`` (Optional[str]): Название Wi-Fi сети.
+ - ``bssid`` (Optional[str]): MAC-адрес точки доступа.
+ - ``bitrate`` (Optional[int]): Скорость соединения.
+ - ``strength`` (Optional[int]): Уровень сигнала.
+ - ``frequency`` (Optional[float]): Рабочая частота.
+ - ``mode`` (Union[str, int]): Режим работы.
"""
ssid: Optional[str] = None
@@ -113,17 +113,17 @@ class WiFiDevice(NetworkInterface):
bitrate: Optional[int] = None
strength: Optional[int] = None
frequency: Optional[float] = None
- mode: Union[str, int] = None
+ mode: Optional[Union[str, int]] = None
class NetworkInterfacesContainer(BaseModel):
- """
- Контейнер для группы сетевых интерфейсов с координатами
+ """Контейнер для набора сетевых интерфейсов.
Атрибуты:
- interfaces (List[Union[NetworkInterface, WiFiDevice]]): Список интерфейсов
- container_x (int): Положение по оси X
- container_y (int): Положение по оси Y
+
+ - ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Список интерфейсов.
+ - ``container_x`` (int): Положение по оси X.
+ - ``container_y`` (int): Положение по оси Y.
"""
interfaces: List[Union[NetworkInterface, WiFiDevice]]
@@ -132,15 +132,15 @@ class NetworkInterfacesContainer(BaseModel):
class Activation(BaseModel):
- """
- Модель активированных интерфейсов
+ """Модель узла активных интерфейсов.
Атрибуты:
- id (int): Идентификатор ноды активированных интерфейсов
- interfaces (List[Union[NetworkInterface, WiFiDevice]]): Активированные интерфейсы
- container_x (int): Положение по X на холсте
- container_y (int): Положение по Y на холсте
- isOpen (bool): Флаг скрытия ноды на холсте
+
+ - ``id`` (int): Идентификатор ноды.
+ - ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Активные интерфейсы.
+ - ``container_x`` (int): Координата X на холсте.
+ - ``container_y`` (int): Координата Y на холсте.
+ - ``isOpen`` (bool): Флаг отображения узла на холсте.
"""
id: int
@@ -151,15 +151,15 @@ class Activation(BaseModel):
class Deactivation(BaseModel):
- """
- Модель отключенных интерфейсов
+ """Модель узла отключённых интерфейсов.
Атрибуты:
- id (int): Идентификатор ноды отключенных интерфейсов
- interfaces (List[Union[NetworkInterface, WiFiDevice]]): Отключенные интерфейсы
- container_x (int): Положение по X на холсте
- container_y (int): Положение по Y на холсте
- isOpen (bool): Флаг скрытия ноды на холсте
+
+ - ``id`` (int): Идентификатор ноды.
+ - ``interfaces`` (List[Union[NetworkInterface, WiFiDevice]]): Список отключённых интерфейсов.
+ - ``container_x`` (int): Координата X на холсте.
+ - ``container_y`` (int): Координата Y на холсте.
+ - ``isOpen`` (bool): Флаг отображения узла на холсте.
"""
id: int
diff --git a/SRV/models/request.py b/SRV/models/request.py
index cb6a3b4..f2163e9 100644
--- a/SRV/models/request.py
+++ b/SRV/models/request.py
@@ -2,31 +2,31 @@ from pydantic import BaseModel
class NoteRequest(BaseModel):
- """
- Модель добавления заметки к просканированному устройству
+ """Модель запроса на добавление заметки к устройству.
Атрибуты:
- note (str): Заметка устройства
+
+ - ``note`` (str): Текст заметки.
"""
note: str
class WifiConnectRequest(BaseModel):
- """
- Модель для подключения к точке доступа Wi-Fi
+ """Модель запроса на подключение к Wi-Fi.
Атрибуты:
- ssid (str): Название точки доступа Wi-Fi
- password (str): Пароль точки доступа Wi-Fi
+
+ - ``ssid`` (str): Название точки доступа.
+ - ``password`` (str): Пароль доступа.
"""
ssid: str
password: str
class WifiDisconnectRequest(BaseModel):
- """
- Модель для отключения от точки доступа Wi-Fi
+ """Модель запроса на отключение от Wi-Fi.
Атрибуты:
- ssid (str): Название точки доступа Wi-Fi
+
+ - ``ssid`` (str): Название точки доступа.
"""
- ssid: str
\ No newline at end of file
+ ssid: str
diff --git a/SRV/models/subnet.py b/SRV/models/subnet.py
index d281cfe..9496281 100644
--- a/SRV/models/subnet.py
+++ b/SRV/models/subnet.py
@@ -1,20 +1,19 @@
from pydantic import BaseModel
from typing import Optional, List
-
class Device(BaseModel):
- """
- Сканируемое устройство в подсети
+ """Модель устройства, обнаруженного в подсети.
Атрибуты:
- ip (str): IP-адрес устройства
- mac (Optional[str]): MAC-адрес устройства
- hostname (Optional[str]): Имя хоста
- vendor (Optional[str]): Название производителя устройства (По MAC-адресу)
- open_ports (List[str]): Открытые порты устройства
- status (str): Статус устройства (онлайн/оффлайн)
- last_seen (Optional[str]): Последняя активность устройства
- note (Optional[str]): Заметка устройства
+
+ - ``ip`` (str): IP-адрес устройства.
+ - ``mac`` (Optional[str]): MAC-адрес устройства.
+ - ``hostname`` (Optional[str]): Имя хоста.
+ - ``vendor`` (Optional[str]): Производитель оборудования.
+ - ``open_ports`` (List[str]): Открытые порты.
+ - ``status`` (str): Состояние устройства (online/offline).
+ - ``last_seen`` (Optional[str]): Время последнего обнаружения.
+ - ``note`` (Optional[str]): Пользовательская заметка.
"""
ip: str
@@ -26,22 +25,23 @@ class Device(BaseModel):
last_seen: Optional[str] = None
note: Optional[str] = None
-
class SubnetScanResult(BaseModel):
- """
- Результат сканирования устройств в подсети
+ """Результат сканирования подсети.
Атрибуты:
- devices (List[Device]): Список просканированных устройств
+
+ - ``devices`` (List[Device]): Найденные устройства.
+ - ``error`` (Optional[str]): Сообщение об ошибке сканирования.
"""
devices: List[Device] = []
+ error: Optional[str] = None
-#class SubnetScanRequest(BaseModel):
-# """
-# Параметры для сканирования подсети
-#
-# Атрибуты:
-# subnet (str): Подсеть для сканирования (например 192.168.1.0/24)
-# """
-# subnet: Optional[str] = "192.168.1.0/24"
+class SubnetScanRequest(BaseModel):
+ """Параметры запроса на сканирование подсети.
+
+ Атрибуты:
+
+ - ``subnet`` (Optional[str]): CIDR подсети (например, ``192.168.1.0/24``).
+ """
+ subnet: Optional[str] = None
diff --git a/SRV/models/wifi.py b/SRV/models/wifi.py
index e870026..893bfe1 100644
--- a/SRV/models/wifi.py
+++ b/SRV/models/wifi.py
@@ -3,19 +3,19 @@ from typing import Optional, List
class WifiAccessPoint(BaseModel):
- """
- Модель точки доступа Wi-Fi
+ """Модель Wi-Fi точки доступа.
Атрибуты:
- ssid (Optional[str]): Название точки доступа. Может быть скрытым
- bssid (str): Уникальный идентификатор точки доступа (по типу MAC-адреса)
- channel (Optional[int]): Канал, на котором работает точка доступа
- signal (Optional[int]): Уровень сигнала (dBm)
- encryption (Optional[str]): Тип шифрования (WPA2, WPA3, открытая и т.д.)
- frequency (Optional[str]): Частота вещания (2.4GHz/5GHz)
- vendor (Optional[str]): Производитель точки доступа (определяется по MAC-адресу)
- hidden (bool): Флаг скрытия названия точки доступа (ssid)
- note (Optional[str]): Заметка для точки доступа
+
+ - ``ssid`` (Optional[str]): Название сети (может быть скрытым).
+ - ``bssid`` (str): Уникальный идентификатор точки доступа.
+ - ``channel`` (Optional[int]): Номер канала.
+ - ``signal`` (Optional[int]): Уровень сигнала в dBm.
+ - ``encryption`` (Optional[str]): Тип шифрования (WPA/WPA2/WPA3/открытая).
+ - ``frequency`` (Optional[str]): Диапазон частот (2.4/5 ГГц).
+ - ``vendor`` (Optional[str]): Производитель оборудования.
+ - ``hidden`` (bool): Флаг скрытого SSID.
+ - ``note`` (Optional[str]): Пользовательская заметка.
"""
ssid: Optional[str] = None
@@ -30,11 +30,11 @@ class WifiAccessPoint(BaseModel):
class WifiScanResult(BaseModel):
- """
- Результат сканирования Wi-Fi
+ """Результат сканирования Wi-Fi.
Атрибуты:
- access_points (List[WifiAccessPoint]): Список найденных Wi-Fi точек доступа
+
+ - ``access_points`` (List[WifiAccessPoint]): Найденные точки доступа.
"""
- access_points: List[WifiAccessPoint]
\ No newline at end of file
+ access_points: List[WifiAccessPoint]
diff --git a/SRV/poetry.lock b/SRV/poetry.lock
index 85487e7..5c87602 100644
--- a/SRV/poetry.lock
+++ b/SRV/poetry.lock
@@ -1,15 +1,34 @@
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
+[[package]]
+name = "accessible-pygments"
+version = "0.0.5"
+description = "A collection of accessible pygments styles"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"},
+ {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"},
+]
+
+[package.dependencies]
+pygments = ">=1.5"
+
+[package.extras]
+dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"]
+tests = ["hypothesis", "pytest"]
+
[[package]]
name = "aiofiles"
-version = "24.1.0"
+version = "25.1.0"
description = "File support for asyncio."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"},
- {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
+ {file = "aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695"},
+ {file = "aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2"},
]
[[package]]
@@ -26,103 +45,137 @@ files = [
[[package]]
name = "aiohttp"
-version = "3.12.13"
+version = "3.13.2"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29"},
- {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0"},
- {file = "aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5"},
- {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40"},
- {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6"},
- {file = "aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad"},
- {file = "aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178"},
- {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c"},
- {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358"},
- {file = "aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc"},
- {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2"},
- {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3"},
- {file = "aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd"},
- {file = "aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9"},
- {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73"},
- {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347"},
- {file = "aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6"},
- {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a"},
- {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5"},
- {file = "aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf"},
- {file = "aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e"},
- {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938"},
- {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace"},
- {file = "aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103"},
- {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911"},
- {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3"},
- {file = "aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd"},
- {file = "aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706"},
- {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4"},
- {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1"},
- {file = "aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd"},
- {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055"},
- {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c"},
- {file = "aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8"},
- {file = "aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122"},
- {file = "aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce"},
+ {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"},
+ {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"},
+ {file = "aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802"},
+ {file = "aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f"},
+ {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6"},
+ {file = "aiohttp-3.13.2-cp310-cp310-win32.whl", hash = "sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251"},
+ {file = "aiohttp-3.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514"},
+ {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0"},
+ {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb"},
+ {file = "aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592"},
+ {file = "aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782"},
+ {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8"},
+ {file = "aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec"},
+ {file = "aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c"},
+ {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b"},
+ {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc"},
+ {file = "aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e"},
+ {file = "aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169"},
+ {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248"},
+ {file = "aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e"},
+ {file = "aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45"},
+ {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be"},
+ {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742"},
+ {file = "aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e"},
+ {file = "aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476"},
+ {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23"},
+ {file = "aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254"},
+ {file = "aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a"},
+ {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b"},
+ {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61"},
+ {file = "aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011"},
+ {file = "aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4"},
+ {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a"},
+ {file = "aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940"},
+ {file = "aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734"},
+ {file = "aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f"},
+ {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989"},
+ {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d"},
+ {file = "aiohttp-3.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07"},
+ {file = "aiohttp-3.13.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac"},
+ {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329"},
+ {file = "aiohttp-3.13.2-cp39-cp39-win32.whl", hash = "sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084"},
+ {file = "aiohttp-3.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5"},
+ {file = "aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca"},
]
[package.dependencies]
aiohappyeyeballs = ">=2.5.0"
-aiosignal = ">=1.1.2"
+aiosignal = ">=1.4.0"
attrs = ">=17.3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
@@ -130,22 +183,35 @@ propcache = ">=0.2.0"
yarl = ">=1.17.0,<2.0"
[package.extras]
-speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""]
+speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
[[package]]
name = "aiosignal"
-version = "1.3.2"
+version = "1.4.0"
description = "aiosignal: a list of registered asynchronous callbacks"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"},
- {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"},
+ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"},
+ {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"},
]
[package.dependencies]
frozenlist = ">=1.1.0"
+typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""}
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+description = "A light, configurable Sphinx theme"
+optional = false
+python-versions = ">=3.10"
+groups = ["main", "dev"]
+files = [
+ {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"},
+ {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
+]
[[package]]
name = "annotated-types"
@@ -161,14 +227,14 @@ files = [
[[package]]
name = "anyio"
-version = "4.9.0"
-description = "High level compatibility layer for multiple asynchronous event loop implementations"
+version = "4.11.0"
+description = "High-level concurrency and networking framework on top of asyncio or Trio"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
- {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
+ {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"},
+ {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"},
]
[package.dependencies]
@@ -177,29 +243,34 @@ sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
-doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
-test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
-trio = ["trio (>=0.26.1)"]
+trio = ["trio (>=0.31.0)"]
[[package]]
name = "attrs"
-version = "25.3.0"
+version = "25.4.0"
description = "Classes Without Boilerplate"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
- {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"},
+ {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+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]
-benchmark = ["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-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "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]"]
-dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
-docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"]
-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\""]
+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]]
name = "backports-tarfile"
@@ -283,16 +354,39 @@ files = [
tests = ["pytest (>=3.2.1,!=3.3.0)"]
typecheck = ["mypy"]
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.2"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.7.0"
+groups = ["dev"]
+files = [
+ {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"},
+ {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+typing-extensions = ">=4.0.0"
+
+[package.extras]
+cchardet = ["cchardet"]
+chardet = ["chardet"]
+charset-normalizer = ["charset-normalizer"]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
[[package]]
name = "build"
-version = "1.2.2.post1"
+version = "1.3.0"
description = "A simple, correct Python build frontend"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"},
- {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"},
+ {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"},
+ {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"},
]
[package.dependencies]
@@ -301,22 +395,19 @@ packaging = ">=19.1"
pyproject_hooks = "*"
[package.extras]
-docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"]
-test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"]
-typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"]
uv = ["uv (>=0.1.18)"]
-virtualenv = ["virtualenv (>=20.0.35)"]
+virtualenv = ["virtualenv (>=20.11) ; python_version < \"3.10\"", "virtualenv (>=20.17) ; python_version >= \"3.10\" and python_version < \"3.14\"", "virtualenv (>=20.31) ; python_version >= \"3.14\""]
[[package]]
name = "cachecontrol"
-version = "0.14.3"
+version = "0.14.4"
description = "httplib2 caching for requests"
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"},
- {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"},
+ {file = "cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b"},
+ {file = "cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1"},
]
[package.dependencies]
@@ -325,202 +416,241 @@ msgpack = ">=0.5.2,<2.0.0"
requests = ">=2.16.0"
[package.extras]
-dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"]
+dev = ["cachecontrol[filecache,redis]", "cheroot (>=11.1.2)", "cherrypy", "codespell", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "types-redis", "types-requests"]
filecache = ["filelock (>=3.8.0)"]
redis = ["redis (>=2.10.5)"]
[[package]]
name = "certifi"
-version = "2025.6.15"
+version = "2025.11.12"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
- {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"},
- {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"},
+ {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"},
+ {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"},
]
[[package]]
name = "cffi"
-version = "1.17.1"
+version = "2.0.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
+markers = "platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\""
files = [
- {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
- {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
- {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
- {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
- {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
- {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
- {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
- {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
- {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
- {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
- {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
- {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
- {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
- {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
- {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
- {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
- {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
- {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
- {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
- {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
- {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
- {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
- {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
- {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
- {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
- {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
- {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
- {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
+ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"},
+ {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"},
+ {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"},
+ {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"},
+ {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"},
+ {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"},
+ {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"},
+ {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"},
+ {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"},
+ {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"},
+ {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"},
+ {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"},
+ {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"},
+ {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"},
+ {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"},
+ {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"},
+ {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"},
+ {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"},
+ {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"},
+ {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"},
+ {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"},
+ {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"},
+ {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"},
+ {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"},
+ {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"},
+ {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"},
+ {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"},
+ {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"},
+ {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"},
+ {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"},
+ {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"},
+ {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"},
+ {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"},
+ {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"},
+ {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"},
+ {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"},
+ {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"},
+ {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"},
+ {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"},
+ {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"},
+ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"},
+ {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"},
]
[package.dependencies]
-pycparser = "*"
+pycparser = {version = "*", markers = "implementation_name != \"PyPy\""}
[[package]]
name = "charset-normalizer"
-version = "3.4.2"
+version = "3.4.4"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
- {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
- {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
- {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"},
+ {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"},
+ {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"},
+ {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"},
+ {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"},
+ {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"},
+ {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"},
+ {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"},
+ {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"},
+ {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"},
]
[[package]]
@@ -541,14 +671,14 @@ rapidfuzz = ">=3.0.0,<4.0.0"
[[package]]
name = "click"
-version = "8.2.1"
+version = "8.3.1"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"},
- {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"},
+ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"},
+ {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"},
]
[package.dependencies]
@@ -560,12 +690,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-groups = ["main"]
-markers = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\""
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"", dev = "sys_platform == \"win32\""}
[[package]]
name = "crashtest"
@@ -581,62 +711,79 @@ files = [
[[package]]
name = "cryptography"
-version = "45.0.5"
+version = "46.0.3"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
-python-versions = "!=3.9.0,!=3.9.1,>=3.7"
+python-versions = "!=3.9.0,!=3.9.1,>=3.8"
groups = ["main"]
files = [
- {file = "cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9"},
- {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27"},
- {file = "cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e"},
- {file = "cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174"},
- {file = "cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9"},
- {file = "cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63"},
- {file = "cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42"},
- {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492"},
- {file = "cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0"},
- {file = "cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a"},
- {file = "cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f"},
- {file = "cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e"},
- {file = "cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1"},
- {file = "cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f"},
- {file = "cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a"},
+ {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"},
+ {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"},
+ {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"},
+ {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"},
+ {file = "cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"},
+ {file = "cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"},
+ {file = "cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"},
+ {file = "cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"},
+ {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"},
+ {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"},
+ {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"},
+ {file = "cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"},
+ {file = "cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"},
+ {file = "cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"},
+ {file = "cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"},
+ {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"},
+ {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"},
+ {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"},
+ {file = "cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"},
+ {file = "cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"},
+ {file = "cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"},
+ {file = "cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"},
+ {file = "cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"},
+ {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"},
+ {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"},
]
[package.dependencies]
-cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""}
+cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""}
[package.extras]
-docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""]
+docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
-nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""]
-pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
+nox = ["nox[uv] (>=2024.4.15)"]
+pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"]
sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"]
-test = ["certifi (>=2024)", "cryptography-vectors (==45.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
+test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test-randomorder = ["pytest-randomly"]
[[package]]
@@ -656,102 +803,103 @@ test = ["tap.py"]
[[package]]
name = "distlib"
-version = "0.3.9"
+version = "0.4.0"
description = "Distribution utilities"
optional = false
python-versions = "*"
groups = ["main"]
files = [
- {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
- {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
+ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"},
+ {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"},
]
[[package]]
name = "dnspython"
-version = "2.7.0"
+version = "2.8.0"
description = "DNS toolkit"
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"},
- {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"},
+ {file = "dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af"},
+ {file = "dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f"},
]
[package.extras]
-dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
-dnssec = ["cryptography (>=43)"]
-doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
-doq = ["aioquic (>=1.0.0)"]
-idna = ["idna (>=3.7)"]
-trio = ["trio (>=0.23)"]
-wmi = ["wmi (>=1.5.1)"]
+dev = ["black (>=25.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.17.0)", "mypy (>=1.17)", "pylint (>=3)", "pytest (>=8.4)", "pytest-cov (>=6.2.0)", "quart-trio (>=0.12.0)", "sphinx (>=8.2.0)", "sphinx-rtd-theme (>=3.0.0)", "twine (>=6.1.0)", "wheel (>=0.45.0)"]
+dnssec = ["cryptography (>=45)"]
+doh = ["h2 (>=4.2.0)", "httpcore (>=1.0.0)", "httpx (>=0.28.0)"]
+doq = ["aioquic (>=1.2.0)"]
+idna = ["idna (>=3.10)"]
+trio = ["trio (>=0.30)"]
+wmi = ["wmi (>=1.5.1) ; platform_system == \"Windows\""]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
+ {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
+]
[[package]]
name = "dulwich"
-version = "0.22.8"
+version = "0.24.10"
description = "Python Git Library"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127"},
- {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f"},
- {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8318bc0921d42e3e69f03716f983a301b5ee4c8dc23c7f2c5bbb28581257a9"},
- {file = "dulwich-0.22.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7a0f96a2a87f3b4f7feae79d2ac6b94107d6b7d827ac08f2f331b88c8f597a1"},
- {file = "dulwich-0.22.8-cp310-cp310-win32.whl", hash = "sha256:432a37b25733202897b8d67cdd641688444d980167c356ef4e4dd15a17a39a24"},
- {file = "dulwich-0.22.8-cp310-cp310-win_amd64.whl", hash = "sha256:f3a15e58dac8b8a76073ddca34e014f66f3672a5540a99d49ef6a9c09ab21285"},
- {file = "dulwich-0.22.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0852edc51cff4f4f62976bdaa1d82f6ef248356c681c764c0feb699bc17d5782"},
- {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:826aae8b64ac1a12321d6b272fc13934d8f62804fda2bc6ae46f93f4380798eb"},
- {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ae726f923057d36cdbb9f4fb7da0d0903751435934648b13f1b851f0e38ea1"},
- {file = "dulwich-0.22.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6987d753227f55cf75ba29a8dab69d1d83308ce483d7a8c6d223086f7a42e125"},
- {file = "dulwich-0.22.8-cp311-cp311-win32.whl", hash = "sha256:7757b4a2aad64c6f1920082fc1fccf4da25c3923a0ae7b242c08d06861dae6e1"},
- {file = "dulwich-0.22.8-cp311-cp311-win_amd64.whl", hash = "sha256:12b243b7e912011c7225dc67480c313ac8d2990744789b876016fb593f6f3e19"},
- {file = "dulwich-0.22.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d81697f74f50f008bb221ab5045595f8a3b87c0de2c86aa55be42ba97421f3cd"},
- {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bff1da8e2e6a607c3cb45f5c2e652739589fe891245e1d5b770330cdecbde41"},
- {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9969099e15b939d3936f8bee8459eaef7ef5a86cd6173393a17fe28ca3d38aff"},
- {file = "dulwich-0.22.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:017152c51b9a613f0698db28c67cf3e0a89392d28050dbf4f4ac3f657ea4c0dc"},
- {file = "dulwich-0.22.8-cp312-cp312-win32.whl", hash = "sha256:ee70e8bb8798b503f81b53f7a103cb869c8e89141db9005909f79ab1506e26e9"},
- {file = "dulwich-0.22.8-cp312-cp312-win_amd64.whl", hash = "sha256:dc89c6f14dcdcbfee200b0557c59ae243835e42720be143526d834d0e53ed3af"},
- {file = "dulwich-0.22.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbade3342376be1cd2409539fe1b901d2d57a531106bbae204da921ef4456a74"},
- {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71420ffb6deebc59b2ce875e63d814509f9c1dc89c76db962d547aebf15670c7"},
- {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a626adbfac44646a125618266a24133763bdc992bf8bd0702910d67e6b994443"},
- {file = "dulwich-0.22.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1476c9c4e4ede95714d06c4831883a26680e37b040b8b6230f506e5ba39f51"},
- {file = "dulwich-0.22.8-cp313-cp313-win32.whl", hash = "sha256:b2b31913932bb5bd41658dd398b33b1a2d4d34825123ad54e40912cfdfe60003"},
- {file = "dulwich-0.22.8-cp313-cp313-win_amd64.whl", hash = "sha256:7a44e5a61a7989aca1e301d39cfb62ad2f8853368682f524d6e878b4115d823d"},
- {file = "dulwich-0.22.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9cd0c67fb44a38358b9fcabee948bf11044ef6ce7a129e50962f54c176d084e"},
- {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b79b94726c3f4a9e5a830c649376fd0963236e73142a4290bac6bc9fc9cb120"},
- {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16bbe483d663944972e22d64e1f191201123c3b5580fbdaac6a4f66bfaa4fc11"},
- {file = "dulwich-0.22.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e02d403af23d93dc1f96eb2408e25efd50046e38590a88c86fa4002adc9849b0"},
- {file = "dulwich-0.22.8-cp39-cp39-win32.whl", hash = "sha256:8bdd9543a77fb01be704377f5e634b71f955fec64caa4a493dc3bfb98e3a986e"},
- {file = "dulwich-0.22.8-cp39-cp39-win_amd64.whl", hash = "sha256:3b6757c6b3ba98212b854a766a4157b9cb79a06f4e1b06b46dec4bd834945b8e"},
- {file = "dulwich-0.22.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7bb18fa09daa1586c1040b3e2777d38d4212a5cdbe47d384ba66a1ac336fcc4c"},
- {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2fda8e87907ed304d4a5962aea0338366144df0df60f950b8f7f125871707f"},
- {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1748cd573a0aee4d530bc223a23ccb8bb5b319645931a37bd1cfb68933b720c1"},
- {file = "dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a631b2309feb9a9631eabd896612ba36532e3ffedccace57f183bb868d7afc06"},
- {file = "dulwich-0.22.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:00e7d9a3d324f9e0a1b27880eec0e8e276ff76519621b66c1a429ca9eb3f5a8d"},
- {file = "dulwich-0.22.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f8aa3de93201f9e3e40198725389aa9554a4ee3318a865f96a8e9bc9080f0b25"},
- {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e8da9dd8135884975f5be0563ede02179240250e11f11942801ae31ac293f37"},
- {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc5ce2435fb3abdf76f1acabe48f2e4b3f7428232cadaef9daaf50ea7fa30ee"},
- {file = "dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982b21cc3100d959232cadb3da0a478bd549814dd937104ea50f43694ec27153"},
- {file = "dulwich-0.22.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6bde2b13a05cc0ec2ecd4597a99896663544c40af1466121f4d046119b874ce3"},
- {file = "dulwich-0.22.8-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6d446cb7d272a151934ad4b48ba691f32486d5267cf2de04ee3b5e05fc865326"},
- {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f6338e6cf95cd76a0191b3637dc3caed1f988ae84d8e75f876d5cd75a8dd81a"},
- {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e004fc532ea262f2d5f375068101ca4792becb9d4aa663b050f5ac31fda0bb5c"},
- {file = "dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bfdbc6fa477dee00d04e22d43a51571cd820cfaaaa886f0f155b8e29b3e3d45"},
- {file = "dulwich-0.22.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae900c8e573f79d714c1d22b02cdadd50b64286dd7203028f0200f82089e4950"},
- {file = "dulwich-0.22.8-py3-none-any.whl", hash = "sha256:ffc7a02e62b72884de58baaa3b898b7f6427893e79b1289ffa075092efe59181"},
- {file = "dulwich-0.22.8.tar.gz", hash = "sha256:701547310415de300269331abe29cb5717aa1ea377af826bf513d0adfb1c209b"},
+ {file = "dulwich-0.24.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1f511f7afe1f36e37193214e4e069685d7d0378e756cc96a2fcb138bdf9fefca"},
+ {file = "dulwich-0.24.10-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2a56f9838e5d2414a2b57bab370b73b9803fefd98836ef841f0fd489b5cc1349"},
+ {file = "dulwich-0.24.10-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:90b24c0827299cfb53c4f4d4fedc811be5c4b10c11172ff6e5a5c52277fe0b3a"},
+ {file = "dulwich-0.24.10-cp310-cp310-win32.whl", hash = "sha256:0dfae8c59b97964a907fdf4c5809154a18fd8c55f2eb6d8fd1607464165a9aa2"},
+ {file = "dulwich-0.24.10-cp310-cp310-win_amd64.whl", hash = "sha256:0e1601789554e3d15b294356c78a5403521c27d5460e64dbbc44ffd5b10af4c3"},
+ {file = "dulwich-0.24.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf94fa73211d2f029751a72e1ca3a2fd35c6f5d9bb434acdf10a4a79ca322dd"},
+ {file = "dulwich-0.24.10-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:b715a9f85ed71bef8027275c1bded064e4925071ae8c8a8d9a20c67b31faf3cd"},
+ {file = "dulwich-0.24.10-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:858fae0c7121715282a993abb1919385a28e1a9c4f136f568748d283c2ba874f"},
+ {file = "dulwich-0.24.10-cp311-cp311-win32.whl", hash = "sha256:393e9c3cdd382cff20b5beb66989376d6da69e3b0dfec046a884707ab5d27ac9"},
+ {file = "dulwich-0.24.10-cp311-cp311-win_amd64.whl", hash = "sha256:470d6cd8207e1a5ff1fb34c4c6fac2ec9a96d618f7062e5fb96c5260927bb9a7"},
+ {file = "dulwich-0.24.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c724e5fc67c45f3c813f2630795ac388e3e6310534212f799a7a6bf230648c8"},
+ {file = "dulwich-0.24.10-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6a25ca1605a94090514af408f9df64427281aefbb726f542e97d86d3a7c8ec18"},
+ {file = "dulwich-0.24.10-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d9793fc1e42149a650a017dc8ce38485368a41729b9937e1dfcfedd0591ebe9d"},
+ {file = "dulwich-0.24.10-cp312-cp312-win32.whl", hash = "sha256:1601bfea3906b52c924fae5b6ba32a0b087fb8fae927607e6b5381e6f7559611"},
+ {file = "dulwich-0.24.10-cp312-cp312-win_amd64.whl", hash = "sha256:f7bfa9f0bfae57685754b163eef6641609047460939d28052e3beeb63efa6795"},
+ {file = "dulwich-0.24.10-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:843de5f678436a27b33aea0f2b87fd0453afdd0135f885a3ca44bc3147846dd2"},
+ {file = "dulwich-0.24.10-cp313-cp313-android_21_x86_64.whl", hash = "sha256:4914abb6408a719b7a1f7d9a182d1efd92c326e178b440faf582df50f9f032db"},
+ {file = "dulwich-0.24.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ce6e05ec50f258ccd14d83114eb32cc5bb241ae4a8c7199d014fd7568de285b1"},
+ {file = "dulwich-0.24.10-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3581ae0af33f28e6c0834d2f41ca67ca81cd92a589e6a5f985e6c64373232958"},
+ {file = "dulwich-0.24.10-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:019af16c850ae85254289f9633a29dea02f45351c4182ea20b0c1394c074a13b"},
+ {file = "dulwich-0.24.10-cp313-cp313-win32.whl", hash = "sha256:4b5c225477a529e1d4a2b5e51272a418177e34803938391ce41b7573b2e5b0d0"},
+ {file = "dulwich-0.24.10-cp313-cp313-win_amd64.whl", hash = "sha256:752c32d517dc608dbb8414061eaaec8ac8a05591b29531f81a83336b018b26c6"},
+ {file = "dulwich-0.24.10-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:44f62e0244531a8c43ca7771e201ec9e7f6a2fb27f8c3c623939bc03c1f50423"},
+ {file = "dulwich-0.24.10-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e2eda4a634d6f1ac4c0d4786f8772495c8840dfc2b3e595507376bf5e5b0f9c5"},
+ {file = "dulwich-0.24.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b19af8a3ab051003ba05f15fc5c0d6f0d427e795639490790f34ec0558e99e3"},
+ {file = "dulwich-0.24.10-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:90028182b9a47ea4efed51c81298f3a98e279d7bf5c1f91c47101927a309ee45"},
+ {file = "dulwich-0.24.10-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:8df79c8080471f363e4dfcfc4e0d2e61e6da73af1fd7d31cb6ae0d34af45a6b4"},
+ {file = "dulwich-0.24.10-cp39-cp39-win32.whl", hash = "sha256:f102c38207540fa485e85e0b763ce3725a2d49d846dbf316ed271e27fd85ff21"},
+ {file = "dulwich-0.24.10-cp39-cp39-win_amd64.whl", hash = "sha256:c262ffc94338999e7808b434dccafaccd572d03b42d4ef140059d4b7cad765a3"},
+ {file = "dulwich-0.24.10-py3-none-any.whl", hash = "sha256:15b32f8c3116a1c0a042dde8da96f65a607e263e860ee42b3d4a98ce2c2f4a06"},
+ {file = "dulwich-0.24.10.tar.gz", hash = "sha256:30e028979b6fa7220c913da9c786026611c10746c06496149742602b36a11f6b"},
]
[package.dependencies]
-urllib3 = ">=1.25"
+typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.12\""}
+urllib3 = ">=2.2.2"
[package.extras]
-dev = ["mypy (==1.15.0)", "ruff (==0.9.7)"]
+colordiff = ["rich"]
+dev = ["codespell (==2.4.1)", "dissolve (>=0.1.1)", "mypy (==1.18.2)", "ruff (==0.13.2)"]
fastimport = ["fastimport"]
-https = ["urllib3 (>=1.24.1)"]
+fuzzing = ["atheris"]
+https = ["urllib3 (>=2.2.2)"]
+merge = ["merge3"]
paramiko = ["paramiko"]
+patiencediff = ["patiencediff"]
pgp = ["gpg"]
[[package]]
@@ -775,14 +923,14 @@ gmpy2 = ["gmpy2"]
[[package]]
name = "email-validator"
-version = "2.2.0"
+version = "2.3.0"
description = "A robust email address syntax and deliverability validation library."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
- {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
+ {file = "email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4"},
+ {file = "email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426"},
]
[package.dependencies]
@@ -818,34 +966,225 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt
[[package]]
name = "fastapi-cli"
-version = "0.0.7"
+version = "0.0.16"
description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4"},
- {file = "fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e"},
+ {file = "fastapi_cli-0.0.16-py3-none-any.whl", hash = "sha256:addcb6d130b5b9c91adbbf3f2947fe115991495fdb442fe3e51b5fc6327df9f4"},
+ {file = "fastapi_cli-0.0.16.tar.gz", hash = "sha256:e8a2a1ecf7a4e062e3b2eec63ae34387d1e142d4849181d936b23c4bdfe29073"},
]
[package.dependencies]
-rich-toolkit = ">=0.11.1"
+fastapi-cloud-cli = {version = ">=0.1.1", optional = true, markers = "extra == \"standard\""}
+rich-toolkit = ">=0.14.8"
+typer = ">=0.15.1"
+uvicorn = {version = ">=0.15.0", extras = ["standard"]}
+
+[package.extras]
+standard = ["fastapi-cloud-cli (>=0.1.1)", "uvicorn[standard] (>=0.15.0)"]
+standard-no-fastapi-cloud-cli = ["uvicorn[standard] (>=0.15.0)"]
+
+[[package]]
+name = "fastapi-cloud-cli"
+version = "0.5.2"
+description = "Deploy and manage FastAPI Cloud apps from the command line 🚀"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "fastapi_cloud_cli-0.5.2-py3-none-any.whl", hash = "sha256:783ab5e41baf8afcbbfb7e513ac5bdf7376202053b8bf2d158a38978dba1ca69"},
+ {file = "fastapi_cloud_cli-0.5.2.tar.gz", hash = "sha256:34d04ffadb2562c3ebb39e6f5a599c47353750f75a2ef0a2d9c1442ed09f3308"},
+]
+
+[package.dependencies]
+fastar = ">=0.5.0"
+httpx = ">=0.27.0"
+pydantic = {version = ">=1.6.1", extras = ["email"]}
+rich-toolkit = ">=0.14.5"
+rignore = ">=0.5.1"
+sentry-sdk = ">=2.20.0"
typer = ">=0.12.3"
uvicorn = {version = ">=0.15.0", extras = ["standard"]}
[package.extras]
standard = ["uvicorn[standard] (>=0.15.0)"]
+[[package]]
+name = "fastar"
+version = "0.8.0"
+description = "High-level bindings for the Rust tar crate"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "fastar-0.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c9f930cff014cf79d396d0541bd9f3a3f170c9b5e45d10d634d98f9ed08788c3"},
+ {file = "fastar-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07b70f712d20622346531a4b46bb332569bea621f61314c0b7e80903a16d14cf"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:330639db3bfba4c6d132421a2a4aeb81e7bea8ce9159cdb6e247fbc5fae97686"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ea7ceb6231e48d7bb0d7dc13e946baa29c7f6873eaf4afb69725d6da349033"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a90695a601a78bbca910fdf2efcdf3103c55d0de5a5c6e93556d707bf886250b"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d0bf655ff4c9320b0ca8a5b128063d5093c0c8c1645a2b5f7167143fd8531aa"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8df22cdd8d58e7689aa89b2e4a07e8e5fa4f88d2d9c2621f0e88a49be97ccea"},
+ {file = "fastar-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5e6ad722685128521c8fb44cf25bd38669650ba3a4b466b8903e5aa28e1a0"},
+ {file = "fastar-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:31cd541231a2456e32104da891cf9962c3b40234d0465cbf9322a6bc8a1b05d5"},
+ {file = "fastar-0.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:175db2a98d67ced106468e8987975484f8bbbd5ad99201da823b38bafb565ed5"},
+ {file = "fastar-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada877ab1c65197d772ce1b1c2e244d4799680d8b3f136a4308360f3d8661b23"},
+ {file = "fastar-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:01084cb75f13ca6a8e80bd41584322523189f8e81b472053743d6e6c3062b5a6"},
+ {file = "fastar-0.8.0-cp310-cp310-win32.whl", hash = "sha256:ca639b9909805e44364ea13cca2682b487e74826e4ad75957115ec693228d6b6"},
+ {file = "fastar-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:fbc0f2ed0f4add7fb58034c576584d44d7eaaf93dee721dfb26dbed6e222dbac"},
+ {file = "fastar-0.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cd9c0d3ebf7a0a6f642f771cf41b79f7c98d40a3072a8abe1174fbd9bd615bd3"},
+ {file = "fastar-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2875a077340fe4f8099bd3ed8fa90d9595e1ac3cd62ae19ab690d5bf550eeb35"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a999263d9f87184bf2801833b2ecf105e03c0dd91cac78685673b70da564fd64"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c41111da56430f638cbfc498ebdcc7d30f63416e904b27b7695c29bd4889cb8"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3719541a12bb09ab1eae91d2c987a9b2b7d7149c52e7109ba6e15b74aabc49b1"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9b0fff8079b18acdface7ef1b7f522fd9a589f65ca4a1a0dd7c92a0886c2a2"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac073576c1931959191cb20df38bab21dd152f66c940aa3ca8b22e39f753b2f3"},
+ {file = "fastar-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:003b59a7c3e405b6a7bff8fab17d31e0ccbc7f06730a8f8ca1694eeea75f3c76"},
+ {file = "fastar-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a7b96748425efd9fc155cd920d65088a1b0d754421962418ea73413d02ff515a"},
+ {file = "fastar-0.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:90957a30e64418b02df5b4d525bea50403d98a4b1f29143ce5914ddfa7e54ee4"},
+ {file = "fastar-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f6e784a8015623fbb7ccca1af372fd82cb511b408ddd2348dc929fc6e415df73"},
+ {file = "fastar-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a03eaf287bbc93064688a1220580ce261e7557c8898f687f4d0b281c85b28d3c"},
+ {file = "fastar-0.8.0-cp311-cp311-win32.whl", hash = "sha256:661a47ed90762f419406c47e802f46af63a08254ba96abd1c8191e4ce967b665"},
+ {file = "fastar-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b48abd6056fef7bc3d414aafb453c5b07fdf06d2df5a2841d650288a3aa1e9d3"},
+ {file = "fastar-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:50c18788b3c6ffb85e176dcb8548bb8e54616a0519dcdbbfba66f6bbc4316933"},
+ {file = "fastar-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f10d2adfe40f47ff228f4efaa32d409d732ded98580e03ed37c9535b5fc923d"},
+ {file = "fastar-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b930da9d598e3bc69513d131f397e6d6be4643926ef3de5d33d1e826631eb036"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9d210da2de733ca801de83e931012349d209f38b92d9630ccaa94bd445bdc9b8"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa02270721517078a5bd61a38719070ac2537a4aa6b6c48cf369cf2abc59174a"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83c391e5b789a720e4d0029b9559f5d6dee3226693c5b39c0eab8eaece997e0f"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3258d7a78a72793cdd081545da61cabe85b1f37634a1d0b97ffee0ff11d105ef"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6eab95dd985cdb6a50666cbeb9e4814676e59cfe52039c880b69d67cfd44767"},
+ {file = "fastar-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:829b1854166141860887273c116c94e31357213fa8e9fe8baeb18bd6c38aa8d9"},
+ {file = "fastar-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1667eae13f9457a3c737f4376d68e8c3e548353538b28f7e4273a30cb3965cd"},
+ {file = "fastar-0.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b864a95229a7db0814cd9ef7987cb713fd43dce1b0d809dd17d9cd6f02fdde3e"},
+ {file = "fastar-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c05fbc5618ce17675a42576fa49858d79734627f0a0c74c0875ab45ee8de340c"},
+ {file = "fastar-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7f41c51ee96f338662ee3c3df4840511ba3f9969606840f1b10b7cb633a3c716"},
+ {file = "fastar-0.8.0-cp312-cp312-win32.whl", hash = "sha256:d949a1a2ea7968b734632c009df0571c94636a5e1622c87a6e2bf712a7334f47"},
+ {file = "fastar-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc645994d5b927d769121094e8a649b09923b3c13a8b0b98696d8f853f23c532"},
+ {file = "fastar-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:d81ee82e8dc78a0adb81728383bd39611177d642a8fa2d601d4ad5ad59e5f3bd"},
+ {file = "fastar-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a3253a06845462ca2196024c7a18f5c0ba4de1532ab1c4bad23a40b332a06a6a"},
+ {file = "fastar-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5cbeb3ebfa0980c68ff8b126295cc6b208ccd81b638aebc5a723d810a7a0e5d2"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1c0d5956b917daac77d333d48b3f0f3ff927b8039d5b32d8125462782369f761"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27b404db2b786b65912927ce7f3790964a4bcbde42cdd13091b82a89cd655e1c"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0902fc89dcf1e7f07b8563032a4159fe2b835e4c16942c76fd63451d0e5f76a3"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:069347e2f0f7a8b99bbac8cd1bc0e06c7b4a31dc964fc60d84b95eab3d869dc1"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd135306f6bfe9a835918280e0eb440b70ab303e0187d90ab51ca86e143f70d"},
+ {file = "fastar-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d06d6897f43c27154b5f2d0eb930a43a81b7eec73f6f0b0114814d4a10ab38"},
+ {file = "fastar-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a922f8439231fa0c32b15e8d70ff6d415619b9d40492029dabbc14a0c53b5f18"},
+ {file = "fastar-0.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a739abd51eb766384b4caff83050888e80cd75bbcfec61e6d1e64875f94e4a40"},
+ {file = "fastar-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a65f419d808b23ac89d5cd1b13a2f340f15bc5d1d9af79f39fdb77bba48ff1b"},
+ {file = "fastar-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7bb2ae6c0cce58f0db1c9f20495e7557cca2c1ee9c69bbd90eafd54f139171c5"},
+ {file = "fastar-0.8.0-cp313-cp313-win32.whl", hash = "sha256:b28753e0d18a643272597cb16d39f1053842aa43131ad3e260c03a2417d38401"},
+ {file = "fastar-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:620e5d737dce8321d49a5ebb7997f1fd0047cde3512082c27dc66d6ac8c1927a"},
+ {file = "fastar-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:c4c4bd08df563120cd33e854fe0a93b81579e8571b11f9b7da9e84c37da2d6b6"},
+ {file = "fastar-0.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:50b36ce654ba44b0e13fae607ae17ee6e1597b69f71df1bee64bb8328d881dfc"},
+ {file = "fastar-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63a892762683d7ab00df0227d5ea9677c62ff2cde9b875e666c0be569ed940f3"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4ae6a145c1bff592644bde13f2115e0239f4b7babaf506d14e7d208483cf01a5"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae0ff7c0a1c7e1428404b81faee8aebef466bfd0be25bfe4dabf5d535c68741"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dbfd87dbd217b45c898b2dbcd0169aae534b2c1c5cbe3119510881f6a5ac8ef5"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5abd99fcba83ef28c8fe6ae2927edc79053db43a0457a962ed85c9bf150d37"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91d4c685620c3a9d6b5ae091dbabab4f98b20049b7ecc7976e19cc9016c0d5d6"},
+ {file = "fastar-0.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77c2f2cad76e9dc7b6701297adb1eba87d0485944b416fc2ccf5516c01219a3"},
+ {file = "fastar-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e7f07c4a3dada7757a8fc430a5b4a29e6ef696d2212747213f57086ffd970316"},
+ {file = "fastar-0.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:90c0c3fe55105c0aed8a83135dbdeb31e683455dbd326a1c48fa44c378b85616"},
+ {file = "fastar-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fb9ee51e5bffe0dab3d3126d3a4fac8d8f7235cedcb4b8e74936087ce1c157f3"},
+ {file = "fastar-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e380b1e8d30317f52406c43b11e98d11e1d68723bbd031e18049ea3497b59a6d"},
+ {file = "fastar-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1c4ffc06e9c4a8ca498c07e094670d8d8c0d25b17ca6465b9774da44ea997ab1"},
+ {file = "fastar-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:5517a8ad4726267c57a3e0e2a44430b782e00b230bf51c55b5728e758bb3a692"},
+ {file = "fastar-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:58030551046ff4a8616931e52a36c83545ff05996db5beb6e0cd2b7e748aa309"},
+ {file = "fastar-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:1e7d29b6bfecb29db126a08baf3c04a5ab667f6cea2b7067d3e623a67729c4a6"},
+ {file = "fastar-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05eb7b96940f9526b485f1d0b02393839f0f61cac4b1f60024984f8b326d2640"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:619352d8ac011794e2345c462189dc02ba634750d23cd9d86a9267dd71b1f278"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74ebfecef3fe6d7a90355fac1402fd30636988332a1d33f3e80019a10782bb24"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2975aca5a639e26a3ab0d23b4b0628d6dd6d521146c3c11486d782be621a35aa"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afc438eaed8ff0dcdd9308268be5cb38c1db7e94c3ccca7c498ca13a4a4535a3"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ced0a5399cc0a84a858ef0a31ca2d0c24d3bbec4bcda506a9192d8119f3590a"},
+ {file = "fastar-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec9b23da8c4c039da3fe2e358973c66976a0c8508aa06d6626b4403cb5666c19"},
+ {file = "fastar-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dfba078fcd53478032fd0ceed56960ec6b7ff0511cfc013a8a3a4307e3a7bac4"},
+ {file = "fastar-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ade56c94c14be356d295fecb47a3fcd473dd43a8803ead2e2b5b9e58feb6dcfa"},
+ {file = "fastar-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e48d938f9366db5e59441728f70b7f6c1ccfab7eff84f96f9b7e689b07786c52"},
+ {file = "fastar-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:79c441dc1482ff51a54fb3f57ae6f7bb3d2cff88fa2cc5d196c519f8aab64a56"},
+ {file = "fastar-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:187f61dc739afe45ac8e47ed7fd1adc45d52eac110cf27d579155720507d6fbe"},
+ {file = "fastar-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40e9d763cf8bf85ce2fa256e010aa795c0fe3d3bd1326d5c3084e6ce7857127e"},
+ {file = "fastar-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e59673307b6a08210987059a2bdea2614fe26e3335d0e5d1a3d95f49a05b1418"},
+ {file = "fastar-0.8.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5793b5db86ff0d588057b9089bf904a9ac288de0323a9973452a011a48ec23eb"},
+ {file = "fastar-0.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3cb073ab1905127ab6e052a5c7ccd409557ef086571f27de938764d3eaadfe07"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2716309f7326224b9f1341077d8c65ebb26335e5c93c409e1a23be03f1a01c50"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5ea223170ee6eb1eaf25ff8193df66a939c891f85a9a33def3add9df2ee1232"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5f24c6c3628faa3ee51df54d77dbf47c4f77a1951ea4ea14e4ccb855babced5"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d98894e4c3a2178f33f695940a615376728f6109f1a3431ac0a9fe98fe84ec7"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77d7016f446678d44f1823f40a947db741643fa328142dac6f181046ba205b01"},
+ {file = "fastar-0.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e88a80b40b7f929a7719a13d7332b4cb1344c5a1ac497044bd24f2adadf04c4"},
+ {file = "fastar-0.8.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7b8eb42f346024df3800d078fc0763275b1964d5d0762aa831bb0b539b5f1ee3"},
+ {file = "fastar-0.8.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:41527617a8b592a29fa874e4dba305874b150601e2bf2e17a9f8099a9d179f28"},
+ {file = "fastar-0.8.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:feb8f73ad25ad84f986dc53e7c6561b281ee2087500f6e400899c3bf1a3f6dc0"},
+ {file = "fastar-0.8.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:afbdc2e87b7e56e11ad330859fe17d7a93a76cd637d7f33d1c9edd566d2f58d9"},
+ {file = "fastar-0.8.0-cp38-cp38-win32.whl", hash = "sha256:1ccc1610c05183c0ff82fe93cdbc4eb0ea8b11f2f6d94f6d31ae342164fc6033"},
+ {file = "fastar-0.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96abf34135cffb9652360cd827bda19855b803038d932dcd2a686b3d4e7e1ce"},
+ {file = "fastar-0.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:923afc2db5192e56e71952a88e3fe5965c7c9c910d385d2db7573136f064f2fa"},
+ {file = "fastar-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbe775356930f3aab0ce709fdf8ecf90c10882f5bbdcea215c89a3b14090c50"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ff516154e77f4bf78c31a0c11aa78a8a80e11b6964ec6f28982e42ffcbb543c"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2fdd1c987ff2300bdf39baed556f8e155f8577018775e794a268ecf1707610"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d80e4dad8ee2362a71870b1e735800bb5e97f12ebbee4bd0cf15a81ad2428b5a"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a17abee1febf5363ed2633f5e13de4be481ba1ab5f77860d39470eccdc4b65af"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64cbde8e0ece3d799090a4727f936f66c5990d3ac59416f3de76a2c676e8e568"},
+ {file = "fastar-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63d98b26590d293a9d9a379bae88367a8f3a6137c28819ed6dd6e11aca4a5c6e"},
+ {file = "fastar-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf440983d4d64582bddf2f0bd3c43ea1db93a8c31cf7c20e473bffaf6d9c0b6d"},
+ {file = "fastar-0.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1d90cbf984a39afda27afe08e40c2d8eddc49c5e80590af641610c7b6dc20161"},
+ {file = "fastar-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ca0db5e563d84b639fe15385eeca940777b6d2f0a1f3bb7cd5b55ab7124f0554"},
+ {file = "fastar-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:42ff3052d74684a636423d4f040db88eebd4caf20842fa5f06020e0130c01f69"},
+ {file = "fastar-0.8.0-cp39-cp39-win32.whl", hash = "sha256:15e3dfaa769d2117ef707e5f47c62126d1b63f8e9c85133112f33f1fbdf8942f"},
+ {file = "fastar-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:5153aa1c194316d0f67b6884a62d122d51fce4196263e92e4bca2a6c47cd44c0"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2127cf2e80ffd49744a160201e0e2f55198af6c028a7b3f750026e0b1f1caa4e"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ff85094f10003801339ac4fa9b20a3410c2d8f284d4cba2dc99de6e98c877812"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3dbca235f0bd804cca6602fe055d3892bebf95fb802e6c6c7d872fb10f7abc6c"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e54bfdee6c81a0005e147319e93d8797f442308032c92fa28d03ef8fda076"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a78e5221b94a80800930b7fd0d0e797ae73aadf7044c05ed46cb9bdf870f022"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997092d31ff451de8d0568f6773f3517cb87dcd0bc76184edb65d7154390a6f8"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:558e8fcf8fe574541df5db14a46cd98bfbed14a811b7014a54f2b714c0cfac42"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d2a54f87e2908cc19e1a6ee249620174fbefc54a219aba1eaa6f31657683c3"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:ef94901537be277f9ec59db939eb817960496c6351afede5b102699b5098604d"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:0afbb92f78bf29d5e9db76fb46cbabc429e49015cddf72ab9e761afbe88ac100"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fb59c7925e7710ad178d9e1a3e65edf295d9a042a0cdcb673b4040949eb8ad0a"},
+ {file = "fastar-0.8.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e6c4d6329da568ec36b1347b0c09c4d27f9dfdeddf9f438ddb16799ecf170098"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:998e3fa4b555b63eb134e6758437ed739ad1652fdd2a61dfe1dacbfddc35fe66"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f83e60d845091f3a12bc37f412774264d161576eaf810ed8b43567eb934b7e5"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:299672e1c74d8b73c61684fac9159cfc063d35f4b165996a88facb0e26862cb5"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d3a27066b84d015deab5faee78565509bb33b137896443e4144cb1be1a5f90"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef0bcf4385bbdd3c1acecce2d9ea7dab7cc9b8ee0581bbccb7ab11908a7ce288"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f10ef62b6eda6cb6fd9ba8e1fe08a07d7b2bdcc8eaa00eb91566143b92ed7eee"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4f6c82a8ee98c17aa48585ee73b51c89c1b010e5c951af83e07c3436180e3fc"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6129067fcb86276635b5857010f4e9b9c7d5d15dd571bb03c6c1ed73c40fd92"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4cc9e77019e489f1ddac446b6a5b9dfb5c3d9abd142652c22a1d9415dbcc0e47"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:382bfe82c026086487cb17fee12f4c1e2b4e67ce230f2e04487d3e7ddfd69031"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:908d2b9a1ff3d549cc304b32f95706a536da8f0bcb0bc0f9e4c1cce39b80e218"},
+ {file = "fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1aa7dbde2d2d73eb5b6203d0f74875cb66350f0f1b4325b4839fc8fbbf5d074e"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:284036bae786a520456ad3f58e72aaf1bd5d74e309132e568343564daa4ae383"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5aba0942b4f56acdb8fa8aa7cb506f70c1a17bf13dcab318a17ffb467cb2e7ec"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:52eda6230799db7bbd44461c622161e9bcd43603399da19b0daab2782e0030b0"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f860566b9f3cb1900980f46a4c3f003990c0009c11730f988f758542c17a2364"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78f3fe5f45437c66d1dbece5f31aa487e48ef46d76b2082b873d5fa18013ebe1"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82bc445202bbc53f067bb15e3b8639f01fd54d3096a0f9601240690cfd7c9684"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b1208b5453cfe7192e54765f73844b80d684bd8dc6d6acbbb60ead42590b13e"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8922754c66699e27d4f1ce07c9c256228054cdc9bb36363e8bb5b503453a6da"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:92cad46dfbb9969359823c9f61165ec32d5d675d86e863889416e9b64efea95c"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f4eb9560a447ff6a4b377f08b6e5d3a31909a612b028f2c57810ffaf570eceb8"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:52455794e6cc2b6a6dbf141a1c4312a1a1215d75e8849a35fcff694454da880f"},
+ {file = "fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8de5decfa18a03807ae26ba5af095c2c04ac31ae915e9a849363a4495463171f"},
+ {file = "fastar-0.8.0.tar.gz", hash = "sha256:f4d4d68dbf1c4c2808f0e730fac5843493fc849f70fe3ad3af60dfbaf68b9a12"},
+]
+
[[package]]
name = "fastjsonschema"
-version = "2.21.1"
+version = "2.21.2"
description = "Fastest Python implementation of JSON schema"
optional = false
python-versions = "*"
groups = ["main"]
files = [
- {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"},
- {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"},
+ {file = "fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463"},
+ {file = "fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de"},
]
[package.extras]
@@ -853,150 +1192,191 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc
[[package]]
name = "filelock"
-version = "3.18.0"
+version = "3.20.0"
description = "A platform independent file lock."
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
- {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
+ {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"},
+ {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"},
]
-[package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
-typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
-
[[package]]
name = "findpython"
-version = "0.6.3"
+version = "0.7.1"
description = "A utility to find python versions on your system"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "findpython-0.6.3-py3-none-any.whl", hash = "sha256:a85bb589b559cdf1b87227cc233736eb7cad894b9e68021ee498850611939ebc"},
- {file = "findpython-0.6.3.tar.gz", hash = "sha256:5863ea55556d8aadc693481a14ac4f3624952719efc1c5591abb0b4a9e965c94"},
+ {file = "findpython-0.7.1-py3-none-any.whl", hash = "sha256:1b78b1ff6e886cbddeffe80f8ecdbf2b8b061169bbd18b673070e26b644c51ac"},
+ {file = "findpython-0.7.1.tar.gz", hash = "sha256:9f29e6a3dabdb75f2b39c949772c0ed26eab15308006669f3478cdab0d867c78"},
]
[package.dependencies]
packaging = ">=20"
+platformdirs = ">=4.3.6"
[[package]]
name = "frozenlist"
-version = "1.7.0"
+version = "1.8.0"
description = "A list-like structure which implements collections.abc.MutableSequence"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"},
- {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"},
- {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"},
- {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"},
- {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"},
- {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"},
- {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"},
- {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"},
- {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"},
- {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"},
- {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"},
- {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"},
- {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"},
- {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"},
- {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"},
- {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"},
- {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"},
- {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"},
- {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"},
- {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"},
- {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"},
- {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"},
- {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"},
- {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"},
- {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"},
- {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"},
- {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"},
- {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"},
- {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"},
- {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"},
- {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"},
- {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"},
- {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"},
- {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"},
- {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"},
- {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"},
- {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"},
- {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"},
- {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"},
- {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"},
- {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"},
- {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"},
- {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"},
- {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"},
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"},
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"},
+ {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"},
+ {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"},
+ {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"},
+ {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"},
+ {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"},
+ {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"},
+ {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"},
+ {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"},
+ {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"},
+ {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"},
+ {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"},
+ {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"},
+ {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"},
+ {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"},
+ {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"},
+ {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"},
+ {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"},
+ {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"},
+ {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"},
+ {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"},
+ {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"},
+ {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"},
+ {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"},
+ {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"},
+ {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"},
+ {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"},
+ {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"},
+ {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"},
]
+[[package]]
+name = "furo"
+version = "2025.9.25"
+description = "A clean customisable Sphinx documentation theme."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+ {file = "furo-2025.9.25-py3-none-any.whl", hash = "sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe"},
+ {file = "furo-2025.9.25.tar.gz", hash = "sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98"},
+]
+
+[package.dependencies]
+accessible-pygments = ">=0.0.5"
+beautifulsoup4 = "*"
+pygments = ">=2.7"
+sphinx = ">=6.0,<9.0"
+sphinx-basic-ng = ">=1.0.0.beta2"
+
[[package]]
name = "h11"
version = "0.16.0"
@@ -1033,60 +1413,57 @@ trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httptools"
-version = "0.6.4"
+version = "0.7.1"
description = "A collection of framework independent HTTP protocol utils."
optional = false
-python-versions = ">=3.8.0"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"},
- {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"},
- {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"},
- {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"},
- {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"},
- {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"},
- {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"},
- {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"},
- {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"},
- {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"},
- {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"},
- {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"},
- {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"},
- {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"},
- {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"},
- {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"},
- {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"},
- {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"},
- {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"},
- {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"},
- {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"},
- {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"},
- {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"},
- {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"},
- {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"},
- {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"},
- {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"},
- {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"},
- {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"},
- {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"},
- {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"},
- {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"},
- {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"},
- {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"},
- {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"},
- {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"},
- {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"},
- {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"},
- {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"},
- {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"},
- {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"},
- {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"},
- {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"},
+ {file = "httptools-0.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:11d01b0ff1fe02c4c32d60af61a4d613b74fad069e47e06e9067758c01e9ac78"},
+ {file = "httptools-0.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d86c1e5afdc479a6fdabf570be0d3eb791df0ae727e8dbc0259ed1249998d4"},
+ {file = "httptools-0.7.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8c751014e13d88d2be5f5f14fc8b89612fcfa92a9cc480f2bc1598357a23a05"},
+ {file = "httptools-0.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:654968cb6b6c77e37b832a9be3d3ecabb243bbe7a0b8f65fbc5b6b04c8fcabed"},
+ {file = "httptools-0.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b580968316348b474b020edf3988eecd5d6eec4634ee6561e72ae3a2a0e00a8a"},
+ {file = "httptools-0.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d496e2f5245319da9d764296e86c5bb6fcf0cf7a8806d3d000717a889c8c0b7b"},
+ {file = "httptools-0.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbf8317bfccf0fed3b5680c559d3459cccf1abe9039bfa159e62e391c7270568"},
+ {file = "httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657"},
+ {file = "httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70"},
+ {file = "httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df"},
+ {file = "httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e"},
+ {file = "httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274"},
+ {file = "httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec"},
+ {file = "httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb"},
+ {file = "httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5"},
+ {file = "httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5"},
+ {file = "httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03"},
+ {file = "httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2"},
+ {file = "httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362"},
+ {file = "httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c"},
+ {file = "httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321"},
+ {file = "httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3"},
+ {file = "httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca"},
+ {file = "httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c"},
+ {file = "httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66"},
+ {file = "httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346"},
+ {file = "httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650"},
+ {file = "httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6"},
+ {file = "httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270"},
+ {file = "httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3"},
+ {file = "httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1"},
+ {file = "httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b"},
+ {file = "httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60"},
+ {file = "httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca"},
+ {file = "httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96"},
+ {file = "httptools-0.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac50afa68945df63ec7a2707c506bd02239272288add34539a2ef527254626a4"},
+ {file = "httptools-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de987bb4e7ac95b99b805b99e0aae0ad51ae61df4263459d36e07cf4052d8b3a"},
+ {file = "httptools-0.7.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d169162803a24425eb5e4d51d79cbf429fd7a491b9e570a55f495ea55b26f0bf"},
+ {file = "httptools-0.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49794f9250188a57fa73c706b46cb21a313edb00d337ca4ce1a011fe3c760b28"},
+ {file = "httptools-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aeefa0648362bb97a7d6b5ff770bfb774930a327d7f65f8208394856862de517"},
+ {file = "httptools-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0d92b10dbf0b3da4823cde6a96d18e6ae358a9daa741c71448975f6a2c339cad"},
+ {file = "httptools-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:5ddbd045cfcb073db2449563dd479057f2c2b681ebc232380e63ef15edc9c023"},
+ {file = "httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9"},
]
-[package.extras]
-test = ["Cython (>=0.29.24)"]
-
[[package]]
name = "httpx"
version = "0.28.1"
@@ -1114,19 +1491,31 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "idna"
-version = "3.10"
+version = "3.11"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
-python-versions = ">=3.6"
-groups = ["main"]
+python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
- {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
- {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
+ {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+groups = ["main", "dev"]
+files = [
+ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
[[package]]
name = "importlib-metadata"
version = "8.7.0"
@@ -1164,6 +1553,18 @@ files = [
{file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"},
]
+[[package]]
+name = "invoke"
+version = "2.2.1"
+description = "Pythonic task execution"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+files = [
+ {file = "invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"},
+ {file = "invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"},
+]
+
[[package]]
name = "jaraco-classes"
version = "3.4.0"
@@ -1204,14 +1605,14 @@ test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-c
[[package]]
name = "jaraco-functools"
-version = "4.2.1"
+version = "4.3.0"
description = "Functools like those found in stdlib"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e"},
- {file = "jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353"},
+ {file = "jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8"},
+ {file = "jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294"},
]
[package.dependencies]
@@ -1248,7 +1649,7 @@ version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
@@ -1262,14 +1663,14 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "keyring"
-version = "25.6.0"
+version = "25.7.0"
description = "Store and access your passwords safely."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"},
- {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"},
+ {file = "keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f"},
+ {file = "keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b"},
]
[package.dependencies]
@@ -1286,20 +1687,19 @@ check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"
completion = ["shtab (>=1.1.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-enabler = ["pytest-enabler (>=2.2)"]
+enabler = ["pytest-enabler (>=3.4)"]
test = ["pyfakefs", "pytest (>=6,!=8.1.*)"]
-type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"]
+type = ["pygobject-stubs", "pytest-mypy (>=1.0.1)", "shtab", "types-pywin32"]
[[package]]
name = "mac-vendor-lookup"
-version = "0.1.12"
+version = "0.1.13"
description = "Find the vendor for a given MAC address"
optional = false
-python-versions = "<4, >=3.5"
+python-versions = "<4,>=3.5"
groups = ["main"]
files = [
- {file = "mac_vendor_lookup-0.1.12-py3-none-any.whl", hash = "sha256:aeec6eac01b07e6558d889b51f475a1e1e938e09cab409a069ab6a43b13cba58"},
- {file = "mac_vendor_lookup-0.1.12.tar.gz", hash = "sha256:74e1723e177d4deb02977148d3fa04a7916f4bf93268e2afe3240529272bf80d"},
+ {file = "mac_vendor_lookup-0.1.13-py3-none-any.whl", hash = "sha256:6c2b5ac4b27dab71ed43f7ac6402c6fde1463bcd730b8d58417f0004b3aafd40"},
]
[package.dependencies]
@@ -1327,7 +1727,7 @@ version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
@@ -1348,82 +1748,130 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
-version = "3.0.2"
+version = "3.0.3"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
- {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"},
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"},
+ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
]
+[[package]]
+name = "mdit-py-plugins"
+version = "0.5.0"
+description = "Collection of plugins for markdown-it-py"
+optional = false
+python-versions = ">=3.10"
+groups = ["main", "dev"]
+files = [
+ {file = "mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f"},
+ {file = "mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.0.0,<5.0.0"
+
+[package.extras]
+code-style = ["pre-commit"]
+rtd = ["myst-parser", "sphinx-book-theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
@@ -1431,205 +1879,271 @@ files = [
[[package]]
name = "more-itertools"
-version = "10.7.0"
+version = "10.8.0"
description = "More routines for operating on iterables, beyond itertools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e"},
- {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"},
+ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"},
+ {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"},
]
[[package]]
name = "msgpack"
-version = "1.1.1"
+version = "1.1.2"
description = "MessagePack serializer"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"},
- {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"},
- {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2"},
- {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4"},
- {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0"},
- {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26"},
- {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75"},
- {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338"},
- {file = "msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd"},
- {file = "msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8"},
- {file = "msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558"},
- {file = "msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d"},
- {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0"},
- {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f"},
- {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704"},
- {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2"},
- {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2"},
- {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752"},
- {file = "msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295"},
- {file = "msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458"},
- {file = "msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238"},
- {file = "msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157"},
- {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce"},
- {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a"},
- {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c"},
- {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b"},
- {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef"},
- {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a"},
- {file = "msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c"},
- {file = "msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4"},
- {file = "msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0"},
- {file = "msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9"},
- {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8"},
- {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a"},
- {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac"},
- {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b"},
- {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7"},
- {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5"},
- {file = "msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323"},
- {file = "msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69"},
- {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285"},
- {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600"},
- {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9"},
- {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78"},
- {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a"},
- {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6"},
- {file = "msgpack-1.1.1-cp38-cp38-win32.whl", hash = "sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142"},
- {file = "msgpack-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad"},
- {file = "msgpack-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b"},
- {file = "msgpack-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232"},
- {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf"},
- {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf"},
- {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90"},
- {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1"},
- {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88"},
- {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478"},
- {file = "msgpack-1.1.1-cp39-cp39-win32.whl", hash = "sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57"},
- {file = "msgpack-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084"},
- {file = "msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd"},
+ {file = "msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2"},
+ {file = "msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87"},
+ {file = "msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251"},
+ {file = "msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a"},
+ {file = "msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f"},
+ {file = "msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f"},
+ {file = "msgpack-1.1.2-cp310-cp310-win32.whl", hash = "sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9"},
+ {file = "msgpack-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa"},
+ {file = "msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c"},
+ {file = "msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0"},
+ {file = "msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296"},
+ {file = "msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef"},
+ {file = "msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c"},
+ {file = "msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e"},
+ {file = "msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e"},
+ {file = "msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68"},
+ {file = "msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406"},
+ {file = "msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa"},
+ {file = "msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb"},
+ {file = "msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f"},
+ {file = "msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42"},
+ {file = "msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9"},
+ {file = "msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620"},
+ {file = "msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029"},
+ {file = "msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b"},
+ {file = "msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69"},
+ {file = "msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf"},
+ {file = "msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7"},
+ {file = "msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999"},
+ {file = "msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e"},
+ {file = "msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162"},
+ {file = "msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794"},
+ {file = "msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c"},
+ {file = "msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9"},
+ {file = "msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84"},
+ {file = "msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00"},
+ {file = "msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939"},
+ {file = "msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e"},
+ {file = "msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931"},
+ {file = "msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014"},
+ {file = "msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2"},
+ {file = "msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717"},
+ {file = "msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b"},
+ {file = "msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af"},
+ {file = "msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a"},
+ {file = "msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b"},
+ {file = "msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245"},
+ {file = "msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90"},
+ {file = "msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20"},
+ {file = "msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27"},
+ {file = "msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b"},
+ {file = "msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff"},
+ {file = "msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46"},
+ {file = "msgpack-1.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea5405c46e690122a76531ab97a079e184c0daf491e588592d6a23d3e32af99e"},
+ {file = "msgpack-1.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fba231af7a933400238cb357ecccf8ab5d51535ea95d94fc35b7806218ff844"},
+ {file = "msgpack-1.1.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a8f6e7d30253714751aa0b0c84ae28948e852ee7fb0524082e6716769124bc23"},
+ {file = "msgpack-1.1.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94fd7dc7d8cb0a54432f296f2246bc39474e017204ca6f4ff345941d4ed285a7"},
+ {file = "msgpack-1.1.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:350ad5353a467d9e3b126d8d1b90fe05ad081e2e1cef5753f8c345217c37e7b8"},
+ {file = "msgpack-1.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6bde749afe671dc44893f8d08e83bf475a1a14570d67c4bb5cec5573463c8833"},
+ {file = "msgpack-1.1.2-cp39-cp39-win32.whl", hash = "sha256:ad09b984828d6b7bb52d1d1d0c9be68ad781fa004ca39216c8a1e63c0f34ba3c"},
+ {file = "msgpack-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:67016ae8c8965124fdede9d3769528ad8284f14d635337ffa6a713a580f6c030"},
+ {file = "msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e"},
]
[[package]]
name = "multidict"
-version = "6.6.3"
+version = "6.7.0"
description = "multidict implementation"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817"},
- {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140"},
- {file = "multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8"},
- {file = "multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61"},
- {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b"},
- {file = "multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318"},
- {file = "multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485"},
- {file = "multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5"},
- {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c"},
- {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df"},
- {file = "multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9"},
- {file = "multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56"},
- {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183"},
- {file = "multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5"},
- {file = "multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2"},
- {file = "multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb"},
- {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6"},
- {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f"},
- {file = "multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a"},
- {file = "multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75"},
- {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10"},
- {file = "multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5"},
- {file = "multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17"},
- {file = "multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b"},
- {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55"},
- {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b"},
- {file = "multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca"},
- {file = "multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1"},
- {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6"},
- {file = "multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e"},
- {file = "multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9"},
- {file = "multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600"},
- {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134"},
- {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37"},
- {file = "multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0"},
- {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d"},
- {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c"},
- {file = "multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e"},
- {file = "multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d"},
- {file = "multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb"},
- {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22"},
- {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557"},
- {file = "multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab"},
- {file = "multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092"},
- {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed"},
- {file = "multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b"},
- {file = "multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578"},
- {file = "multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d"},
- {file = "multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a"},
- {file = "multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc"},
+ {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"},
+ {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"},
+ {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"},
+ {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"},
+ {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"},
+ {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"},
+ {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"},
+ {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"},
+ {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"},
+ {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"},
+ {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"},
+ {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"},
+ {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"},
+ {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"},
+ {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"},
+ {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"},
+ {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"},
+ {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"},
+ {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"},
+ {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"},
+ {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"},
+ {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"},
+ {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"},
+ {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"},
+ {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"},
+ {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"},
+ {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"},
+ {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"},
+ {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"},
+ {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"},
+ {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"},
+ {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"},
+ {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"},
+ {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"},
+ {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"},
+ {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"},
+ {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"},
+ {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"},
+ {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"},
+ {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"},
+ {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"},
+ {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"},
+ {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"},
+ {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"},
+ {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"},
+ {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"},
+ {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"},
+ {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"},
+ {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"},
+ {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"},
+ {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"},
+ {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"},
+ {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"},
+ {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"},
+ {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"},
+ {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"},
+ {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"},
+ {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"},
+ {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"},
+ {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"},
+ {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"},
+ {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"},
+ {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"},
+ {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"},
+ {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"},
+ {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"},
]
+[[package]]
+name = "myst-parser"
+version = "4.0.1"
+description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser,"
+optional = false
+python-versions = ">=3.10"
+groups = ["main", "dev"]
+files = [
+ {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"},
+ {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"},
+]
+
+[package.dependencies]
+docutils = ">=0.19,<0.22"
+jinja2 = "*"
+markdown-it-py = ">=3.0,<4.0"
+mdit-py-plugins = ">=0.4.1,<1.0"
+pyyaml = "*"
+sphinx = ">=7,<9"
+
+[package.extras]
+code-style = ["pre-commit (>=4.0,<5.0)"]
+linkify = ["linkify-it-py (>=2.0,<3.0)"]
+rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"]
+testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"]
+testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"]
+
[[package]]
name = "netaddr"
version = "1.3.0"
@@ -1685,13 +2199,50 @@ files = [
{file = "netifaces-0.11.0.tar.gz", hash = "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32"},
]
+[[package]]
+name = "nuitka"
+version = "2.8.8"
+description = "Python compiler with full language support and CPython compatibility"
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+ {file = "nuitka-2.8.8.tar.gz", hash = "sha256:fd0e652ed4f1452f34eb8e25c6902b153aad00882347c2b6b7ad80fb6f02de5e"},
+]
+
+[package.dependencies]
+ordered-set = ">=4.1.0"
+zstandard = ">=0.15"
+
+[package.extras]
+all = ["imageio", "setuptools (>=42)", "toml", "zstandard (>=0.15)"]
+app = ["zstandard (>=0.15)"]
+build-wheel = ["setuptools (>=42)", "toml", "wheel"]
+icon-conversion = ["imageio"]
+onefile = ["zstandard (>=0.15)"]
+
+[[package]]
+name = "ordered-set"
+version = "4.1.0"
+description = "An OrderedSet is a custom MutableSet that remembers its order, so that every"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+ {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"},
+ {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"},
+]
+
+[package.extras]
+dev = ["black", "mypy", "pytest"]
+
[[package]]
name = "packaging"
version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
@@ -1699,25 +2250,24 @@ files = [
[[package]]
name = "paramiko"
-version = "3.5.1"
+version = "4.0.0"
description = "SSH2 protocol library"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61"},
- {file = "paramiko-3.5.1.tar.gz", hash = "sha256:b2c665bc45b2b215bd7d7f039901b14b067da00f3a11e6640995fd58f2664822"},
+ {file = "paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9"},
+ {file = "paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f"},
]
[package.dependencies]
bcrypt = ">=3.2"
cryptography = ">=3.3"
+invoke = ">=2.0"
pynacl = ">=1.5"
[package.extras]
-all = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""]
gssapi = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""]
-invoke = ["invoke (>=2.0)"]
[[package]]
name = "passlib"
@@ -1742,14 +2292,14 @@ totp = ["cryptography"]
[[package]]
name = "pbs-installer"
-version = "2025.7.2"
+version = "2025.11.20"
description = "Installer for Python Build Standalone"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "pbs_installer-2025.7.2-py3-none-any.whl", hash = "sha256:dd778682922f200174e41cf4e019030d412430756d085cf986237c7c23f67ea9"},
- {file = "pbs_installer-2025.7.2.tar.gz", hash = "sha256:72026d253a6f002514aa0bfb4a650a20333102e7679df0fff810b858720598d9"},
+ {file = "pbs_installer-2025.11.20-py3-none-any.whl", hash = "sha256:5588b40a41b03101514c71cadb37ad75a3476dee43ba5fdaeec699e51ec0e94a"},
+ {file = "pbs_installer-2025.11.20.tar.gz", hash = "sha256:6469b854835fa316c24c938df4563a72adae3d14b659eb4b23fa4bb347fd45f3"},
]
[package.dependencies]
@@ -1778,66 +2328,81 @@ testing = ["pytest", "pytest-cov", "wheel"]
[[package]]
name = "platformdirs"
-version = "4.3.8"
+version = "4.5.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
- {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
+ {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"},
+ {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"},
]
[package.extras]
-docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
-type = ["mypy (>=1.14.1)"]
+docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"]
+type = ["mypy (>=1.18.2)"]
+
+[[package]]
+name = "pockets"
+version = "0.9.1"
+description = "A collection of helpful Python tools!"
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+ {file = "pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86"},
+ {file = "pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3"},
+]
+
+[package.dependencies]
+six = ">=1.5.2"
[[package]]
name = "poetry"
-version = "2.1.3"
+version = "2.2.1"
description = "Python dependency management and packaging made easy."
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
- {file = "poetry-2.1.3-py3-none-any.whl", hash = "sha256:7054d3f97ccce7f31961ead16250407c4577bfe57e2037a190ae2913fc40a20c"},
- {file = "poetry-2.1.3.tar.gz", hash = "sha256:f2c9bd6790b19475976d88ea4553bcc3533c0dc73f740edc4fffe9e2add50594"},
+ {file = "poetry-2.2.1-py3-none-any.whl", hash = "sha256:f5958b908b96c5824e2acbb8b19cdef8a3351c62142d7ecff2d705396c8ca34c"},
+ {file = "poetry-2.2.1.tar.gz", hash = "sha256:bef9aa4bb00ce4c10b28b25e7bac724094802d6958190762c45df6c12749b37c"},
]
[package.dependencies]
build = ">=1.2.1,<2.0.0"
cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]}
cleo = ">=2.1.0,<3.0.0"
-dulwich = ">=0.22.6,<0.23.0"
+dulwich = ">=0.24.0,<0.25.0"
fastjsonschema = ">=2.18.0,<3.0.0"
-findpython = ">=0.6.2,<0.7.0"
+findpython = ">=0.6.2,<0.8.0"
installer = ">=0.7.0,<0.8.0"
keyring = ">=25.1.0,<26.0.0"
-packaging = ">=24.0"
+packaging = ">=24.2"
pbs-installer = {version = ">=2025.1.6,<2026.0.0", extras = ["download", "install"]}
pkginfo = ">=1.12,<2.0"
platformdirs = ">=3.0.0,<5"
-poetry-core = "2.1.3"
+poetry-core = "2.2.1"
pyproject-hooks = ">=1.0.0,<2.0.0"
requests = ">=2.26,<3.0"
requests-toolbelt = ">=1.0.0,<2.0.0"
shellingham = ">=1.5,<2.0"
tomlkit = ">=0.11.4,<1.0.0"
trove-classifiers = ">=2022.5.19"
-virtualenv = ">=20.26.6,<21.0.0"
+virtualenv = ">=20.26.6"
xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""}
[[package]]
name = "poetry-core"
-version = "2.1.3"
+version = "2.2.1"
description = "Poetry PEP 517 Build Backend"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
- {file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"},
- {file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"},
+ {file = "poetry_core-2.2.1-py3-none-any.whl", hash = "sha256:bdfce710edc10bfcf9ab35041605c480829be4ab23f5bc01202cfe5db8f125ab"},
+ {file = "poetry_core-2.2.1.tar.gz", hash = "sha256:97e50d8593c8729d3f49364b428583e044087ee3def1e010c6496db76bd65ac5"},
]
[[package]]
@@ -1856,112 +2421,148 @@ files = [
poetry = ">=2.0.0,<3.0.0"
poetry-core = ">=1.7.0,<3.0.0"
+[[package]]
+name = "polib"
+version = "1.2.0"
+description = "A library to manipulate gettext files (po and mo files)."
+optional = false
+python-versions = "*"
+groups = ["main"]
+files = [
+ {file = "polib-1.2.0-py2.py3-none-any.whl", hash = "sha256:1c77ee1b81feb31df9bca258cbc58db1bbb32d10214b173882452c73af06d62d"},
+ {file = "polib-1.2.0.tar.gz", hash = "sha256:f3ef94aefed6e183e342a8a269ae1fc4742ba193186ad76f175938621dbfc26b"},
+]
+
[[package]]
name = "propcache"
-version = "0.3.2"
+version = "0.4.1"
description = "Accelerated property cache"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"},
- {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"},
- {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"},
- {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"},
- {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"},
- {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"},
- {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"},
- {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"},
- {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"},
- {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"},
- {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"},
- {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"},
- {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"},
- {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"},
- {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"},
- {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"},
- {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"},
- {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"},
- {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"},
- {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"},
- {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"},
- {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"},
- {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"},
- {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"},
- {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"},
- {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"},
- {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"},
- {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"},
- {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"},
- {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"},
- {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"},
- {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"},
- {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"},
- {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"},
- {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"},
- {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"},
- {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"},
- {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"},
- {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"},
- {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"},
- {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"},
- {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"},
- {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"},
- {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"},
- {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"},
- {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"},
- {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"},
- {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"},
- {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"},
- {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"},
- {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"},
- {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"},
- {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"},
- {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"},
- {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"},
- {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"},
- {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"},
- {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"},
- {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"},
- {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"},
- {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"},
- {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"},
- {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"},
- {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"},
- {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"},
- {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"},
- {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"},
- {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"},
+ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"},
+ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"},
+ {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"},
+ {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"},
+ {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"},
+ {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"},
+ {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"},
+ {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"},
+ {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"},
+ {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"},
+ {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"},
+ {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"},
+ {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"},
+ {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"},
+ {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"},
+ {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"},
+ {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"},
+ {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"},
+ {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"},
+ {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"},
+ {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"},
+ {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"},
+ {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"},
+ {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"},
+ {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"},
+ {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"},
+ {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"},
+ {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"},
+ {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"},
+ {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"},
+ {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"},
+ {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"},
+ {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"},
+ {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"},
+ {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"},
+ {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"},
+ {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"},
+ {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"},
+ {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"},
+ {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"},
+ {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"},
+ {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"},
+ {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"},
+ {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"},
+ {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"},
+ {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"},
+ {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"},
+ {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"},
+ {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"},
+ {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"},
+ {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"},
+ {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"},
]
[[package]]
@@ -1978,33 +2579,35 @@ files = [
[[package]]
name = "pycparser"
-version = "2.22"
+version = "2.23"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
+markers = "(platform_python_implementation != \"PyPy\" or sys_platform == \"darwin\") and implementation_name != \"PyPy\""
files = [
- {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
- {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"},
+ {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"},
]
[[package]]
name = "pydantic"
-version = "2.11.7"
+version = "2.12.5"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
- {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
+ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"},
+ {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.33.2"
-typing-extensions = ">=4.12.2"
-typing-inspection = ">=0.4.0"
+email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
+pydantic-core = "2.41.5"
+typing-extensions = ">=4.14.1"
+typing-inspection = ">=0.4.2"
[package.extras]
email = ["email-validator (>=2.0.0)"]
@@ -2012,115 +2615,137 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows
[[package]]
name = "pydantic-core"
-version = "2.33.2"
+version = "2.41.5"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
- {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
- {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
- {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
- {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
- {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
- {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
- {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
- {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
- {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
- {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
- {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
- {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
- {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
- {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
- {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
- {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
- {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
- {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
- {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
- {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
- {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
- {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
- {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
- {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
- {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
- {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
- {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
- {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
- {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"},
+ {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"},
]
[package.dependencies]
-typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+typing-extensions = ">=4.14.1"
[[package]]
name = "pydbus"
@@ -2140,7 +2765,7 @@ version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
@@ -2151,30 +2776,47 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pynacl"
-version = "1.5.0"
+version = "1.6.1"
description = "Python binding to the Networking and Cryptography (NaCl) library"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
- {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
- {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
- {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
- {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
- {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
- {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
- {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
- {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
- {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+ {file = "pynacl-1.6.1-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:7d7c09749450c385301a3c20dca967a525152ae4608c0a096fe8464bfc3df93d"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc734c1696ffd49b40f7c1779c89ba908157c57345cf626be2e0719488a076d3"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3cd787ec1f5c155dc8ecf39b1333cfef41415dc96d392f1ce288b4fe970df489"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b35d93ab2df03ecb3aa506be0d3c73609a51449ae0855c2e89c7ed44abde40b"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dece79aecbb8f4640a1adbb81e4aa3bfb0e98e99834884a80eb3f33c7c30e708"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c2228054f04bf32d558fb89bb99f163a8197d5a9bf4efa13069a7fa8d4b93fc3"},
+ {file = "pynacl-1.6.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:2b12f1b97346f177affcdfdc78875ff42637cb40dcf79484a97dae3448083a78"},
+ {file = "pynacl-1.6.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e735c3a1bdfde3834503baf1a6d74d4a143920281cb724ba29fb84c9f49b9c48"},
+ {file = "pynacl-1.6.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3384a454adf5d716a9fadcb5eb2e3e72cd49302d1374a60edc531c9957a9b014"},
+ {file = "pynacl-1.6.1-cp314-cp314t-win32.whl", hash = "sha256:d8615ee34d01c8e0ab3f302dcdd7b32e2bcf698ba5f4809e7cc407c8cdea7717"},
+ {file = "pynacl-1.6.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5f5b35c1a266f8a9ad22525049280a600b19edd1f785bccd01ae838437dcf935"},
+ {file = "pynacl-1.6.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d984c91fe3494793b2a1fb1e91429539c6c28e9ec8209d26d25041ec599ccf63"},
+ {file = "pynacl-1.6.1-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:a6f9fd6d6639b1e81115c7f8ff16b8dedba1e8098d2756275d63d208b0e32021"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e49a3f3d0da9f79c1bec2aa013261ab9fa651c7da045d376bd306cf7c1792993"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7713f8977b5d25f54a811ec9efa2738ac592e846dd6e8a4d3f7578346a841078"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3becafc1ee2e5ea7f9abc642f56b82dcf5be69b961e782a96ea52b55d8a9fc"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ce50d19f1566c391fedc8dc2f2f5be265ae214112ebe55315e41d1f36a7f0a9"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:543f869140f67d42b9b8d47f922552d7a967e6c116aad028c9bfc5f3f3b3a7b7"},
+ {file = "pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a2bb472458c7ca959aeeff8401b8efef329b0fc44a89d3775cffe8fad3398ad8"},
+ {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3206fa98737fdc66d59b8782cecc3d37d30aeec4593d1c8c145825a345bba0f0"},
+ {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:53543b4f3d8acb344f75fd4d49f75e6572fce139f4bfb4815a9282296ff9f4c0"},
+ {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:319de653ef84c4f04e045eb250e6101d23132372b0a61a7acf91bac0fda8e58c"},
+ {file = "pynacl-1.6.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:262a8de6bba4aee8a66f5edf62c214b06647461c9b6b641f8cd0cb1e3b3196fe"},
+ {file = "pynacl-1.6.1-cp38-abi3-win32.whl", hash = "sha256:9fd1a4eb03caf8a2fe27b515a998d26923adb9ddb68db78e35ca2875a3830dde"},
+ {file = "pynacl-1.6.1-cp38-abi3-win_amd64.whl", hash = "sha256:a569a4069a7855f963940040f35e87d8bc084cb2d6347428d5ad20550a0a1a21"},
+ {file = "pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf"},
+ {file = "pynacl-1.6.1.tar.gz", hash = "sha256:8d361dac0309f2b6ad33b349a56cd163c98430d409fa503b10b70b3ad66eaa1d"},
]
[package.dependencies]
-cffi = ">=1.4.1"
+cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\" and python_version >= \"3.9\""}
[package.extras]
-docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
-tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
+docs = ["sphinx (<7)", "sphinx_rtd_theme"]
+tests = ["hypothesis (>=3.27.0)", "pytest (>=7.4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
[[package]]
name = "pyproject-hooks"
@@ -2190,14 +2832,14 @@ files = [
[[package]]
name = "python-dotenv"
-version = "1.1.1"
+version = "1.2.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
- {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
+ {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"},
+ {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"},
]
[package.extras]
@@ -2278,169 +2920,178 @@ files = [
[[package]]
name = "pyyaml"
-version = "6.0.2"
+version = "6.0.3"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
- {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
- {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
- {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
- {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
- {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
- {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
- {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
- {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
- {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
- {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
- {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
- {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
- {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
- {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
- {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
- {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
- {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
- {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
- {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
- {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
- {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
- {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
- {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
- {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
- {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+ {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"},
+ {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"},
+ {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"},
+ {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"},
+ {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"},
+ {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"},
+ {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"},
+ {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"},
+ {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"},
+ {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"},
+ {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"},
+ {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"},
+ {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"},
+ {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"},
+ {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"},
+ {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"},
+ {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"},
+ {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"},
+ {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"},
+ {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"},
+ {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"},
+ {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"},
+ {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"},
+ {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"},
+ {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"},
+ {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"},
+ {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"},
+ {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"},
+ {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"},
+ {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"},
+ {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"},
+ {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"},
+ {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"},
+ {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"},
+ {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"},
+ {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"},
+ {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"},
+ {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"},
+ {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"},
+ {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"},
+ {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"},
+ {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"},
+ {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"},
+ {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"},
+ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]
[[package]]
name = "rapidfuzz"
-version = "3.13.0"
+version = "3.14.3"
description = "rapid fuzzy string matching"
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107"},
- {file = "rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f"},
- {file = "rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e"},
- {file = "rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264"},
- {file = "rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-win32.whl", hash = "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138"},
- {file = "rapidfuzz-3.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99"},
- {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4"},
- {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998"},
- {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9"},
- {file = "rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141"},
+ {file = "rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5"},
+ {file = "rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329"},
+ {file = "rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15"},
+ {file = "rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c"},
+ {file = "rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253"},
+ {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23"},
+ {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300"},
+ {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede"},
+ {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6"},
+ {file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5"},
+ {file = "rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f"},
]
[package.extras]
@@ -2448,14 +3099,14 @@ all = ["numpy"]
[[package]]
name = "requests"
-version = "2.32.4"
+version = "2.32.5"
description = "Python HTTP for Humans."
optional = false
-python-versions = ">=3.8"
-groups = ["main"]
+python-versions = ">=3.9"
+groups = ["main", "dev"]
files = [
- {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
- {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
+ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
+ {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
]
[package.dependencies]
@@ -2485,14 +3136,14 @@ requests = ">=2.0.1,<3.0.0"
[[package]]
name = "rich"
-version = "14.0.0"
+version = "14.2.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
- {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"},
- {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"},
+ {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"},
+ {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"},
]
[package.dependencies]
@@ -2504,14 +3155,14 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "rich-toolkit"
-version = "0.14.8"
+version = "0.16.0"
description = "Rich toolkit for building command-line applications"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "rich_toolkit-0.14.8-py3-none-any.whl", hash = "sha256:c54bda82b93145a79bbae04c3e15352e6711787c470728ff41fdfa0c2f0c11ae"},
- {file = "rich_toolkit-0.14.8.tar.gz", hash = "sha256:1f77b32e6c25d9e3644c1efbce00d8d90daf2457b3abdb4699e263c03b9ca6cf"},
+ {file = "rich_toolkit-0.16.0-py3-none-any.whl", hash = "sha256:3f4307f678c5c1e22c36d89ac05f1cd145ed7174f19c1ce5a4d3664ba77c0f9e"},
+ {file = "rich_toolkit-0.16.0.tar.gz", hash = "sha256:2f554b00b194776639f4d80f2706980756b659ceed9f345ebbd9de77d1bdd0f4"},
]
[package.dependencies]
@@ -2519,6 +3170,186 @@ click = ">=8.1.7"
rich = ">=13.7.1"
typing-extensions = ">=4.12.2"
+[[package]]
+name = "rignore"
+version = "0.7.6"
+description = "Python Bindings for the ignore crate"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45"},
+ {file = "rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87"},
+ {file = "rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f"},
+ {file = "rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322"},
+ {file = "rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa"},
+ {file = "rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e"},
+ {file = "rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb"},
+ {file = "rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b"},
+ {file = "rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39"},
+ {file = "rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c"},
+ {file = "rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c"},
+ {file = "rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049"},
+ {file = "rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce"},
+ {file = "rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb"},
+ {file = "rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e"},
+ {file = "rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345"},
+ {file = "rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5"},
+ {file = "rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a"},
+ {file = "rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581"},
+ {file = "rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8"},
+ {file = "rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd"},
+ {file = "rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20"},
+ {file = "rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b"},
+ {file = "rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5"},
+ {file = "rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca"},
+ {file = "rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696"},
+ {file = "rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046"},
+ {file = "rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543"},
+ {file = "rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051"},
+ {file = "rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf"},
+ {file = "rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7"},
+ {file = "rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de"},
+ {file = "rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79"},
+ {file = "rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b"},
+ {file = "rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e"},
+ {file = "rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360"},
+ {file = "rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5"},
+ {file = "rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e"},
+ {file = "rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804"},
+ {file = "rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732"},
+ {file = "rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94"},
+ {file = "rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9"},
+ {file = "rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7"},
+ {file = "rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441"},
+ {file = "rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72"},
+ {file = "rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b"},
+ {file = "rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001"},
+ {file = "rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304"},
+ {file = "rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304"},
+ {file = "rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8"},
+ {file = "rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21"},
+ {file = "rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f"},
+ {file = "rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb"},
+ {file = "rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013"},
+ {file = "rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c"},
+ {file = "rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc"},
+ {file = "rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9"},
+ {file = "rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96"},
+ {file = "rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896"},
+ {file = "rignore-0.7.6-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:1bd0bf3f4e57f3d50a91dd4eff6a22ddc9b999dbab2b20fb0473332a5551a0be"},
+ {file = "rignore-0.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:afb5157cd217af4f47a13ad7cbfc35de0aa1740331ba662fa02fea94269d5894"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca877c5a7b78fe74d97b34b735ea8f320f97c49083f7bf8fe9b61a02cf677e67"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5fde2bdfd6b3afee19db5efe01e4165437329f9300441c1b25d5b2aa6752c0cc"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef2183285a49653517a100f28d8c1a3e037a5e8cefe79cffe205ecc4b98f5095"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87554ae12f813d3a287a0f2aad957c11e5c4ace17bfed15d471e5be13e95d9fb"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3111040f77ec6b543a501a194c48d5260898e618712472deb91bf48026f1606c"},
+ {file = "rignore-0.7.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8dfa178ead3abeeaf6b8c4fe9c6c9b333d2d66c88735566f919169d18e728fa5"},
+ {file = "rignore-0.7.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:112527b824eaa93c99c2c7eb11e7df83eab46a63d527bcd71a92151bba5d0435"},
+ {file = "rignore-0.7.6-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:435c0c0f38f15d9bef2a97b039b5157bbc32791510670b89504e644de1d27a5e"},
+ {file = "rignore-0.7.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:96e899cd34b422c2d3ad7bef279e16387f217d53ec5f9a25dbc3fcad19470381"},
+ {file = "rignore-0.7.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ba1b9c80df4ea126ef303c7646021f44486342d43b7153f3454e15cd55eaa87"},
+ {file = "rignore-0.7.6-cp38-cp38-win32.whl", hash = "sha256:1a1dffbfd930b27aef1962098710344297d52368b362f918eaf1464b0d8d052c"},
+ {file = "rignore-0.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:7f41cecc799005a029407893071b15082d504f9115a57db9ea893b35f3f70604"},
+ {file = "rignore-0.7.6-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b3746bda73f2fe6a9c3ab2f20b792e7d810b30acbdba044313fbd2d0174802e7"},
+ {file = "rignore-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:67a99cf19a5137cc12f14b78dc1bb3f48500f1d5580702c623297d5297bf2752"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9e851cfa87033c0c3fd9d35dd8b102aff2981db8bc6e0cab27b460bfe38bf3f"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e9b0def154665036516114437a5d603274e5451c0dc9694f622cc3b7e94603e7"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b81274a47e8121224f7f637392b5dfcd9558e32a53e67ba7d04007d8b5281da9"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d75d0b0696fb476664bea1169c8e67b13197750b91eceb4f10b3c7f379c7a204"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ad3aa4dca77cef9168d0c142f72376f5bd27d1d4b8a81561bd01276d3ad9fe1"},
+ {file = "rignore-0.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00f8a59e19d219f44a93af7173de197e0d0e61c386364da20ebe98a303cbe38c"},
+ {file = "rignore-0.7.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6c682f3cdd741e7a30af2581f6a382ac910080977cd1f97c651467b6268352"},
+ {file = "rignore-0.7.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ae4e93193f75ebf6b820241594a78f347785cfd5a5fbbac94634052589418352"},
+ {file = "rignore-0.7.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1163d8b5d3a320d4d7cc8635213328850dc41f60e438c7869d540061adf66c98"},
+ {file = "rignore-0.7.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e685f47b4c58b2df7dee81ebc1ec9dbb7f798b9455c3f22be6d75ac6bddee30"},
+ {file = "rignore-0.7.6-cp39-cp39-win32.whl", hash = "sha256:2af6a0a76575220863cd838693c808a94e750640e0c8a3e9f707e93c2f131fdf"},
+ {file = "rignore-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:a326eab6db9ab85b4afb5e6eb28736a9f2b885a9246d9e8c1989bc693dd059a0"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e"},
+ {file = "rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79"},
+ {file = "rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6ba1511c0ab8cd1ed8d6055bb0a6e629f48bfe04854293e0cd2dd88bd7153f8"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:50586d90be15f9aa8a2e2ee5a042ee6c51e28848812a35f0c95d4bfc0533d469"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b129873dd0ade248e67f25a09b5b72288cbef76ba1a9aae6bac193ee1d8be72"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9d6dd947556ddebfd62753005104986ee14a4e0663818aed19cdf2c33a6b5d5"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91b95faa532efba888b196331e9af69e693635d469185ac52c796e435e2484e5"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1016f430fb56f7e400838bbc56fdf43adddb6fcb7bf2a14731dfd725c2fae6c"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f00c519861926dc703ecbb7bbeb884be67099f96f98b175671fa0a54718f55d1"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e34d172bf50e881b7c02e530ae8b1ea96093f0b16634c344f637227b39707b41"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:101d3143619898db1e7bede2e3e647daf19bb867c4fb25978016d67978d14868"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c9f3b420f54199a2b2b3b532d8c7e0860be3fa51f67501113cca6c7bfc392840"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:1c6795e3694d750ae5ef172eab7d68a52aefbd9168d2e06647df691db2b03a50"},
+ {file = "rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:750a83a254b020e1193bfa7219dc7edca26bd8888a94cdc59720cbe386ab0c72"},
+ {file = "rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671"},
+]
+
+[[package]]
+name = "roman-numerals-py"
+version = "3.1.0"
+description = "Manipulate well-formed Roman numerals"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"},
+ {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"},
+]
+
+[package.extras]
+lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"]
+test = ["pytest (>=8)"]
+
[[package]]
name = "rsa"
version = "4.9.1"
@@ -2553,16 +3384,16 @@ doc = ["sphinx (>=7.0.0)", "sphinx-rtd-theme (>=1.3.0)", "tox (>=3.0.0)"]
[[package]]
name = "sdbus"
-version = "0.14.0"
+version = "0.14.1.post0"
description = "Modern Python D-Bus library. Based on sd-bus from libsystemd."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "sdbus-0.14.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a5b9315090ffbe232c82edc3e6f1a449ed27c7ef7f3ebc81d73326057fba8f1"},
- {file = "sdbus-0.14.0-cp39-abi3-manylinux_2_28_armv7l.whl", hash = "sha256:46f60ee66f18f0a3b9cb19639bc8a9ada62cd7f89343bba8a3140c0563885561"},
- {file = "sdbus-0.14.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d38597858490c2063dd3de508942688075089843ef851fb8c062cb393e8b99d5"},
- {file = "sdbus-0.14.0.tar.gz", hash = "sha256:41d61b76cc05a9ea41d10d70a11e9f9a86ed95f40f713630c5e18340e0e4c76f"},
+ {file = "sdbus-0.14.1.post0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2ec48808f13e118ab23a0919c4230f7c3bc3a167e4215b5c26ba37a8b6d93964"},
+ {file = "sdbus-0.14.1.post0-cp39-abi3-manylinux_2_28_armv7l.whl", hash = "sha256:cce4b73dbeb3e7aea2f1fa557d51d1cb39d32769afa75275966c9d09ead35780"},
+ {file = "sdbus-0.14.1.post0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a76bfac9728217a7492bdac6379e2bd7b7134bf60ff53def05c6ff9b28141539"},
+ {file = "sdbus-0.14.1.post0.tar.gz", hash = "sha256:ae3915ab3e3f0a116632e1e5876df992b9529e82b0b492006eb22f8791ed6deb"},
]
[[package]]
@@ -2582,21 +3413,84 @@ sdbus = ">=0.8rc2"
[[package]]
name = "secretstorage"
-version = "3.3.3"
+version = "3.5.0"
description = "Python bindings to FreeDesktop.org Secret Service API"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.10"
groups = ["main"]
markers = "sys_platform == \"linux\""
files = [
- {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"},
- {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"},
+ {file = "secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137"},
+ {file = "secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be"},
]
[package.dependencies]
cryptography = ">=2.0"
jeepney = ">=0.6"
+[[package]]
+name = "sentry-sdk"
+version = "2.46.0"
+description = "Python client for Sentry (https://sentry.io)"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+files = [
+ {file = "sentry_sdk-2.46.0-py2.py3-none-any.whl", hash = "sha256:4eeeb60198074dff8d066ea153fa6f241fef1668c10900ea53a4200abc8da9b1"},
+ {file = "sentry_sdk-2.46.0.tar.gz", hash = "sha256:91821a23460725734b7741523021601593f35731808afc0bb2ba46c27b8acd91"},
+]
+
+[package.dependencies]
+certifi = "*"
+urllib3 = ">=1.26.11"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.5)"]
+anthropic = ["anthropic (>=0.16)"]
+arq = ["arq (>=0.23)"]
+asyncpg = ["asyncpg (>=0.23)"]
+beam = ["apache-beam (>=2.12)"]
+bottle = ["bottle (>=0.12.13)"]
+celery = ["celery (>=3)"]
+celery-redbeat = ["celery-redbeat (>=2)"]
+chalice = ["chalice (>=1.16.0)"]
+clickhouse-driver = ["clickhouse-driver (>=0.2.0)"]
+django = ["django (>=1.8)"]
+falcon = ["falcon (>=1.4)"]
+fastapi = ["fastapi (>=0.79.0)"]
+flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"]
+google-genai = ["google-genai (>=1.29.0)"]
+grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"]
+http2 = ["httpcore[http2] (==1.*)"]
+httpx = ["httpx (>=0.16.0)"]
+huey = ["huey (>=2)"]
+huggingface-hub = ["huggingface_hub (>=0.22)"]
+langchain = ["langchain (>=0.0.210)"]
+langgraph = ["langgraph (>=0.6.6)"]
+launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"]
+litellm = ["litellm (>=1.77.5)"]
+litestar = ["litestar (>=2.0.0)"]
+loguru = ["loguru (>=0.5)"]
+mcp = ["mcp (>=1.15.0)"]
+openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
+openfeature = ["openfeature-sdk (>=0.7.1)"]
+opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
+opentelemetry-experimental = ["opentelemetry-distro"]
+opentelemetry-otlp = ["opentelemetry-distro[otlp] (>=0.35b0)"]
+pure-eval = ["asttokens", "executing", "pure_eval"]
+pydantic-ai = ["pydantic-ai (>=1.0.0)"]
+pymongo = ["pymongo (>=3.1)"]
+pyspark = ["pyspark (>=2.4.4)"]
+quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
+rq = ["rq (>=0.6)"]
+sanic = ["sanic (>=0.8)"]
+sqlalchemy = ["sqlalchemy (>=1.2)"]
+starlette = ["starlette (>=0.19.1)"]
+starlite = ["starlite (>=1.48)"]
+statsig = ["statsig (>=0.55.3)"]
+tornado = ["tornado (>=6)"]
+unleash = ["UnleashClient (>=6.0.1)"]
+
[[package]]
name = "shellingham"
version = "1.5.4"
@@ -2615,7 +3509,7 @@ version = "1.17.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
@@ -2633,6 +3527,240 @@ files = [
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*"
+groups = ["main", "dev"]
+files = [
+ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"},
+ {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"},
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"},
+ {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"},
+]
+
+[[package]]
+name = "sphinx"
+version = "8.2.3"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.11"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"},
+ {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7.14"
+babel = ">=2.13"
+colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
+docutils = ">=0.20,<0.22"
+imagesize = ">=1.3"
+Jinja2 = ">=3.1"
+packaging = ">=23.0"
+Pygments = ">=2.17"
+requests = ">=2.30.0"
+roman-numerals-py = ">=1.0.0"
+snowballstemmer = ">=2.2"
+sphinxcontrib-applehelp = ">=1.0.7"
+sphinxcontrib-devhelp = ">=1.0.6"
+sphinxcontrib-htmlhelp = ">=2.0.6"
+sphinxcontrib-jsmath = ">=1.0.1"
+sphinxcontrib-qthelp = ">=1.0.6"
+sphinxcontrib-serializinghtml = ">=1.1.9"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"]
+test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "2.5.0"
+description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "sphinx_autodoc_typehints-2.5.0-py3-none-any.whl", hash = "sha256:53def4753239683835b19bfa8b68c021388bd48a096efcb02cdab508ece27363"},
+ {file = "sphinx_autodoc_typehints-2.5.0.tar.gz", hash = "sha256:259e1026b218d563d72743f417fcc25906a9614897fe37f91bd8d7d58f748c3b"},
+]
+
+[package.dependencies]
+sphinx = ">=8.0.2"
+
+[package.extras]
+docs = ["furo (>=2024.8.6)"]
+numpy = ["nptyping (>=2.5)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "defusedxml (>=0.7.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1.1)", "typing-extensions (>=4.12.2)"]
+
+[[package]]
+name = "sphinx-basic-ng"
+version = "1.0.0b2"
+description = "A modern skeleton for Sphinx themes."
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+ {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"},
+ {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"},
+]
+
+[package.dependencies]
+sphinx = ">=4.0"
+
+[package.extras]
+docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"]
+
+[[package]]
+name = "sphinx-markdown-builder"
+version = "0.6.8"
+description = "A Sphinx extension to add markdown generation support."
+optional = false
+python-versions = ">=3.7"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinx_markdown_builder-0.6.8-py3-none-any.whl", hash = "sha256:f04ab42d52449363228b9104569c56b778534f9c41a168af8cfc721a1e0e3edc"},
+ {file = "sphinx_markdown_builder-0.6.8.tar.gz", hash = "sha256:6141b566bf18dd1cd515a0a90efd91c6c4d10fc638554fab2fd19cba66543dd7"},
+]
+
+[package.dependencies]
+docutils = "*"
+sphinx = ">=5.1.0"
+tabulate = "*"
+
+[package.extras]
+dev = ["black", "bumpver", "coveralls", "flake8", "isort", "pip-tools", "pylint", "pytest", "pytest-cov", "sphinx (>=5.3.0)", "sphinxcontrib-plantuml", "sphinxcontrib.httpdomain"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
+ {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
+ {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
+ {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-napoleon"
+version = "0.7"
+description = "Sphinx \"napoleon\" extension."
+optional = false
+python-versions = "*"
+groups = ["dev"]
+files = [
+ {file = "sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8"},
+ {file = "sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef"},
+]
+
+[package.dependencies]
+pockets = ">=0.3"
+six = ">=1.5.2"
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
+ {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["defusedxml (>=0.7.1)", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
+ {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
[[package]]
name = "starlette"
version = "0.46.2"
@@ -2665,6 +3793,21 @@ files = [
[package.dependencies]
paramiko = ">=1.8.0"
+[[package]]
+name = "tabulate"
+version = "0.9.0"
+description = "Pretty-print tabular data"
+optional = false
+python-versions = ">=3.7"
+groups = ["main", "dev"]
+files = [
+ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
+ {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
+]
+
+[package.extras]
+widechars = ["wcwidth"]
+
[[package]]
name = "tomlkit"
version = "0.13.3"
@@ -2679,26 +3822,26 @@ files = [
[[package]]
name = "trove-classifiers"
-version = "2025.5.9.12"
+version = "2025.11.14.15"
description = "Canonical source for classifiers on PyPI (pypi.org)."
optional = false
python-versions = "*"
groups = ["main"]
files = [
- {file = "trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce"},
- {file = "trove_classifiers-2025.5.9.12.tar.gz", hash = "sha256:7ca7c8a7a76e2cd314468c677c69d12cc2357711fcab4a60f87994c1589e5cb5"},
+ {file = "trove_classifiers-2025.11.14.15-py3-none-any.whl", hash = "sha256:d1dac259c1e908939862e3331177931c6df0a37af2c1a8debcc603d9115fcdd9"},
+ {file = "trove_classifiers-2025.11.14.15.tar.gz", hash = "sha256:6b60f49d40bbd895bc61d8dc414fc2f2286d70eb72ed23548db8cf94f62804ca"},
]
[[package]]
name = "typer"
-version = "0.16.0"
+version = "0.20.0"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"},
- {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"},
+ {file = "typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a"},
+ {file = "typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37"},
]
[package.dependencies]
@@ -2709,26 +3852,26 @@ typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
-version = "4.14.0"
+version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
- {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
- {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]]
name = "typing-inspection"
-version = "0.4.1"
+version = "0.4.2"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"},
- {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"},
+ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"},
+ {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"},
]
[package.dependencies]
@@ -2740,7 +3883,7 @@ version = "2.5.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
{file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
@@ -2791,67 +3934,79 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)
[[package]]
name = "uvloop"
-version = "0.21.0"
+version = "0.22.1"
description = "Fast implementation of asyncio event loop on top of libuv"
optional = false
-python-versions = ">=3.8.0"
+python-versions = ">=3.8.1"
groups = ["main"]
markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
files = [
- {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"},
- {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"},
- {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"},
- {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"},
- {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"},
- {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"},
- {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"},
- {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"},
- {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"},
- {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"},
- {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"},
- {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"},
- {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"},
- {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"},
- {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"},
- {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"},
- {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"},
- {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"},
- {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"},
- {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"},
- {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"},
- {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"},
- {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"},
- {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"},
- {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"},
- {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"},
- {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"},
- {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"},
- {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"},
- {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"},
- {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"},
- {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"},
- {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"},
- {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"},
- {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"},
- {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"},
- {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"},
+ {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c"},
+ {file = "uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792"},
+ {file = "uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86"},
+ {file = "uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd"},
+ {file = "uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2"},
+ {file = "uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec"},
+ {file = "uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9"},
+ {file = "uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77"},
+ {file = "uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21"},
+ {file = "uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702"},
+ {file = "uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733"},
+ {file = "uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473"},
+ {file = "uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42"},
+ {file = "uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6"},
+ {file = "uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370"},
+ {file = "uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4"},
+ {file = "uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2"},
+ {file = "uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0"},
+ {file = "uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705"},
+ {file = "uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8"},
+ {file = "uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d"},
+ {file = "uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e"},
+ {file = "uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e"},
+ {file = "uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad"},
+ {file = "uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142"},
+ {file = "uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74"},
+ {file = "uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35"},
+ {file = "uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25"},
+ {file = "uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6"},
+ {file = "uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079"},
+ {file = "uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289"},
+ {file = "uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3"},
+ {file = "uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c"},
+ {file = "uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21"},
+ {file = "uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88"},
+ {file = "uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e"},
+ {file = "uvloop-0.22.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:80eee091fe128e425177fbd82f8635769e2f32ec9daf6468286ec57ec0313efa"},
+ {file = "uvloop-0.22.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:017bd46f9e7b78e81606329d07141d3da446f8798c6baeec124260e22c262772"},
+ {file = "uvloop-0.22.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3e5c6727a57cb6558592a95019e504f605d1c54eb86463ee9f7a2dbd411c820"},
+ {file = "uvloop-0.22.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57df59d8b48feb0e613d9b1f5e57b7532e97cbaf0d61f7aa9aa32221e84bc4b6"},
+ {file = "uvloop-0.22.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:55502bc2c653ed2e9692e8c55cb95b397d33f9f2911e929dc97c4d6b26d04242"},
+ {file = "uvloop-0.22.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4a968a72422a097b09042d5fa2c5c590251ad484acf910a651b4b620acd7f193"},
+ {file = "uvloop-0.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4"},
+ {file = "uvloop-0.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c"},
+ {file = "uvloop-0.22.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54"},
+ {file = "uvloop-0.22.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659"},
+ {file = "uvloop-0.22.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743"},
+ {file = "uvloop-0.22.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7"},
+ {file = "uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f"},
]
[package.extras]
dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"]
-docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
-test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx_rtd_theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["aiohttp (>=3.10.5)", "flake8 (>=6.1,<7.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=25.3.0,<25.4.0)", "pycodestyle (>=2.11.0,<2.12.0)"]
[[package]]
name = "virtualenv"
-version = "20.31.2"
+version = "20.35.4"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"},
- {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"},
+ {file = "virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"},
+ {file = "virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"},
]
[package.dependencies]
@@ -2865,118 +4020,121 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]]
name = "watchfiles"
-version = "1.1.0"
+version = "1.1.1"
description = "Simple, modern and high performance file watching and code reload in python."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"},
- {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5"},
- {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9"},
- {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72"},
- {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc"},
- {file = "watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587"},
- {file = "watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82"},
- {file = "watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2"},
- {file = "watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8"},
- {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"},
- {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4"},
- {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d"},
- {file = "watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2"},
- {file = "watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12"},
- {file = "watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a"},
- {file = "watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179"},
- {file = "watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd"},
- {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f"},
- {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4"},
- {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f"},
- {file = "watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd"},
- {file = "watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47"},
- {file = "watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6"},
- {file = "watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30"},
- {file = "watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b"},
- {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c"},
- {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b"},
- {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb"},
- {file = "watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9"},
- {file = "watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7"},
- {file = "watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5"},
- {file = "watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1"},
- {file = "watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4"},
- {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20"},
- {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef"},
- {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb"},
- {file = "watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297"},
- {file = "watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92"},
- {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e"},
- {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b"},
- {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259"},
- {file = "watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f"},
- {file = "watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb"},
- {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147"},
- {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8"},
- {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db"},
- {file = "watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa"},
- {file = "watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29"},
- {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e"},
- {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86"},
- {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f"},
- {file = "watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267"},
- {file = "watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc"},
- {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5"},
- {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d"},
- {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea"},
- {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6"},
- {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3"},
- {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c"},
- {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432"},
- {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792"},
- {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9"},
- {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a"},
- {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866"},
- {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277"},
- {file = "watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575"},
+ {file = "watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c"},
+ {file = "watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863"},
+ {file = "watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab"},
+ {file = "watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82"},
+ {file = "watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4"},
+ {file = "watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844"},
+ {file = "watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e"},
+ {file = "watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5"},
+ {file = "watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff"},
+ {file = "watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606"},
+ {file = "watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701"},
+ {file = "watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10"},
+ {file = "watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849"},
+ {file = "watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4"},
+ {file = "watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e"},
+ {file = "watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d"},
+ {file = "watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb"},
+ {file = "watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803"},
+ {file = "watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94"},
+ {file = "watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43"},
+ {file = "watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9"},
+ {file = "watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9"},
+ {file = "watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404"},
+ {file = "watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18"},
+ {file = "watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae"},
+ {file = "watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d"},
+ {file = "watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b"},
+ {file = "watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374"},
+ {file = "watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0"},
+ {file = "watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42"},
+ {file = "watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef"},
+ {file = "watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf"},
+ {file = "watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5"},
+ {file = "watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510"},
+ {file = "watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05"},
+ {file = "watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6"},
+ {file = "watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81"},
+ {file = "watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b"},
+ {file = "watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a"},
+ {file = "watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099"},
+ {file = "watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01"},
+ {file = "watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70"},
+ {file = "watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2"},
+ {file = "watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02"},
+ {file = "watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be"},
+ {file = "watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f"},
+ {file = "watchfiles-1.1.1-cp39-cp39-win32.whl", hash = "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b"},
+ {file = "watchfiles-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957"},
+ {file = "watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3"},
+ {file = "watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2"},
+ {file = "watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d"},
+ {file = "watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b"},
+ {file = "watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88"},
+ {file = "watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336"},
+ {file = "watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24"},
+ {file = "watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49"},
+ {file = "watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f"},
+ {file = "watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34"},
+ {file = "watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc"},
+ {file = "watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e"},
+ {file = "watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2"},
]
[package.dependencies]
@@ -3063,83 +4221,63 @@ files = [
[[package]]
name = "xattr"
-version = "1.1.4"
+version = "1.3.0"
description = "Python wrapper for extended filesystem attributes"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
markers = "sys_platform == \"darwin\""
files = [
- {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"},
- {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"},
- {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"},
- {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"},
- {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"},
- {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"},
- {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"},
- {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"},
- {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"},
- {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"},
- {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"},
- {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"},
- {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"},
- {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"},
- {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"},
- {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"},
- {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"},
- {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"},
- {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"},
- {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"},
- {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"},
- {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"},
- {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"},
- {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"},
- {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"},
- {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"},
- {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"},
- {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"},
- {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"},
- {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"},
- {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"},
- {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"},
- {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"},
- {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"},
- {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"},
- {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"},
- {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"},
- {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"},
- {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"},
- {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"},
- {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"},
- {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"},
- {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"},
- {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"},
- {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"},
- {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"},
- {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"},
- {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"},
- {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"},
- {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"},
- {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"},
- {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"},
- {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"},
- {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"},
- {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"},
- {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"},
- {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"},
- {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"},
- {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"},
- {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"},
- {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"},
- {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"},
- {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"},
- {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"},
- {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"},
- {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"},
- {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"},
- {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"},
- {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"},
- {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"},
+ {file = "xattr-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a80c4617e08670cdc3ba71f1dbb275c1627744c5c3641280879cb3bc95a07237"},
+ {file = "xattr-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51cdaa359f5cd2861178ae01ea3647b56dbdfd98e724a8aa3c04f77123b78217"},
+ {file = "xattr-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2fea070768d7d2d25797817bea93bf0a6fda6449e88cfee8bb3d75de9ed11c7b"},
+ {file = "xattr-1.3.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69bca34be2d7a928389aff4e32f27857e1c62d04c91ec7c1519b1636870bd58f"},
+ {file = "xattr-1.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05f8e068409742d246babba60cff8310b2c577745491f498b08bf068e0c867a3"},
+ {file = "xattr-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bbd06987102bc11f5cbd08b15d1029832b862cf5bc61780573fc0828812f01ca"},
+ {file = "xattr-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b8589744116d2c37928b771c50383cb281675cd6dcfd740abfab6883e3d4af85"},
+ {file = "xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8"},
+ {file = "xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89"},
+ {file = "xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811"},
+ {file = "xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d"},
+ {file = "xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5"},
+ {file = "xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db"},
+ {file = "xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3"},
+ {file = "xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5"},
+ {file = "xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4"},
+ {file = "xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386"},
+ {file = "xattr-1.3.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b4345387087fffcd28f709eb45aae113d911e1a1f4f0f70d46b43ba81e69ccdd"},
+ {file = "xattr-1.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe92bb05eb849ab468fe13e942be0f8d7123f15d074f3aba5223fad0c4b484de"},
+ {file = "xattr-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c42ef5bdac3febbe28d3db14d3a8a159d84ba5daca2b13deae6f9f1fc0d4092"},
+ {file = "xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236"},
+ {file = "xattr-1.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:937d8c91f6f372788aff8cc0984c4be3f0928584839aaa15ff1c95d64562071c"},
+ {file = "xattr-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e470b3f15e9c3e263662506ff26e73b3027e1c9beac2cbe9ab89cad9c70c0495"},
+ {file = "xattr-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f2238b2a973fcbf5fefa1137db97c296d27f4721f7b7243a1fac51514565e9ec"},
+ {file = "xattr-1.3.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f32bb00395371f4a3bed87080ae315b19171ba114e8a5aa403a2c8508998ce78"},
+ {file = "xattr-1.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:78df56bfe3dd4912548561ed880225437d6d49ef082fe6ccd45670810fa53cfe"},
+ {file = "xattr-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:864c34c14728f21c3ef89a9f276d75ae5e31dd34f48064e0d37e4bf0f671fc6e"},
+ {file = "xattr-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1fd185b3f01121bd172c98b943f9341ca3b9ea6c6d3eb7fe7074723614d959ff"},
+ {file = "xattr-1.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:630c85020282bd0bcb72c3d031491c4e91d7f29bb4c094ebdfb9db51375c5b07"},
+ {file = "xattr-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:95f1e14a4d9ca160b4b78c527bf2bac6addbeb0fd9882c405fc0b5e3073a8752"},
+ {file = "xattr-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88557c0769f64b1d014aada916c9630cfefa38b0be6c247eae20740d2d8f7b47"},
+ {file = "xattr-1.3.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6992eb5da32c0a1375a9eeacfab15c66eebc8bd34be63ebd1eae80cc2f8bf03"},
+ {file = "xattr-1.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da5954424099ca9d402933eaf6112c29ddde26e6da59b32f0bf5a4e35eec0b28"},
+ {file = "xattr-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:726b4d0b66724759132cacdcd84a5b19e00b0cdf704f4c2cf96d0c08dc5eaeb5"},
+ {file = "xattr-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:928c49ceb0c70fc04732e46fa236d7c8281bfc3db1b40875e5f548bb14d2668c"},
+ {file = "xattr-1.3.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f3bef26fd2d5d7b17488f4cc4424a69894c5a8ed71dd5f657fbbf69f77f68a51"},
+ {file = "xattr-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:64f1fb511f8463851e0d97294eb0e0fde54b059150da90582327fb43baa1bb92"},
+ {file = "xattr-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1e6c216927b16fd4b72df655d5124b69b2a406cb3132b5231179021182f0f0d1"},
+ {file = "xattr-1.3.0-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c0d9ab346cdd20539afddf2f9e123efee0fe8d54254d9fc580b4e2b4e6d77351"},
+ {file = "xattr-1.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2c5e7ba0e893042deef4e8638db7a497680f587ac7bd6d68925f29af633dfa6b"},
+ {file = "xattr-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e0dabb39596d8d7b83d6f9f7fa30be68cf15bfb135cb633e2aad9887d308a32"},
+ {file = "xattr-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5eeaa944516b7507ec51456751334b4880e421de169bbd067c4f32242670d606"},
+ {file = "xattr-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03712f84e056dcd23c36db03a1f45417a26eef2c73d47c2c7d425bf932601587"},
+ {file = "xattr-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45f85233a51c71659969ce364abe6bd0c9048a302b7fcdbea675dc63071e47ff"},
+ {file = "xattr-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fefcf20d040e79ec3bf6e7dc0fdcfd972f70f740d5a69ed67b20c699bb9cea"},
+ {file = "xattr-1.3.0-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9e68a02adde8a5f8675be5e8edc837eb6fdbe214a6ee089956fae11d633c0e51"},
+ {file = "xattr-1.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:50c12d92f5214b0416cf4b4fafcd02dca5434166657553b74b8ba6abc66cb4b4"},
+ {file = "xattr-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c69999ed70411ac2859f1f8c918eb48a6fd2a71ef41dc03ee846f69e2200bb2"},
+ {file = "xattr-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3cf29da6840eb94b881eab692ae83b1421c9c15a0cd92ffb97a0696ceac8cac"},
+ {file = "xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036"},
]
[package.dependencies]
@@ -3150,116 +4288,142 @@ test = ["pytest"]
[[package]]
name = "yarl"
-version = "1.20.1"
+version = "1.22.0"
description = "Yet another URL library"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"},
- {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"},
- {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"},
- {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"},
- {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"},
- {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"},
- {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"},
- {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"},
- {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"},
- {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"},
- {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"},
- {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"},
- {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"},
- {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"},
- {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"},
- {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"},
- {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"},
- {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"},
- {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"},
- {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"},
- {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"},
- {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"},
- {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"},
- {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"},
- {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"},
- {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"},
- {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"},
- {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"},
- {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"},
- {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"},
- {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"},
- {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"},
- {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"},
- {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"},
- {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"},
- {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"},
- {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"},
- {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"},
- {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"},
- {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"},
- {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"},
- {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"},
- {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"},
- {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"},
+ {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"},
+ {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"},
+ {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"},
+ {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"},
+ {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"},
+ {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"},
+ {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"},
+ {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"},
+ {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"},
+ {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"},
+ {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"},
+ {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"},
+ {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"},
+ {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"},
+ {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"},
+ {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"},
+ {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"},
+ {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"},
+ {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"},
+ {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"},
+ {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"},
+ {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"},
+ {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"},
+ {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"},
+ {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"},
+ {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"},
+ {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"},
+ {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"},
+ {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"},
+ {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"},
+ {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"},
+ {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"},
+ {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"},
+ {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"},
+ {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"},
+ {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"},
+ {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"},
+ {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"},
+ {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"},
+ {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"},
+ {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"},
+ {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"},
+ {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"},
+ {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"},
+ {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"},
+ {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"},
+ {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"},
+ {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"},
+ {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"},
+ {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"},
+ {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"},
+ {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"},
+ {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"},
+ {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"},
+ {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"},
+ {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"},
+ {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"},
+ {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"},
+ {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"},
+ {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"},
+ {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"},
+ {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"},
+ {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"},
+ {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"},
+ {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"},
+ {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"},
+ {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"},
+ {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"},
+ {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"},
+ {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"},
+ {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"},
+ {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"},
+ {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"},
+ {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"},
+ {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"},
+ {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"},
+ {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"},
+ {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"},
+ {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"},
+ {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"},
+ {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"},
+ {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"},
+ {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"},
+ {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"},
+ {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"},
+ {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"},
+ {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"},
+ {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"},
+ {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"},
+ {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"},
+ {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"},
+ {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"},
+ {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"},
+ {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"},
+ {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"},
+ {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"},
+ {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"},
+ {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"},
+ {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"},
+ {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"},
+ {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"},
+ {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"},
+ {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"},
+ {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"},
+ {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"},
+ {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"},
+ {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"},
+ {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"},
+ {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"},
+ {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"},
+ {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"},
+ {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"},
+ {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"},
+ {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"},
+ {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"},
+ {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"},
+ {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"},
+ {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"},
+ {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"},
+ {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"},
+ {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"},
+ {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"},
+ {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"},
+ {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"},
+ {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"},
+ {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"},
+ {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"},
+ {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"},
+ {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"},
+ {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"},
]
[package.dependencies]
@@ -3290,118 +4454,117 @@ type = ["pytest-mypy"]
[[package]]
name = "zstandard"
-version = "0.23.0"
+version = "0.25.0"
description = "Zstandard bindings for Python"
optional = false
-python-versions = ">=3.8"
-groups = ["main"]
+python-versions = ">=3.9"
+groups = ["main", "dev"]
files = [
- {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"},
- {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"},
- {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"},
- {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"},
- {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"},
- {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"},
- {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"},
- {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"},
- {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"},
- {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"},
- {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"},
- {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"},
- {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"},
- {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"},
- {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"},
- {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"},
- {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"},
- {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"},
- {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"},
- {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"},
- {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"},
- {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"},
- {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"},
- {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"},
- {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"},
- {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"},
- {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"},
- {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"},
- {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"},
- {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"},
- {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"},
- {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"},
- {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"},
- {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"},
- {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"},
- {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"},
- {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"},
- {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"},
- {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"},
- {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"},
- {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"},
- {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"},
- {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"},
- {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"},
- {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"},
- {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"},
- {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"},
- {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"},
- {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"},
- {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"},
- {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"},
- {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"},
- {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"},
- {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"},
- {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"},
- {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"},
- {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"},
- {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"},
- {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"},
- {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"},
- {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"},
+ {file = "zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd"},
+ {file = "zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7"},
+ {file = "zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550"},
+ {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d"},
+ {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b"},
+ {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0"},
+ {file = "zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e"},
+ {file = "zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74"},
+ {file = "zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa"},
+ {file = "zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e"},
+ {file = "zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c"},
+ {file = "zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f"},
+ {file = "zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431"},
+ {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a"},
+ {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc"},
+ {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6"},
+ {file = "zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa"},
+ {file = "zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7"},
+ {file = "zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4"},
+ {file = "zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2"},
+ {file = "zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137"},
+ {file = "zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b"},
+ {file = "zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00"},
+ {file = "zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64"},
+ {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea"},
+ {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb"},
+ {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a"},
+ {file = "zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512"},
+ {file = "zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa"},
+ {file = "zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd"},
+ {file = "zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01"},
+ {file = "zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9"},
+ {file = "zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94"},
+ {file = "zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1"},
+ {file = "zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f"},
+ {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea"},
+ {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e"},
+ {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551"},
+ {file = "zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98"},
+ {file = "zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf"},
+ {file = "zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09"},
+ {file = "zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5"},
+ {file = "zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049"},
+ {file = "zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3"},
+ {file = "zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f"},
+ {file = "zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c"},
+ {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439"},
+ {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043"},
+ {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859"},
+ {file = "zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0"},
+ {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7"},
+ {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2"},
+ {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344"},
+ {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c"},
+ {file = "zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088"},
+ {file = "zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12"},
+ {file = "zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2"},
+ {file = "zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d"},
+ {file = "zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0"},
+ {file = "zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2"},
+ {file = "zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df"},
+ {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53"},
+ {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3"},
+ {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362"},
+ {file = "zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388"},
+ {file = "zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27"},
+ {file = "zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649"},
+ {file = "zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860"},
+ {file = "zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b"},
]
-[package.dependencies]
-cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""}
-
[package.extras]
-cffi = ["cffi (>=1.11)"]
+cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""]
[metadata]
lock-version = "2.1"
python-versions = "^3.11"
-content-hash = "7139a70d2eb0c9e91e792256388371d98f71538cbbdd3722b0fc33d980f3a210"
+content-hash = "497fe973e7cf9e229ed08197dbe26b0935bc07d553fc4fea57b746a1b5b7d686"
diff --git a/SRV/pyproject.toml b/SRV/pyproject.toml
index e32b59f..704961f 100644
--- a/SRV/pyproject.toml
+++ b/SRV/pyproject.toml
@@ -1,8 +1,8 @@
[tool.poetry]
name = "GONETS"
-version = "0.1.0"
+version = "0.3.0"
description = "API для управления системными сервисами Linux"
-authors = ["Sergey Galanskiy ", "Vladimir Shalimow "]
+authors = ["Sergey Galanskiy "]
readme = "README.md"
packages = [
{include = "api"},
@@ -11,6 +11,7 @@ packages = [
{include = "services"},
{include = "core"},
{include = "storage"},
+ {include = "utils"},
]
@@ -38,8 +39,24 @@ mac-vendor-lookup = "^0.1.12"
pywifi = "^1.1.12"
manuf = "^1.1.5"
poetry-plugin-export = "^1.9.0"
+babel = "^2.17.0"
+polib = "^1.2.0"
+docutils = "^0.21.2"
+myst-parser = "^4.0.1"
+sphinx-markdown-builder = "^0.6.8"
+[tool.poetry.group.dev.dependencies]
+sphinx = "^8.2.3"
+furo = "^2025.9.25"
+sphinx-autodoc-typehints = "2.5.0"
+sphinxcontrib-napoleon = "^0.7"
+myst-parser = "^4.0.1"
+sphinx-markdown-builder = "^0.6.8"
+nuitka = "^2.8.6"
+ordered-set = "^4.1.0"
+zstandard = "^0.25.0"
+
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
diff --git a/SRV/requirements.txt b/SRV/requirements.txt
index 304c86c..7c10d2a 100644
--- a/SRV/requirements.txt
+++ b/SRV/requirements.txt
@@ -1,74 +1,83 @@
-aiofiles==24.1.0 ; python_version >= "3.11" and python_version < "4.0"
+aiofiles==25.1.0 ; python_version >= "3.11" and python_version < "4.0"
aiohappyeyeballs==2.6.1 ; python_version >= "3.11" and python_version < "4.0"
-aiohttp==3.12.13 ; python_version >= "3.11" and python_version < "4.0"
-aiosignal==1.3.2 ; python_version >= "3.11" and python_version < "4.0"
+aiohttp==3.13.1 ; python_version >= "3.11" and python_version < "4.0"
+aiosignal==1.4.0 ; python_version >= "3.11" and python_version < "4.0"
+alabaster==1.0.0 ; python_version >= "3.11" and python_version < "4.0"
annotated-types==0.7.0 ; python_version >= "3.11" and python_version < "4.0"
-anyio==4.9.0 ; python_version >= "3.11" and python_version < "4.0"
-attrs==25.3.0 ; python_version >= "3.11" and python_version < "4.0"
+anyio==4.11.0 ; python_version >= "3.11" and python_version < "4.0"
+attrs==25.4.0 ; python_version >= "3.11" and python_version < "4.0"
+babel==2.17.0 ; python_version >= "3.11" and python_version < "4.0"
backports-tarfile==1.2.0 ; python_version == "3.11"
bcrypt==4.3.0 ; python_version >= "3.11" and python_version < "4.0"
-build==1.2.2.post1 ; python_version >= "3.11" and python_version < "4.0"
+build==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
cachecontrol==0.14.3 ; python_version >= "3.11" and python_version < "4.0"
-certifi==2025.6.15 ; python_version >= "3.11" and python_version < "4.0"
-cffi==1.17.1 ; python_version >= "3.11" and python_version < "4.0"
-charset-normalizer==3.4.2 ; python_version >= "3.11" and python_version < "4.0"
+certifi==2025.10.5 ; python_version >= "3.11" and python_version < "4.0"
+cffi==2.0.0 ; python_version >= "3.11" and python_version < "4.0" and (sys_platform == "darwin" or platform_python_implementation != "PyPy")
+charset-normalizer==3.4.4 ; python_version >= "3.11" and python_version < "4.0"
cleo==2.1.0 ; python_version >= "3.11" and python_version < "4.0"
-click==8.2.1 ; python_version >= "3.11" and python_version < "4.0"
+click==8.3.0 ; python_version >= "3.11" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and (platform_system == "Windows" or sys_platform == "win32" or os_name == "nt")
crashtest==0.4.1 ; python_version >= "3.11" and python_version < "4.0"
-cryptography==45.0.5 ; python_version >= "3.11" and python_version < "4.0"
+cryptography==46.0.3 ; python_version >= "3.11" and python_version < "4.0"
dbus-python==1.4.0 ; python_version >= "3.11" and python_version < "4.0"
-distlib==0.3.9 ; python_version >= "3.11" and python_version < "4.0"
-dnspython==2.7.0 ; python_version >= "3.11" and python_version < "4.0"
-dulwich==0.22.8 ; python_version >= "3.11" and python_version < "4.0"
+distlib==0.4.0 ; python_version >= "3.11" and python_version < "4.0"
+dnspython==2.8.0 ; python_version >= "3.11" and python_version < "4.0"
+docutils==0.21.2 ; python_version >= "3.11" and python_version < "4.0"
+dulwich==0.24.6 ; python_version >= "3.11" and python_version < "4.0"
ecdsa==0.19.1 ; python_version >= "3.11" and python_version < "4.0"
-email-validator==2.2.0 ; python_version >= "3.11" and python_version < "4.0"
-fastapi-cli==0.0.7 ; python_version >= "3.11" and python_version < "4.0"
+email-validator==2.3.0 ; python_version >= "3.11" and python_version < "4.0"
+fastapi-cli==0.0.14 ; python_version >= "3.11" and python_version < "4.0"
+fastapi-cloud-cli==0.3.1 ; python_version >= "3.11" and python_version < "4.0"
fastapi==0.115.14 ; python_version >= "3.11" and python_version < "4.0"
-fastjsonschema==2.21.1 ; python_version >= "3.11" and python_version < "4.0"
-filelock==3.18.0 ; python_version >= "3.11" and python_version < "4.0"
-findpython==0.6.3 ; python_version >= "3.11" and python_version < "4.0"
-frozenlist==1.7.0 ; python_version >= "3.11" and python_version < "4.0"
+fastjsonschema==2.21.2 ; python_version >= "3.11" and python_version < "4.0"
+filelock==3.20.0 ; python_version >= "3.11" and python_version < "4.0"
+findpython==0.7.0 ; python_version >= "3.11" and python_version < "4.0"
+frozenlist==1.8.0 ; python_version >= "3.11" and python_version < "4.0"
h11==0.16.0 ; python_version >= "3.11" and python_version < "4.0"
httpcore==1.0.9 ; python_version >= "3.11" and python_version < "4.0"
-httptools==0.6.4 ; python_version >= "3.11" and python_version < "4.0"
+httptools==0.7.1 ; python_version >= "3.11" and python_version < "4.0"
httpx==0.28.1 ; python_version >= "3.11" and python_version < "4.0"
-idna==3.10 ; python_version >= "3.11" and python_version < "4.0"
+idna==3.11 ; python_version >= "3.11" and python_version < "4.0"
+imagesize==1.4.1 ; python_version >= "3.11" and python_version < "4.0"
importlib-metadata==8.7.0 ; python_version == "3.11"
installer==0.7.0 ; python_version >= "3.11" and python_version < "4.0"
+invoke==2.2.1 ; python_version >= "3.11" and python_version < "4.0"
jaraco-classes==3.4.0 ; python_version >= "3.11" and python_version < "4.0"
jaraco-context==6.0.1 ; python_version >= "3.11" and python_version < "4.0"
-jaraco-functools==4.2.1 ; python_version >= "3.11" and python_version < "4.0"
+jaraco-functools==4.3.0 ; python_version >= "3.11" and python_version < "4.0"
jeepney==0.9.0 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux"
jinja2==3.1.6 ; python_version >= "3.11" and python_version < "4.0"
keyring==25.6.0 ; python_version >= "3.11" and python_version < "4.0"
mac-vendor-lookup==0.1.12 ; python_version >= "3.11" and python_version < "4.0"
manuf==1.1.5 ; python_version >= "3.11" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.11" and python_version < "4.0"
-markupsafe==3.0.2 ; python_version >= "3.11" and python_version < "4.0"
+markupsafe==3.0.3 ; python_version >= "3.11" and python_version < "4.0"
+mdit-py-plugins==0.5.0 ; python_version >= "3.11" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.11" and python_version < "4.0"
-more-itertools==10.7.0 ; python_version >= "3.11" and python_version < "4.0"
-msgpack==1.1.1 ; python_version >= "3.11" and python_version < "4.0"
-multidict==6.6.3 ; python_version >= "3.11" and python_version < "4.0"
+more-itertools==10.8.0 ; python_version >= "3.11" and python_version < "4.0"
+msgpack==1.1.2 ; python_version >= "3.11" and python_version < "4.0"
+multidict==6.7.0 ; python_version >= "3.11" and python_version < "4.0"
+myst-parser==4.0.1 ; python_version >= "3.11" and python_version < "4.0"
netaddr==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
netifaces==0.11.0 ; python_version >= "3.11" and python_version < "4.0"
packaging==25.0 ; python_version >= "3.11" and python_version < "4.0"
-paramiko==3.5.1 ; python_version >= "3.11" and python_version < "4.0"
+paramiko==4.0.0 ; python_version >= "3.11" and python_version < "4.0"
passlib==1.7.4 ; python_version >= "3.11" and python_version < "4.0"
-pbs-installer==2025.7.2 ; python_version >= "3.11" and python_version < "4.0"
+pbs-installer==2025.10.14 ; python_version >= "3.11" and python_version < "4.0"
pkginfo==1.12.1.2 ; python_version >= "3.11" and python_version < "4.0"
-platformdirs==4.3.8 ; python_version >= "3.11" and python_version < "4.0"
-poetry-core==2.1.3 ; python_version >= "3.11" and python_version < "4.0"
+platformdirs==4.5.0 ; python_version >= "3.11" and python_version < "4.0"
+poetry-core==2.2.1 ; python_version >= "3.11" and python_version < "4.0"
poetry-plugin-export==1.9.0 ; python_version >= "3.11" and python_version < "4.0"
-poetry==2.1.3 ; python_version >= "3.11" and python_version < "4.0"
-propcache==0.3.2 ; python_version >= "3.11" and python_version < "4.0"
+poetry==2.2.1 ; python_version >= "3.11" and python_version < "4.0"
+polib==1.2.0 ; python_version >= "3.11" and python_version < "4.0"
+propcache==0.4.1 ; python_version >= "3.11" and python_version < "4.0"
pyasn1==0.6.1 ; python_version >= "3.11" and python_version < "4.0"
-pycparser==2.22 ; python_version >= "3.11" and python_version < "4.0"
-pydantic-core==2.33.2 ; python_version >= "3.11" and python_version < "4.0"
-pydantic==2.11.7 ; python_version >= "3.11" and python_version < "4.0"
+pycparser==2.23 ; python_version >= "3.11" and python_version < "4.0" and (platform_python_implementation != "PyPy" or sys_platform == "darwin") and implementation_name != "PyPy"
+pydantic-core==2.41.4 ; python_version >= "3.11" and python_version < "4.0"
+pydantic==2.12.3 ; python_version >= "3.11" and python_version < "4.0"
pydbus==0.6.0 ; python_version >= "3.11" and python_version < "4.0"
pygments==2.19.2 ; python_version >= "3.11" and python_version < "4.0"
-pynacl==1.5.0 ; python_version >= "3.11" and python_version < "4.0"
+pynacl==1.6.0 ; python_version >= "3.11" and python_version < "4.0"
pyproject-hooks==1.2.0 ; python_version >= "3.11" and python_version < "4.0"
python-dotenv==1.1.1 ; python_version >= "3.11" and python_version < "4.0"
python-jose==3.5.0 ; python_version >= "3.11" and python_version < "4.0"
@@ -76,35 +85,48 @@ python-multipart==0.0.20 ; python_version >= "3.11" and python_version < "4.0"
python-pam==2.0.2 ; python_version >= "3.11" and python_version < "4.0"
pywifi==1.1.12 ; python_version >= "3.11" and python_version < "4.0"
pywin32-ctypes==0.2.3 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "win32"
-pyyaml==6.0.2 ; python_version >= "3.11" and python_version < "4.0"
-rapidfuzz==3.13.0 ; python_version >= "3.11" and python_version < "4.0"
+pyyaml==6.0.3 ; python_version >= "3.11" and python_version < "4.0"
+rapidfuzz==3.14.1 ; python_version >= "3.11" and python_version < "4.0"
requests-toolbelt==1.0.0 ; python_version >= "3.11" and python_version < "4.0"
-requests==2.32.4 ; python_version >= "3.11" and python_version < "4.0"
-rich-toolkit==0.14.8 ; python_version >= "3.11" and python_version < "4.0"
-rich==14.0.0 ; python_version >= "3.11" and python_version < "4.0"
+requests==2.32.5 ; python_version >= "3.11" and python_version < "4.0"
+rich-toolkit==0.15.1 ; python_version >= "3.11" and python_version < "4.0"
+rich==14.2.0 ; python_version >= "3.11" and python_version < "4.0"
+rignore==0.7.1 ; python_version >= "3.11" and python_version < "4.0"
+roman-numerals-py==3.1.0 ; python_version >= "3.11" and python_version < "4.0"
rsa==4.9.1 ; python_version >= "3.11" and python_version < "4.0"
scapy==2.6.1 ; python_version >= "3.11" and python_version < "4.0"
sdbus-networkmanager==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
-sdbus==0.14.0 ; python_version >= "3.11" and python_version < "4.0"
-secretstorage==3.3.3 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux"
+sdbus==0.14.1.post0 ; python_version >= "3.11" and python_version < "4.0"
+secretstorage==3.4.0 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "linux"
+sentry-sdk==2.42.1 ; python_version >= "3.11" and python_version < "4.0"
shellingham==1.5.4 ; python_version >= "3.11" and python_version < "4.0"
six==1.17.0 ; python_version >= "3.11" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.11" and python_version < "4.0"
+snowballstemmer==3.0.1 ; python_version >= "3.11" and python_version < "4.0"
+sphinx-markdown-builder==0.6.8 ; python_version >= "3.11" and python_version < "4.0"
+sphinx==8.2.3 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-applehelp==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-devhelp==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-htmlhelp==2.1.0 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-qthelp==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
+sphinxcontrib-serializinghtml==2.0.0 ; python_version >= "3.11" and python_version < "4.0"
starlette==0.46.2 ; python_version >= "3.11" and python_version < "4.0"
storage==0.0.4.3 ; python_version >= "3.11" and python_version < "4.0"
+tabulate==0.9.0 ; python_version >= "3.11" and python_version < "4.0"
tomlkit==0.13.3 ; python_version >= "3.11" and python_version < "4.0"
-trove-classifiers==2025.5.9.12 ; python_version >= "3.11" and python_version < "4.0"
-typer==0.16.0 ; python_version >= "3.11" and python_version < "4.0"
-typing-extensions==4.14.0 ; python_version >= "3.11" and python_version < "4.0"
-typing-inspection==0.4.1 ; python_version >= "3.11" and python_version < "4.0"
+trove-classifiers==2025.9.11.17 ; python_version >= "3.11" and python_version < "4.0"
+typer==0.20.0 ; python_version >= "3.11" and python_version < "4.0"
+typing-extensions==4.15.0 ; python_version >= "3.11" and python_version < "4.0"
+typing-inspection==0.4.2 ; python_version >= "3.11" and python_version < "4.0"
urllib3==2.5.0 ; python_version >= "3.11" and python_version < "4.0"
uuid==1.30 ; python_version >= "3.11" and python_version < "4.0"
uvicorn==0.32.1 ; python_version >= "3.11" and python_version < "4.0"
-uvloop==0.21.0 ; python_version >= "3.11" and python_version < "4.0" and sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy"
-virtualenv==20.31.2 ; python_version >= "3.11" and python_version < "4.0"
-watchfiles==1.1.0 ; python_version >= "3.11" and python_version < "4.0"
+uvloop==0.22.1 ; python_version >= "3.11" and python_version < "4.0" and sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy"
+virtualenv==20.35.3 ; python_version >= "3.11" and python_version < "4.0"
+watchfiles==1.1.1 ; python_version >= "3.11" and python_version < "4.0"
websockets==14.2 ; python_version >= "3.11" and python_version < "4.0"
-xattr==1.1.4 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "darwin"
-yarl==1.20.1 ; python_version >= "3.11" and python_version < "4.0"
+xattr==1.3.0 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "darwin"
+yarl==1.22.0 ; python_version >= "3.11" and python_version < "4.0"
zipp==3.23.0 ; python_version == "3.11"
-zstandard==0.23.0 ; python_version >= "3.11" and python_version < "4.0"
+zstandard==0.25.0 ; python_version >= "3.11" and python_version < "4.0"
diff --git a/SRV/services/arp_service.py b/SRV/services/arp_service.py
index d68464d..9c18e20 100644
--- a/SRV/services/arp_service.py
+++ b/SRV/services/arp_service.py
@@ -1,8 +1,41 @@
-from scapy.layers.l2 import ARP, Ether
-from scapy.sendrecv import srp
+"""
+Сервис низкоуровневого ARP-сканирования локальной сети.
+
+Назначение:
+
+- Обнаружение активных узлов в пределах сетей хоста путём широковещательных ARP-запросов от имени выбранного интерфейса.
+- Помощь сервисам более высокого уровня (например, IPService) при проверке занятости адресов перед применением конфигураций.
+
+Особенности реализации:
+
+- Определяет подключённые сети интерфейса через `netifaces` и строит CIDR для сканирования.
+- Формирует ARP-пакеты с помощью `scapy` (Ether/ARP) и отправляет их с таймаутом.
+- Добавляет локальный адрес интерфейса в результирующий список клиентов.
+
+Требования/ограничения:
+
+- Для отправки ARP-кадров могут потребоваться повышенные привилегии или capability на сырой сокет.
+"""
+import os
+import logging
import netifaces
from netaddr import IPNetwork
+from utils.i18n import _, _load_translations
+from babel.support import Translations
+logger = logging.getLogger(__name__)
+
+try:
+ from scapy.layers.l2 import ARP, Ether
+ from scapy.sendrecv import srp
+except Exception as exc: # pragma: no cover - fallback for doc builds
+ if os.getenv("SPHINX_BUILD"):
+ ARP = Ether = None # type: ignore
+
+ def srp(*args, **kwargs):
+ raise RuntimeError("scapy is unavailable during documentation build") from exc
+ else:
+ raise
class NetworkScanner:
"""Инициализация сетевого сканера.
@@ -11,24 +44,67 @@ class NetworkScanner:
вывода информации о клиентах.
"""
- def __init__(self, interface=None):
+ def __init__(self, interface=None, translations: Translations = None):
"""Инициализация параметров сетевого сканера.
Args:
interface (str, optional): Название сетевого интерфейса. По умолчанию
используется интерфейс по умолчанию из netifaces.gateways().
+ translations: Объект переводов для текущего языка.
Инициализирует интерфейс, информацию об IP-адресах и локальный MAC-адрес.
"""
- self.interface = (
- interface or netifaces.gateways()["default"][netifaces.AF_INET][1])
- self.ip_info = netifaces.ifaddresses(self.interface).get(
- netifaces.AF_INET, [])
- self.networks = [
- IPNetwork(f"{ip['addr']}/{ip['netmask']}") for ip in self.ip_info
- ]
- self.local_mac = netifaces.ifaddresses(
- self.interface)[netifaces.AF_LINK][0]["addr"]
+ self.translations = translations or _load_translations("ru")
+ self.interface = None
+ self.ip_info = []
+ self.networks = []
+ self.local_mac = None
+
+ try:
+ candidate_interface = interface
+ if not candidate_interface:
+ gateways = netifaces.gateways()
+ default_gateway = gateways.get("default", {}).get(netifaces.AF_INET)
+ if default_gateway:
+ candidate_interface = default_gateway[1]
+ else:
+ for iface in netifaces.interfaces():
+ if iface == "lo":
+ continue
+ addrs = netifaces.ifaddresses(iface)
+ if netifaces.AF_INET in addrs and addrs[netifaces.AF_INET]:
+ candidate_interface = iface
+ break
+
+ if candidate_interface:
+ self.interface = candidate_interface
+ addresses = netifaces.ifaddresses(self.interface)
+ self.ip_info = addresses.get(netifaces.AF_INET, [])
+ self.networks = [
+ IPNetwork(
+ f"{ip['addr']}/{ip['netmask']}"
+ ) for ip in self.ip_info if ip.get("addr") and ip.get("netmask")
+ ]
+ link_info = addresses.get(netifaces.AF_LINK, [])
+ if link_info:
+ self.local_mac = link_info[0].get("addr")
+ else:
+ logger.warning(
+ "NetworkScanner: IPv4 интерфейс не найден, сканирование будет недоступно"
+ )
+ except Exception as exc:
+ if os.getenv("SPHINX_BUILD"):
+ self.interface = interface or "eth0"
+ self.ip_info = []
+ self.networks = []
+ self.local_mac = "00:00:00:00:00:00"
+ logger.warning(
+ "NetworkScanner falling back to mocked interface during documentation build: %s",
+ exc,
+ )
+ else:
+ logger.error("NetworkScanner инициализация завершилась ошибкой: %s", exc)
+ raise
def _create_arp_packet(self, target_ip):
"""Создаёт ARP-пакет для отправки на указанный IP-адрес.
@@ -59,6 +135,16 @@ class NetworkScanner:
Note:
Учитывает локальные IP-адреса интерфейса и удаляет дубликаты.
"""
+ if not self.interface or not self.networks or ARP is None or Ether is None:
+ fallback_clients = []
+ if self.ip_info:
+ for ip_info in self.ip_info:
+ fallback_clients.append({
+ "ip": ip_info.get("addr"),
+ "mac": self.local_mac,
+ })
+ return [client for client in fallback_clients if client["ip"]]
+
all_clients = []
for network in self.networks:
@@ -79,7 +165,12 @@ class NetworkScanner:
all_clients.extend(clients)
for ip_info in self.ip_info:
- local_client = {"ip": ip_info["addr"], "mac": self.local_mac}
+ if not ip_info.get("addr"):
+ continue
+ local_client = {
+ "ip": ip_info["addr"],
+ "mac": self.local_mac,
+ }
if local_client not in all_clients:
all_clients.append(local_client)
@@ -95,8 +186,8 @@ class NetworkScanner:
Выводит IP-адреса и MAC-адреса в отсортированном по IP порядке.
"""
- print("Доступные устройства в сети:")
- print("IP" + " " * 18 + "MAC")
+ print(self.translations.gettext("Доступные устройства в сети:"))
+ print(self.translations.gettext("IP") + " " * 18 + self.translations.gettext("MAC"))
for client in sorted(clients, key=lambda x: x["ip"]):
print(f"{client['ip']:<16} {client['mac']}")
@@ -104,4 +195,4 @@ class NetworkScanner:
if __name__ == "__main__":
scanner = NetworkScanner()
clients = scanner.scan_network()
- scanner.print_clients(clients)
\ No newline at end of file
+ scanner.print_clients(clients)
diff --git a/SRV/services/auth_service.py b/SRV/services/auth_service.py
index 818fba0..7065755 100644
--- a/SRV/services/auth_service.py
+++ b/SRV/services/auth_service.py
@@ -1,6 +1,6 @@
from jose import JWTError, jwt
from datetime import datetime, timedelta
-from fastapi import HTTPException, Depends, Request
+from fastapi import HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from models.auth import UserCreate, UserLogin, UserResponse, Token
from storage.json_storage import JsonStorage
@@ -9,29 +9,36 @@ from typing import Optional
import configparser
import os
import logging
+from utils.i18n import _, _load_translations
+from babel.support import Translations
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
+CONFIG_PATH = os.getenv("GONETS_CONFIG_PATH", "/etc/gonets/config.ini")
config = configparser.ConfigParser()
-config_path = "/etc/gonets/config.ini"
-if not os.path.exists(config_path):
- raise RuntimeError(f"Конфигурационный файл {config_path} не найден")
-config.read(config_path)
-SECRET_KEY = config["JWT"]["secret_key"]
+read_files = config.read(CONFIG_PATH)
+
+if "JWT" in config:
+ SECRET_KEY = config["JWT"]["secret_key"]
+elif os.getenv("SPHINX_BUILD"):
+ logger.warning("JWT secret not found; using dummy value for documentation build")
+ SECRET_KEY = "sphinx-placeholder-secret"
+else:
+ raise RuntimeError(f"Configuration file {CONFIG_PATH} not found or missing [JWT] section")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
-
class AuthService:
"""Сервис для регистрации, авторизации и управления сессиями"""
- def __init__(self):
+ def __init__(self, translations: Optional[Translations] = None):
self.storage = JsonStorage()
self.session_storage = SessionStorage()
+ self.translations = translations or _load_translations("ru")
def create_access_token(self, data: dict) -> str:
"""Создаёт токен"""
@@ -60,31 +67,31 @@ class AuthService:
return db_user
except ValueError as e:
logger.error(
- f"Ошибка при регистрации пользователя {user.username}: {str(e)}"
+ f"Error registering user {user.username}: {str(e)}"
)
- raise HTTPException(status_code=400, detail=str(e))
+ raise HTTPException(status_code=400, detail=self.translations.gettext(str(e)))
except Exception as e:
logger.error(
- f"Ошибка при регистрации пользователя {user.username}: {type(e).__name__} - {str(e)}"
+ f"Error registering user {user.username}: {type(e).__name__} - {str(e)}"
)
- raise HTTPException(status_code=500, detail="Ошибка регистрации")
+ raise HTTPException(status_code=500, detail=self.translations.gettext("Ошибка регистрации"))
def login(self, user: UserLogin, client_id: str) -> dict:
"""Авторизует пользователя, создаёт сессию и возвращает токен"""
try:
db_user = self.storage.get_user_by_username(user.username)
if not db_user:
- logger.error(f"Пользователь {user.username} не найден")
- raise HTTPException(status_code=401, detail="Неверный логин или пароль")
+ logger.error(f"User {user.username} not found")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Неверный логин или пароль"))
if not self.storage.verify_password(user.password, db_user["password_hash"]):
logger.error(
- f"Неверный пароль для пользователя {user.username}")
- raise HTTPException(status_code=401, detail="Неверный логин или пароль")
+ f"Invalid password for user {user.username}")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Неверный логин или пароль"))
access_token = self.create_access_token(
data={"sub": user.username})
session_id = self.session_storage.create_session(
user.username, access_token, client_id)
- logger.info(f"Пользователь {user.username} успешно авторизован")
+ logger.info(f"User {user.username} successfully logged in")
return {
"access_token": access_token,
"token_type": "bearer",
@@ -93,22 +100,22 @@ class AuthService:
}
except Exception as e:
logger.error(
- f"Ошибка при авторизации пользователя {user.username}: {type(e).__name__} - {str(e)}"
+ f"Error logging in user {user.username}: {type(e).__name__} - {str(e)}"
)
- raise HTTPException(status_code=500, detail="Ошибка авторизации")
+ raise HTTPException(status_code=500, detail=self.translations.gettext("Ошибка авторизации"))
def get_current_user(
self, token: str = Depends(oauth2_scheme)) -> UserResponse:
"""Получает текущего пользователя по токену"""
credentials = self.verify_token(token)
if not credentials:
- logger.error("Неверный или истёкший токен")
- raise HTTPException(status_code=401, detail="Неверный токен")
+ logger.error("Invalid or expired token")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Неверный токен"))
username = credentials["username"]
db_user = self.storage.get_user_by_username(username)
if not db_user:
- logger.error(f"Пользователь {username} не найден")
- raise HTTPException(status_code=401, detail="Пользователь не найден")
+ logger.error(f"User {username} not found")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Пользователь не найден"))
return UserResponse(id=db_user["id"], username=db_user["username"], is_admin=db_user["is_admin"])
def get_user_by_session(self, client_id: str) -> dict:
@@ -116,14 +123,14 @@ class AuthService:
session = self.session_storage.get_latest_session(client_id)
if not session:
logger.error(
- f"Сессия для client_id {client_id} не найдена или истекла")
- raise HTTPException(status_code=401, detail="Сессия недействительна")
+ f"Session for client_id {client_id} not found or expired")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Сессия недействительна"))
username = session["username"]
token = session["token"]
db_user = self.storage.get_user_by_username(username)
if not db_user:
- logger.error(f"Пользователь {username} не найден")
- raise HTTPException(status_code=401, detail="Пользователь не найден")
+ logger.error(f"User {username} not found")
+ raise HTTPException(status_code=401, detail=self.translations.gettext("Пользователь не найден"))
return {
"user":
UserResponse(id=db_user["id"], username=db_user["username"], is_admin=db_user["is_admin"]),
@@ -137,10 +144,10 @@ class AuthService:
if session:
self.session_storage.delete_session(session["session_id"])
logger.info(
- f"Пользователь успешно вышел из системы для client_id {client_id}"
+ f"User successfully logged out for client_id {client_id}"
)
- return {"message": "Успешный выход из системы"}
+ return {"message": self.translations.gettext("Успешный выход из системы")}
except Exception as e:
logger.error(
- f"Ошибка при выходе из системы: {type(e).__name__} - {str(e)}")
- raise HTTPException(status_code=500, detail="Ошибка при выходе из системы")
+ f"Error logging out: {type(e).__name__} - {str(e)}")
+ raise HTTPException(status_code=500, detail=self.translations.gettext("Ошибка при выходе из системы"))
diff --git a/SRV/services/connect_service.py b/SRV/services/connect_service.py
index 984bfab..aef409f 100644
--- a/SRV/services/connect_service.py
+++ b/SRV/services/connect_service.py
@@ -1,6 +1,25 @@
+"""
+Сервис управления подключениями Wi-Fi через `nmcli`.
+
+Назначение:
+
+- Предоставляет операции подключения/отключения к точкам доступа и проверки текущего статуса, ориентируясь на системный NetworkManager.
+
+Поведение:
+
+- Автоматически определяет активный Wi-Fi-интерфейс.
+- Валидирует наличие SSID в листинге перед подключением и интерпретирует частые ошибки `nmcli` (неверный пароль, отсутствующая сеть и т.п.).
+
+Ограничения:
+
+- Требуются права для управления сетевыми соединениями в системе.
+- В окружениях без NetworkManager команды `nmcli` будут недоступны.
+"""
import subprocess
import logging
from typing import Tuple, Optional
+from utils.i18n import _load_translations, _
+from babel.support import Translations
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
@@ -11,12 +30,13 @@ class WifiConnectionService:
Предоставляет методы для подключения, отключения и проверки статуса Wi-Fi соединений.
"""
- def __init__(self):
+ def __init__(self, translations: Optional[Translations] = None):
"""Инициализирует сервис Wi-Fi.
Автоматически определяет доступный Wi-Fi-интерфейс и сохраняет его имя.
"""
self.interface = self._get_wifi_interface()
+ self.translations = translations or _load_translations("ru")
def _get_wifi_interface(self) -> str:
"""Автоматически определяет Wi-Fi-интерфейс.
@@ -29,20 +49,30 @@ class WifiConnectionService:
Exception: Если произошла ошибка при выполнении команд nmcli.
"""
try:
- result = subprocess.run(["nmcli", "radio", "wifi"], capture_output=True, text=True)
- if "enabled" in result.stdout.lower():
- interfaces = subprocess.run(["nmcli", "device", "status"], capture_output=True, text=True).stdout
- for line in interfaces.splitlines():
- if "wifi" in line.lower() and "connected" in line.lower():
- iface = line.split()[0]
- logger.info(f"Используется Wi-Fi-интерфейс: {iface}")
- return iface
- logger.warning("Wi-Fi-интерфейс не найден или не подключён")
- return "wlan0"
- logger.error("Wi-Fi радио отключено")
+ result = subprocess.run(
+ ["nmcli", "radio", "wifi"], capture_output=True, text=True
+ )
+ if "enabled" not in result.stdout.lower():
+ logger.error("Wi-Fi radio is disabled")
+ return ""
+
+ interfaces_output = subprocess.run(
+ ["nmcli", "device", "status"], capture_output=True, text=True
+ ).stdout
+
+ for line in interfaces_output.splitlines():
+ parts = line.split()
+ if len(parts) < 2:
+ continue
+ device_name, device_type = parts[0], parts[1]
+ if device_type.lower() == "wifi":
+ logger.info(f"Using Wi-Fi interface: {device_name}")
+ return device_name
+
+ logger.warning("Wi-Fi interface not found in `nmcli device status` output")
return ""
except Exception as e:
- logger.error(f"Ошибка при определении Wi-Fi-интерфейса: {str(e)}")
+ logger.error(f"Error detecting Wi-Fi interface: {str(e)}")
return ""
def connect(self, ssid: str, password: str) -> Tuple[bool, Optional[str]]:
@@ -63,35 +93,79 @@ class WifiConnectionService:
"""
try:
if not self.interface:
- return False, "Wi-Fi-интерфейс не доступен"
+ return False, self.translations.gettext("Wi-Fi-интерфейс не доступен")
- result = subprocess.run(["nmcli", "-f", "SSID", "device", "wifi", "list"], capture_output=True, text=True)
+ result = subprocess.run(
+ ["nmcli", "-f", "SSID", "device", "wifi", "list"],
+ capture_output=True,
+ text=True,
+ )
if ssid not in result.stdout:
- return False, f"Сеть с SSID {ssid} не найдена в списке доступных сетей"
+ return False, self.translations.gettext("Сеть с SSID {ssid} не найдена в списке доступных сетей").format(ssid=ssid)
- cmd = ["nmcli", "device", "wifi", "connect", ssid, "password", password]
- process = subprocess.run(cmd, capture_output=True, text=True, check=False)
+ def run_connect() -> subprocess.CompletedProcess[str]:
+ cmd = [
+ "nmcli",
+ "device",
+ "wifi",
+ "connect",
+ ssid,
+ "ifname",
+ self.interface,
+ ]
+ if password:
+ cmd.extend(["password", password])
+ return subprocess.run(cmd, capture_output=True, text=True, check=False)
+
+ process = run_connect()
if process.returncode != 0:
error_msg = process.stderr.lower()
- if "secrets were required" in error_msg or "authentication failed" in error_msg or "802-11-wireless-security.psk: недействительное свойство" in process.stderr:
- return False, f"Ошибка подключения к {ssid}: Неверный пароль"
+ key_mgmt_error = (
+ "secrets were required" in error_msg
+ or "authentication failed" in error_msg
+ or "802-11-wireless-security.psk" in error_msg
+ or "key-mgmt" in error_msg
+ )
+ if key_mgmt_error:
+ subprocess.run(
+ ["nmcli", "connection", "delete", "id", ssid],
+ capture_output=True,
+ text=True,
+ )
+ retry_process = run_connect()
+ if retry_process.returncode == 0:
+ logger.info(f"Successfully connected to network {ssid} after profile reset")
+ return True, None
+ error_msg = retry_process.stderr.lower()
+ if (
+ "secrets were required" in error_msg
+ or "authentication failed" in error_msg
+ or "802-11-wireless-security.psk" in error_msg
+ or "key-mgmt" in error_msg
+ ):
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Неверный пароль или тип безопасности").format(ssid=ssid)
+ elif "no network with ssid" in error_msg:
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана").format(ssid=ssid)
+ else:
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: {error}").format(ssid=ssid, error=retry_process.stderr)
elif "no network with ssid" in error_msg:
- return False, f"Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана"
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана").format(ssid=ssid)
else:
- return False, f"Ошибка подключения к {ssid}: {process.stderr}"
- logger.info(f"Успешное подключение к сети {ssid}")
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: {error}").format(ssid=ssid, error=process.stderr)
+
+ logger.info(f"Successfully connected to network {ssid}")
return True, None
except subprocess.CalledProcessError as e:
- logger.error(f"Ошибка подключения к {ssid}: {e.stderr}")
+ logger.error(f"Error connecting to {ssid}: {e.stderr}")
error_msg = e.stderr.lower()
if "secrets were required" in error_msg or "authentication failed" in error_msg or "802-11-wireless-security.psk: недействительное свойство" in e.stderr:
- return False, f"Ошибка подключения к {ssid}: Неверный пароль"
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Неверный пароль").format(ssid=ssid)
elif "no network with ssid" in error_msg:
- return False, f"Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана"
- return False, f"Ошибка подключения к {ssid}: {e.stderr}"
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Сеть с SSID {ssid} недоступна или неверно указана").format(ssid=ssid)
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: {error}").format(ssid=ssid, error=e.stderr)
except Exception as e:
- logger.error(f"Неизвестная ошибка при подключении к {ssid}: {str(e)}")
- return False, f"Ошибка подключения к {ssid}: Неизвестная ошибка - {str(e)}"
+ logger.error(f"Unknown error connecting to {ssid}: {str(e)}")
+ return False, self.translations.gettext("Ошибка подключения к {ssid}: Неизвестная ошибка - {error}").format(ssid=ssid, error=str(e))
def disconnect(self, ssid: str) -> Tuple[bool, Optional[str]]:
"""Отключается от Wi-Fi сети по указанному SSID.
@@ -110,12 +184,12 @@ class WifiConnectionService:
"""
try:
if not self.interface:
- return False, "Wi-Fi-интерфейс не доступен"
+ return False, self.translations.gettext("Wi-Fi-интерфейс не доступен")
result = subprocess.run(["nmcli", "connection", "show", "--active"], capture_output=True, text=True)
active_connections = result.stdout.strip().splitlines()
if not active_connections:
- logger.info(f"Нет активных подключений для SSID {ssid}")
- return True, f"Нет активного подключения для SSID {ssid}"
+ logger.info(f"No active connections for SSID {ssid}")
+ return True, self.translations.gettext("Нет активного подключения для SSID {ssid}").format(ssid=ssid)
for conn in active_connections:
conn_details = conn.split()
if len(conn_details) > 1:
@@ -123,16 +197,16 @@ class WifiConnectionService:
conn_info = subprocess.run(["nmcli", "connection", "show", conn_name], capture_output=True, text=True).stdout
if "802-11-wireless.ssid:" in conn_info and ssid in conn_info:
subprocess.run(["nmcli", "connection", "down", conn_name], capture_output=True, text=True, check=True)
- logger.info(f"Отключено соединение с SSID {ssid}")
+ logger.info(f"Disconnected from SSID {ssid}")
return True, None
- logger.warning(f"Активное подключение с SSID {ssid} не найдено")
- return True, f"Активное подключение с SSID {ssid} не найдено"
+ logger.warning(f"Active connection with SSID {ssid} not found")
+ return True, self.translations.gettext("Активное подключение с SSID {ssid} не найдено").format(ssid=ssid)
except subprocess.CalledProcessError as e:
- logger.error(f"Ошибка при отключении от {ssid}: {e.stderr}")
- return False, f"Ошибка отключения от {ssid}: {e.stderr}"
+ logger.error(f"Error disconnecting from {ssid}: {e.stderr}")
+ return False, self.translations.gettext("Ошибка отключения от {ssid}: {error}").format(ssid=ssid, error=e.stderr)
except Exception as e:
- logger.error(f"Неизвестная ошибка при отключении от {ssid}: {str(e)}")
- return False, f"Ошибка отключения от {ssid}: {str(e)}"
+ logger.error(f"Unknown error disconnecting from {ssid}: {str(e)}")
+ return False, self.translations.gettext("Ошибка отключения от {ssid}: {error}").format(ssid=ssid, error=str(e))
def get_status(self) -> Tuple[bool, Optional[str]]:
"""Проверяет статус текущего подключения к Wi-Fi.
@@ -147,13 +221,13 @@ class WifiConnectionService:
"""
try:
if not self.interface:
- return False, "Wi-Fi-интерфейс не доступен"
+ return False, self.translations.gettext("Wi-Fi-интерфейс не доступен")
result = subprocess.run(["nmcli", "connection", "show", "--active"], capture_output=True, text=True)
if result.stdout.strip():
ssid = result.stdout.splitlines()[1].split()[0]
- logger.info(f"Текущая сеть: {ssid}")
+ logger.info(f"Current network: {ssid}")
return True, ssid
- return False, "Не подключено к Wi-Fi"
+ return False, self.translations.gettext("Не подключено к Wi-Fi")
except Exception as e:
- logger.error(f"Ошибка при проверке статуса: {str(e)}")
- return False, f"Ошибка проверки статуса: {str(e)}"
\ No newline at end of file
+ logger.error(f"Error checking status: {str(e)}")
+ return False, self.translations.gettext("Ошибка проверки статуса: {error}").format(error=str(e))
diff --git a/SRV/services/firewalld_service.py b/SRV/services/firewalld_service.py
index ef0c666..95776e7 100644
--- a/SRV/services/firewalld_service.py
+++ b/SRV/services/firewalld_service.py
@@ -1,3 +1,15 @@
+"""
+Сервис для управления firewalld через D-Bus.
+
+Назначение:
+
+- Обёртка над интерфейсами `org.fedoraproject.FirewallD1` для выполнения типовых операций по зонам: просмотр/изменение дефолтной зоны, добавление и удаление сервисов, портов, источников, перезагрузка конфигурации.
+
+Примечания:
+
+- Используется `pydbus.SystemBus` для доступа к системной шине.
+- Методы генерируют дружелюбные исключения при ошибках, содержащие контекст операции.
+"""
from re import DEBUG
from typing import Dict, List, Optional
diff --git a/SRV/services/ip_service.py b/SRV/services/ip_service.py
index 5115343..52dd9fa 100644
--- a/SRV/services/ip_service.py
+++ b/SRV/services/ip_service.py
@@ -1,4 +1,20 @@
-from typing import Dict, Any
+"""
+Сервис проверки и применения IP-настроек через NetworkManager (D-Bus).
+
+Назначение:
+
+- Перед применением конфигурации проверяет занятость IP адреса, сканируя сегмент сети средствами ARP (через `NetworkScanner`).
+- Применяет изменения профиля подключения через `sdbus_async.networkmanager` и выполняет переподключение интерфейса.
+
+Требования/ограничения:
+
+- Доступ к системной шине D-Bus и права на изменения сетевых профилей.
+- Структура входных данных должна соответствовать схеме `ipv4_updates` для корректного обновления вложенных полей у профиля.
+"""
+import asyncio
+import logging
+from typing import Dict, Any, Optional
+import subprocess
from sdbus_async.networkmanager import (
NetworkManager,
NetworkManagerSettings,
@@ -8,7 +24,11 @@ from sdbus_async.networkmanager import (
import sdbus
from sdbus_async.networkmanager.settings import ConnectionProfile
from services.arp_service import NetworkScanner
+from utils.i18n import _load_translations
+from babel.support import Translations
+from fastapi import Depends
+logger = logging.getLogger(__name__)
class IPService:
"""Сервис проверки доступности IP-адреса и применения настроек сети.
@@ -39,7 +59,7 @@ class IPService:
self.bus = None
self.bus = new_bus
- async def check_ip_config(self, args: Dict[str, Any]) -> Dict[str, Any]:
+ async def check_ip_config(self, args: Dict[str, Any], translations: Translations = None) -> Dict[str, Any]:
"""Проверяет доступность IP-адреса перед его применением.
Выполняет ARP-сканирование сети для проверки, занят ли указанный IP-адрес.
@@ -48,6 +68,7 @@ class IPService:
Args:
args (Dict[str, Any]): Словарь с данными, включая "ipv4_updates"
с полем "ipv4.address-data" для нового IP-адреса.
+ translations (Translations): Объект переводов.
Returns:
Dict[str, Any]: Словарь с ключами:
@@ -57,20 +78,60 @@ class IPService:
Raises:
Exception: Если произошла ошибка при сканировании ARP.
"""
+ translations = translations or _load_translations("ru")
self._reset_bus()
- clients = self.arp_scanner.scan_network()
new_ip = args["ipv4_updates"]["ipv4.address-data"][0]["address"]
+ logger.info("check_ip_config: start ARP scan for %s", new_ip)
+
+ try:
+ clients = await asyncio.wait_for(
+ asyncio.to_thread(self.arp_scanner.scan_network),
+ timeout=10,
+ )
+ except asyncio.TimeoutError as exc:
+ logger.error("check_ip_config: ARP scan timed out for %s", new_ip)
+ return self._fallback_ping(new_ip, translations)
+ except Exception as exc:
+ logger.exception("check_ip_config: ARP scan failed for %s", new_ip)
+ return self._fallback_ping(new_ip, translations, error=str(exc))
+
if any(client["ip"] == new_ip for client in clients):
return {
"available": False,
- "message": f"IP address {new_ip} уже занят."
+ "message": translations.gettext("IP-адрес {ip} уже занят.").format(ip=new_ip)
}
return {
"available": True,
- "message": "Network profile settings are available for use.",
+ "message": translations.gettext("Настройки сетевого профиля доступны для использования.")
}
- async def apply_ip_config(self, args: Dict[str, Any]) -> None:
+ def _fallback_ping(self, ip: str, translations: Translations, error: Optional[str] = None) -> Dict[str, Any]:
+ """Резервная проверка через ping, если ARP-скан недоступен.
+
+ Не блокирует применение: если ping не отвечает, считаем IP свободным,
+ но возвращаем предупреждение.
+ """
+ try:
+ logger.info("check_ip_config: fallback ping for %s after ARP failure (%s)", ip, error or "timeout")
+ result = subprocess.run(
+ ["ping", "-c", "1", "-W", "1", ip],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+ if result.returncode == 0:
+ return {
+ "available": False,
+ "message": translations.gettext("IP-адрес {ip} отвечает на ping").format(ip=ip),
+ }
+ except Exception as exc:
+ logger.warning("check_ip_config: ping fallback failed for %s: %s", ip, exc)
+
+ return {
+ "available": True,
+ "message": translations.gettext("Не удалось проверить ARP, IP {ip} считается свободным").format(ip=ip),
+ }
+
+ async def apply_ip_config(self, args: Dict[str, Any], translations: Translations = None) -> None:
"""Применяет проверенные настройки IP к указанному соединению.
Перезапускает шину D-Bus, обновляет настройки соединения через NetworkManager,
@@ -80,6 +141,7 @@ class IPService:
args (Dict[str, Any]): Словарь с данными, включая:
- "connection_id" (str): Идентификатор соединения.
- "ipv4_updates" (Dict): Обновления настроек IPv4.
+ translations (Translations): Объект переводов.
Raises:
ValueError: Если соединение с указанным ID не найдено.
@@ -88,17 +150,36 @@ class IPService:
Note:
Требуются права для изменения настроек через NetworkManager.
"""
+ translations = translations or _load_translations("ru")
self._reset_bus()
nm = NetworkManager()
settings = NetworkManagerSettings()
- connection_paths = await settings.get_connections_by_id(
- args["connection_id"])
+
+ logger.info("apply_ip_config: start for connection_id=%s", args.get("connection_id"))
+
+ try:
+ connection_paths = await asyncio.wait_for(
+ settings.get_connections_by_id(args["connection_id"]),
+ timeout=5,
+ )
+ except asyncio.TimeoutError as exc:
+ logger.error("apply_ip_config: timeout while fetching connection %s", args.get("connection_id"))
+ raise RuntimeError(
+ translations.gettext("NetworkManager не ответил при поиске профиля")
+ ) from exc
if not connection_paths:
- raise ValueError(f"Подключение {args['connection_id']} не найдено")
+ raise ValueError(translations.gettext("Подключение {connection_id} не найдено").format(connection_id=args["connection_id"]))
connection = NetworkConnectionSettings(connection_paths[0])
- profile = await connection.get_profile()
+ try:
+ profile = await asyncio.wait_for(connection.get_profile(), timeout=5)
+ except asyncio.TimeoutError as exc:
+ logger.error("apply_ip_config: timeout while reading profile %s", args.get("connection_id"))
+ raise RuntimeError(
+ translations.gettext("NetworkManager не ответил при чтении профиля")
+ ) from exc
+
settings_dict = profile.to_settings_dict()
for key_path, value in args["ipv4_updates"].items():
@@ -109,18 +190,51 @@ class IPService:
current[props[-1]] = value
new_profile = ConnectionProfile.from_settings_dict(settings_dict)
- await connection.update(new_profile.to_dbus())
- print(f"Обновлены настройки для {args['connection_id']}:")
+ try:
+ await asyncio.wait_for(connection.update(new_profile.to_dbus()), timeout=5)
+ except asyncio.TimeoutError as exc:
+ logger.error("apply_ip_config: timeout while updating profile %s", args.get("connection_id"))
+ raise RuntimeError(
+ translations.gettext("NetworkManager не ответил при обновлении профиля")
+ ) from exc
+
+ logger.info("apply_ip_config: settings updated for %s", args.get("connection_id"))
+
+ interface_name: Optional[str] = settings_dict["connection"].get("interface-name")
+
+ devices = await asyncio.wait_for(nm.get_devices(), timeout=5)
+ # Если interface-name не задан, пытаемся найти устройство по активному подключению
+ if not interface_name:
+ for device_path in devices:
+ device = NetworkDeviceGeneric(device_path)
+ try:
+ applied = await asyncio.wait_for(device.get_applied_connection(), timeout=3)
+ connection_info = applied[0].get("connection", {})
+ active_id = connection_info.get("id", ("s", None))[1]
+ if active_id == args["connection_id"]:
+ interface_name = await asyncio.wait_for(device.interface, timeout=3)
+ break
+ except Exception as exc:
+ logger.debug("apply_ip_config: unable to derive interface from device %s: %s", device_path, exc)
+ continue
+
+ if not interface_name:
+ raise RuntimeError(
+ translations.gettext("Не удалось определить интерфейс для профиля {connection_id}").format(
+ connection_id=args["connection_id"]
+ )
+ )
- interface_name = settings_dict["connection"]["interface-name"]
- devices = await nm.get_devices()
for device_path in devices:
device = NetworkDeviceGeneric(device_path)
- if await device.interface == interface_name:
- await device.disconnect()
- print(f"Отключён от {interface_name}")
- await nm.activate_connection(connection=connection_paths[0],device=device_path)
- print(f"Переподключён к {args['connection_id']}")
+ if await asyncio.wait_for(device.interface, timeout=3) == interface_name:
+ await asyncio.wait_for(device.disconnect(), timeout=5)
+ logger.info("apply_ip_config: disconnected from %s", interface_name)
+ await asyncio.wait_for(
+ nm.activate_connection(connection=connection_paths[0], device=device_path),
+ timeout=10,
+ )
+ logger.info("apply_ip_config: reconnected to %s", args.get("connection_id"))
return
- raise RuntimeError(f"Устройство для интерфейса {interface_name} не найдено")
\ No newline at end of file
+ raise RuntimeError(translations.gettext("Устройство для интерфейса {interface} не найдено").format(interface=interface_name))
diff --git a/SRV/services/network_service.py b/SRV/services/network_service.py
index b047808..edf0cc1 100644
--- a/SRV/services/network_service.py
+++ b/SRV/services/network_service.py
@@ -1,3 +1,15 @@
+"""
+Сервис чтения сетевого состояния через NetworkManager (D-Bus).
+
+Назначение:
+
+- Предоставляет асинхронные методы для сборки унифицированных моделей сетевых интерфейсов (Ethernet/Wi-Fi), их IP-конфигураций, а также получения имени хоста. Используется API-слоем для визуализации и управления.
+
+Особенности:
+
+- Фильтрация loopback и явно неактивных интерфейсов.
+- Адаптация данных D-Bus в pydantic-модели (`NetworkInterface`, `WiFiDevice`, `NetworkInterfacesContainer`).
+"""
from typing import List, Dict, Tuple, Union, Optional
from sdbus_async.networkmanager import (
NetworkManager,
@@ -20,7 +32,19 @@ from models.network import (
WiFiDevice,
)
import subprocess
+from utils.i18n import _, _load_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:
"""Сервис для взаимодействия с NetworkManager через D-Bus.
@@ -29,7 +53,7 @@ class NetworkService:
их конфигурациях IP, Wi-Fi и состояниях.
"""
- def __init__(self):
+ def __init__(self, translations: Translations = None):
"""Инициализирует сервис NetworkManager.
Устанавливает системный D-Bus и создаёт объекты для взаимодействия с
@@ -38,6 +62,7 @@ class NetworkService:
self.system_bus = sd_bus_open_system()
self.nm = NetworkManager(self.system_bus)
self.nm_settings = NetworkManagerSettings(self.system_bus)
+ self.translations = translations or _load_translations("ru")
async def get_ip_method(self, device: NetworkDeviceGeneric, ip_type: str) -> str:
"""Получает метод IP (например, "auto", "manual") для указанного устройства.
@@ -138,7 +163,7 @@ class NetworkService:
wifi_info = {
"ssid": None,
"bssid": None,
- "bitrate": await wifi_device.bitrate,
+ "bitrate": _extract_value(await wifi_device.bitrate),
"strength": None,
"frequency": None,
"mode": None,
@@ -146,11 +171,11 @@ class NetworkService:
if active_ap != "/":
ap = AccessPoint(active_ap, self.system_bus)
wifi_info.update({
- "ssid": await ap.ssid,
- "bssid": await ap.hw_address,
- "strength": await ap.strength,
- "frequency": await ap.frequency,
- "mode": await ap.mode,
+ "ssid": _extract_value(await ap.ssid),
+ "bssid": _extract_value(await ap.hw_address),
+ "strength": _extract_value(await ap.strength),
+ "frequency": _extract_value(await ap.frequency),
+ "mode": _extract_value(await ap.mode),
})
return wifi_info
@@ -195,8 +220,8 @@ class NetworkService:
"device_mtu": str(await generic_device.mtu),
"ipv4": IPConfig(configurations=await self.get_ip_config(generic_device, "4")),
"ipv6": IPConfig(configurations=await self.get_ip_config(generic_device, "6")),
- "x": initial_positions.get(interface_name, (100, 100))[0],
- "y": initial_positions.get(interface_name, (100, 100))[1],
+ "container_x": initial_positions.get(interface_name, (100, 100))[0],
+ "container_y": initial_positions.get(interface_name, (100, 100))[1],
}
if device_type == "WIFI":
@@ -226,9 +251,9 @@ class NetworkService:
hostname = result.stdout.strip()
return {"hostname": hostname}
except subprocess.CalledProcessError as e:
- raise RuntimeError(f"Failed to execute hostname: {e}")
+ raise RuntimeError(self.translations.gettext("Не удалось выполнить команду hostname: {error}").format(error=str(e)))
except Exception as e:
- raise RuntimeError(f"Error processing hostname output: {e}")
+ raise RuntimeError(self.translations.gettext("Ошибка обработки вывода hostname: {error}").format(error=str(e)))
async def get_interfaces_by_state(
self,
@@ -368,4 +393,4 @@ class NetworkService:
NetworkInterfacesContainer: Объект с неактивными интерфейсами и координатами.
"""
interfaces = await self.get_deactivated_interfaces(initial_positions)
- return NetworkInterfacesContainer(interfaces=interfaces, container_x=container_x, container_y=container_y)
\ No newline at end of file
+ return NetworkInterfacesContainer(interfaces=interfaces, container_x=container_x, container_y=container_y)
diff --git a/SRV/services/settings_service.py b/SRV/services/settings_service.py
index 4b6d9cc..f5ac7d0 100644
--- a/SRV/services/settings_service.py
+++ b/SRV/services/settings_service.py
@@ -1,5 +1,10 @@
import json
import logging
+from typing import Optional
+
+from utils.i18n import _load_translations
+from babel.support import Translations
+from pathlib import Path
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@@ -7,12 +12,17 @@ logger = logging.getLogger(__name__)
class SettingsService:
"""Сервис для управления настройками и очистки данных сети."""
- def __init__(self):
- self.subnet_file = "storage/devices/devices.json"
+ def __init__(self, translations: Translations = None):
+ self.subnet_base_path = "storage/subnets"
self.wifi_file = "storage/wifi/wifi.json"
+ self.translations = translations or _load_translations("ru")
- def clear_subnet(self) -> bool:
- """Очищает данные узлов подсети в файле devices.json.
+ def clear_subnet(self, subnet: Optional[str] = None) -> bool:
+ """Очищает данные узлов подсети в указанном файле или во всех файлах.
+
+ Args:
+ subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+ Если None, очищает все подсети.
Returns:
bool: True, если очистка успешна.
@@ -21,13 +31,27 @@ class SettingsService:
Exception: Если произошла ошибка при записи в файл.
"""
try:
- with open(self.subnet_file, "w") as f:
- json.dump([], f)
- logger.info("Данные узлов подсети успешно очищены")
+ if subnet:
+ file_path = Path(self.subnet_base_path) / f"{subnet.replace('/', '_')}.json"
+ if file_path.exists():
+ with open(file_path, "w") as f:
+ json.dump([], f)
+ logger.info(f"Subnet nodes data cleared for {subnet}")
+ else:
+ logger.info(f"Subnet file not found for {subnet}, clearing considered successful")
+ return True
+ else:
+ for file_path in Path(self.subnet_base_path).glob("*.json"):
+ with open(file_path, "w") as f:
+ json.dump([], f)
+ logger.info(f"Cleared subnet data in {file_path}")
+ return True
+ except FileNotFoundError:
+ logger.info("No subnet files found, clearing considered successful")
return True
except Exception as e:
- logger.error(f"Ошибка очистки узлов подсети: {str(e)}")
- raise
+ logger.error(f"Error clearing subnet nodes: {str(e)}")
+ raise Exception(self.translations.gettext("Ошибка очистки узлов подсети: {error}").format(error=str(e)))
def clear_wifi(self) -> bool:
"""Очищает данные точек доступа Wi-Fi в файле wifi.json.
@@ -41,14 +65,17 @@ class SettingsService:
try:
with open(self.wifi_file, "w") as f:
json.dump([], f)
- logger.info("Данные точек доступа Wi-Fi успешно очищены")
+ logger.info("Wi-Fi access points data successfully cleared")
+ return True
+ except FileNotFoundError:
+ logger.info("Wi-Fi file not found, clearing considered successful")
return True
except Exception as e:
- logger.error(f"Ошибка очистки точек доступа Wi-Fi: {str(e)}")
- raise
+ logger.error(f"Error clearing Wi-Fi access points: {str(e)}")
+ raise Exception(self.translations.gettext("Ошибка очистки точек доступа Wi-Fi: {error}").format(error=str(e)))
def clear_all_data(self) -> bool:
- """Очищает данные узлов подсети и точек доступа Wi-Fi в обоих файлах.
+ """Очищает данные узлов всех подсетей и точек доступа Wi-Fi.
Returns:
bool: True, если очистка успешна.
@@ -57,12 +84,20 @@ class SettingsService:
Exception: Если произошла ошибка при записи в один из файлов.
"""
try:
- with open(self.subnet_file, "w") as f:
- json.dump([], f)
- with open(self.wifi_file, "w") as f:
- json.dump([], f)
- logger.info("Все сетевые данные успешно очищены")
+ try:
+ for file_path in Path(self.subnet_base_path).glob("*.json"):
+ with open(file_path, "w") as f:
+ json.dump([], f)
+ logger.info(f"Cleared subnet data in {file_path}")
+ except FileNotFoundError:
+ logger.info("No subnet files found, clearing considered successful")
+ try:
+ with open(self.wifi_file, "w") as f:
+ json.dump([], f)
+ except FileNotFoundError:
+ logger.info("Wi-Fi file not found, clearing considered successful")
+ logger.info("All network data successfully cleared")
return True
except Exception as e:
- logger.error(f"Ошибка очистки всех сетевых данных: {str(e)}")
- raise
\ No newline at end of file
+ logger.error(f"Error clearing all network data: {str(e)}")
+ raise Exception(self.translations.gettext("Ошибка очистки всех сетевых данных: {error}").format(error=str(e)))
diff --git a/SRV/services/subnet_service.py b/SRV/services/subnet_service.py
index 5384343..3f0655d 100644
--- a/SRV/services/subnet_service.py
+++ b/SRV/services/subnet_service.py
@@ -13,206 +13,224 @@ from pathlib import Path
from datetime import datetime, timedelta
import subprocess
-from models.subnet import SubnetScanResult, Device
-from config.ports import port_config
-from scapy.layers.inet import IP, TCP
from scapy.layers.l2 import ARP, Ether
-from scapy.sendrecv import srp, sr1
+from scapy.sendrecv import srp
+from utils.i18n import get_translations
+from babel.support import Translations
+from fastapi import Depends
+
+from models.subnet import SubnetScanResult, Device
logging.basicConfig(
level=logging.INFO,
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
logger = logging.getLogger(__name__)
+def get_subnet_service(translations: Translations = Depends(get_translations)):
+ """Фабричная функция для создания экземпляра SubnetService.
+
+ Использует зависимости FastAPI для получения объекта переводов и возвращает
+ экземпляр SubnetService с текущей локализацией.
+
+ Args:
+ translations (Translations): Объект переводов для текущего языка.
+
+ Returns:
+ SubnetService: Экземпляр сервиса для работы с подсетями.
+ """
+ return SubnetService(translations)
+
class NetworkScanner:
"""Класс для сканирования подсети с использованием ARP-запросов.
- Выполняет обнаружение устройств в указанной подсети через ARP-пакеты.
- """
+ Назначение:
+ Обнаруживает активные устройства в локальной сети, отправляя широковещательные
+ ARP-запросы по указанной подсети и собирая ответы (IP, MAC).
- def __init__(self, interface: str):
- """Инициализирует сканер сети с указанным сетевым интерфейсом.
+ Детали реализации:
+ - Использует `scapy` (`Ether` + `ARP`) для генерации пакетов и `srp` для
+ отправки/получения на канальном уровне от имени заданного интерфейса.
+ - Таймаут ожидания ответа короткий (2 секунды), ориентирован на интерактивные
+ сценарии UI.
+
+ Ограничения и замечания:
+ - Требуются права для отправки низкоуровневых пакетов (capabilities/root).
+ - В сетях с фильтрацией широковещания количество обнаруженных устройств
+ может быть ограничено.
+ """
+ def __init__(self, interface: str, translations: Translations):
+ """Инициализирует сканер сети.
+
+ Устанавливает интерфейс для сканирования и объект переводов.
Args:
- interface (str): Название сетевого интерфейса (например, "eth0").
+ interface (str): Имя сетевого интерфейса (например, "wlp0s20f3").
+ translations (Translations): Объект переводов для текущего языка.
"""
self.interface = interface
conf.iface = interface
+ self.translations = translations
def scan(self, subnet: str) -> List[Tuple[str, str]]:
"""Сканирует подсеть с использованием ARP-запросов.
+ Отправляет ARP-пакеты для обнаружения активных устройств и возвращает
+ список кортежей (IP, MAC).
+
Args:
- subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
+ subnet (str): Подсеть в формате CIDR (например, "10.1.1.0/24").
Returns:
- List[Tuple[str, str]]: Список кортежей (IP-адрес, MAC-адрес) обнаруженных
- устройств.
+ List[Tuple[str, str]]: Список кортежей (IP-адрес, MAC-адрес) обнаруженных устройств.
Raises:
- Exception: Если произошла ошибка при отправке ARP-пакетов.
+ Exception: Если произошла ошибка сканирования.
"""
+ try:
+ arp = ARP(pdst=subnet)
+ except Exception as e:
+ logger.error(f"Error checking status: {str(e)}")
try:
arp = ARP(pdst=subnet)
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = ether / arp
- result = srp(packet,
- timeout=2,
- verbose=False,
- iface=self.interface)[0]
- return [(received.psrc, received.hwsrc)
- for sent, received in result]
+ result = srp(packet, timeout=2, verbose=False, iface=self.interface)[0]
+ return [(received.psrc, received.hwsrc) for sent, received in result]
except Exception as e:
- logger.error(f"Ошибка ARP-сканирования подсети {subnet}: {str(e)}")
+ logger.error(f"Error scanning subnet {subnet}: {str(e)}")
return []
class PortScanner:
"""Класс для сканирования открытых портов на устройствах.
- Выполняет сканирование портов с использованием TCP SYN-запросов.
- """
+ Назначение:
+ Плейсхолдер для будущего функционала активного сканирования TCP/UDP-портов
+ у обнаруженных узлов. В текущей версии возвращает пустой список.
- def __init__(self, interface: str):
- """Инициализирует сканер портов с указанным сетевым интерфейсом.
+ План развития:
+ - Добавить неблокирующее сканирование наиболее распространённых портов
+ (22, 80, 443 и т.д.) с ограничением по времени.
+ - Настраиваемые профили глубины и скорости сканирования.
+ """
+ def __init__(self, interface: str, translations: Translations):
+ """Инициализирует сканер портов.
+
+ Устанавливает интерфейс для сканирования и объект переводов.
Args:
- interface (str): Название сетевого интерфейса (например, "eth0").
+ interface (str): Имя сетевого интерфейса.
+ translations (Translations): Объект переводов для текущего языка.
"""
self.interface = interface
+ self.translations = translations
def scan(self, ip: str) -> List[str]:
"""Сканирует открытые порты на указанном IP-адресе.
+ Пока возвращает пустой список, так как функциональность не реализована.
+
Args:
ip (str): IP-адрес устройства для сканирования.
Returns:
- List[str]: Список имён открытых портов (например, "HTTP", "SSH").
-
- Raises:
- Exception: Если произошла ошибка при сканировании портов.
+ List[str]: Список открытых портов (пока пустой).
"""
- open_ports = []
-
- def check_port(port: int) -> Optional[str]:
- """Проверяет доступность конкретного порта.
-
- Args:
- port (int): Номер порта для проверки.
-
- Returns:
- Optional[str]: Имя порта, если открыт, иначе None.
- """
- try:
- pkt = sr1(
- IP(dst=ip) / TCP(dport=port, flags="S"),
- timeout=2,
- verbose=False,
- )
- if pkt and pkt.haslayer(TCP) and pkt[TCP].flags == 0x12:
- return port_config.get_port_name(port)
- return None
- except Exception as e:
- logger.debug(
- f"Ошибка проверки порта {port} для IP {ip}: {str(e)}")
- return None
-
- try:
- max_workers = min(len(port_config.get_ports()), 15)
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
- results = executor.map(check_port, port_config.get_ports())
- open_ports = [port for port in results if port is not None]
- logger.debug(f"Открытые порты для IP {ip}: {open_ports}")
- return open_ports
- except Exception as e:
- logger.warning(
- f"Ошибка при сканировании портов для IP {ip}: {str(e)}")
- return []
+ return []
class DeviceInfoCollector:
"""Класс для сбора информации об устройствах, включая вендоров и хостнеймы.
- Управляет кэшированием данных NetBIOS и DNS.
- """
+ Источники данных:
+ - MacLookup: сопоставление префикса MAC (OUI) с названием производителя.
+ - DNS: `gethostbyaddr` с малым таймаутом для обратного разрешения имени.
+ - NetBIOS (nbtscan): опрос сегмента сети для извлечения NetBIOS-имён.
- def __init__(self):
+ Политика кэширования:
+ - Результаты `get_vendor` кэшируются с помощью `functools.lru_cache`.
+ - Результаты `nbtscan` аккумулируются в словарях `nbtscan_cache`/`nbtscan_results`
+ на время жизни экземпляра.
+ """
+ def __init__(self, translations: Translations):
"""Инициализирует коллектор информации об устройствах.
- Создаёт объект MacLookup для определения вендоров и инициализирует кэши.
+ Настраивает кэширование и объект переводов.
+
+ Args:
+ translations (Translations): Объект переводов для текущего языка.
"""
self.mac_lookup = MacLookup()
self.nbtscan_cache = {}
self.nbtscan_results = {}
+ self.translations = translations
@lru_cache(maxsize=1000)
def get_vendor(self, mac: str) -> str:
- """Определяет вендора по MAC-адресу с использованием кэширования.
+ """Получает вендора устройства по MAC-адресу.
+
+ Использует кэширование для повышения производительности.
Args:
- mac (str): MAC-адрес устройства в формате XX:XX:XX:XX:XX:XX.
+ mac (str): MAC-адрес устройства.
Returns:
- str: Название вендора или "Unknown", если вендор не определён.
-
- Raises:
- Exception: Если произошла ошибка при запросе к базе вендоров.
+ str: Название вендора или "Неизвестный" в случае ошибки.
"""
try:
return self.mac_lookup.lookup(mac)
except Exception as e:
- logger.warning(
- f"Не удалось определить производителя для MAC {mac}: {str(e)}")
- return "Unknown"
+ logger.warning(f"Failed to determine vendor for MAC {mac}: {str(e)}")
+ return self.translations.gettext("Неизвестный")
def scan_nbtscan(self, subnet: str) -> None:
- """Выполняет сканирование NetBIOS для указанной подсети.
+ """Сканирует подсеть с помощью nbtscan для получения хостнеймов.
+
+ Сохраняет результаты в кэш и логирует процесс.
Args:
- subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
-
- Сохраняет результаты в кэш, игнорируя хостнеймы "".
+ subnet (str): Подсеть в формате CIDR.
"""
if subnet not in self.nbtscan_results:
try:
- logger.debug(f"Запуск nbtscan для подсети {subnet}")
+ logger.debug(f"Starting nbtscan for subnet {subnet}")
result = subprocess.run(["sudo", "nbtscan", "-r", str(subnet)],
capture_output=True,
text=True,
check=False,
timeout=10)
- logger.debug(f"Полный вывод nbtscan: {result.stdout}")
+ logger.debug(f"Full nbtscan output: {result.stdout}")
if result.returncode == 0:
for line in result.stdout.splitlines():
- logger.debug(f"Обработка строки: {line}")
+ logger.debug(f"Processing line: {line}")
if not line.startswith(("IP address", "-", "Doing NBT")):
parts = [p for p in line.split() if p]
if len(parts) >= 2:
ip_addr, hostname = parts[0], parts[1]
if hostname != "":
self.nbtscan_cache[ip_addr] = hostname
- logger.info(f"NetBIOS: Найден хостнейм {hostname} для IP {ip_addr}")
+ logger.info(f"NetBIOS: Found hostname {hostname} for IP {ip_addr}")
self.nbtscan_results[subnet] = True
except subprocess.TimeoutExpired:
- logger.error(f"NetBIOS: Таймаут для подсети {subnet}")
+ logger.error(f"NetBIOS: Timeout for subnet {subnet}")
except Exception as e:
- logger.error(f"NetBIOS: Ошибка для подсети {subnet}: {str(e)}")
+ logger.error(f"NetBIOS: Error for subnet {subnet}: {str(e)}")
def get_hostname(self, ip: str) -> Optional[str]:
- """Определяет хостнейм для указанного IP-адреса.
+ """Получает хостнейм для указанного IP-адреса.
+
+ Пробует сначала DNS, затем NetBIOS.
Args:
ip (str): IP-адрес устройства.
Returns:
- Optional[str]: Хостнейм, если найден, иначе None.
-
- Сначала пытается использовать DNS, затем NetBIOS через nbtscan.
+ Optional[str]: Хостнейм или None, если не удалось определить.
"""
try:
socket.setdefaulttimeout(2)
return socket.gethostbyaddr(ip)[0]
except (socket.herror, socket.gaierror, socket.timeout):
- logger.debug(f"DNS: Не удалось разрешить имя для IP {ip}")
+ logger.debug(f"DNS: Failed to resolve name for IP {ip}")
subnet = ipaddress.ip_network(f"{ip}/24", strict=False)
if subnet not in self.nbtscan_results:
@@ -221,41 +239,70 @@ class DeviceInfoCollector:
if ip in self.nbtscan_cache:
return self.nbtscan_cache[ip]
- logger.debug(f"Не удалось определить хостнейм для IP {ip} через DNS или NetBIOS")
+ logger.debug(f"Failed to determine hostname for IP {ip} via DNS or NetBIOS")
return None
class DeviceStorage:
"""Класс для хранения и управления данными об устройствах.
- Хранит устройства в JSON-файле и удаляет устаревшие записи.
- """
+ Формат файла подсети::
- def __init__(self, storage_path: str = "storage/devices/devices.json"):
- """Инициализирует хранилище устройств с указанным путём.
+ {
+ "devices": [Device, ...]
+ }
+
+ Политика очистки:
+
+ - Метод `_clean_old_devices` удаляет оффлайн-устройства, не наблюдавшиеся более 7 дней,
+ чтобы поддерживать актуальность списка.
+ """
+ def __init__(self, base_path: str = "storage/subnets", translations: Translations = None):
+ """Инициализирует хранилище устройств.
+
+ Создаёт директорию, если её нет, и задаёт параметры очистки.
Args:
- storage_path (str, optional): Путь к файлу хранения.
- По умолчанию "storage/devices/devices.json".
-
- Создаёт родительские директории, если они не существуют.
+ base_path (str): Путь к директории хранения данных.
+ translations (Translations, optional): Объект переводов.
"""
- self.storage_file = Path(storage_path)
- self.storage_file.parent.mkdir(parents=True, exist_ok=True)
+ self.base_path = Path(base_path)
+ self.base_path.mkdir(parents=True, exist_ok=True)
self.offline_timeout = timedelta(days=7)
+ self.translations = translations or Translations.load("locales", ["ru"], "messages") # Fallback на русский
- def load_devices(self) -> Dict[str, Device]:
- """Загружает устройства из файла хранения.
+ def get_subnet_file(self, subnet: str) -> Path:
+ """Возвращает путь к файлу для конкретной подсети.
+
+ Заменяет символы '/' на '_' для корректного имени файла.
+
+ Args:
+ subnet (str): Подсеть в формате CIDR.
Returns:
- Dict[str, Device]: Словарь устройств, где ключ — MAC-адрес.
-
- Удаляет устройства с неверным форматом last_seen и возвращает пустой словарь
- при ошибке.
+ Path: Путь к файлу.
"""
+ sanitized_subnet = subnet.replace("/", "_")
+ return self.base_path / f"{sanitized_subnet}.json"
+
+ def load_devices(self, subnet: str) -> Dict[str, Device]:
+ """Загружает устройства для указанной подсети.
+
+ Читает данные из JSON-файла и фильтрует устаревшие устройства.
+
+ Args:
+ subnet (str): Подсеть в формате CIDR.
+
+ Returns:
+ Dict[str, Device]: Словарь устройств с MAC как ключом.
+ """
+ file_path = self.get_subnet_file(subnet)
try:
- if self.storage_file.exists():
- with open(self.storage_file, "r", encoding="utf-8") as f:
+ if file_path.exists():
+ with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
+ if not isinstance(data, dict) or "devices" not in data:
+ logger.warning(f"Invalid JSON structure in {file_path}, expected {'devices': [...]}, got {data}")
+ return {}
devices = {}
for device_data in data.get("devices", []):
device = Device(**device_data)
@@ -263,19 +310,23 @@ class DeviceStorage:
devices[device.mac] = device
return self._clean_old_devices(devices)
return {}
+ except json.JSONDecodeError as e:
+ logger.error(f"JSON decode error in {file_path}: {str(e)}")
+ return {}
except Exception as e:
- logger.error(f"Ошибка при загрузке устройств: {str(e)}")
+ logger.error(f"Error loading devices for subnet {subnet}: {str(e)}")
return {}
def _clean_old_devices(self, devices: Dict[str, Device]) -> Dict[str, Device]:
- """Очищает устаревшие устройства из словаря.
+ """Очищает список устройств от устаревших оффлайн-устройств.
+
+ Удаляет устройства, которые оффлайн более 7 дней.
Args:
- devices (Dict[str, Device]): Словарь устройств с MAC-адресами как ключами.
+ devices (Dict[str, Device]): Словарь устройств.
Returns:
- Dict[str, Device]: Отфильтрованный словарь, исключающий устройства
- offline более 7 дней.
+ Dict[str, Device]: Отфильтрованный словарь устройств.
"""
now = datetime.now()
filtered = {}
@@ -286,89 +337,229 @@ class DeviceStorage:
if now - last_seen <= self.offline_timeout:
filtered[mac] = device
else:
- logger.info(f"Удалено устройство {mac} (offline > 7 дней)")
+ logger.info(f"Removed device {mac} (offline > 7 days)")
except ValueError:
- logger.warning(f"Неверный формат last_seen для устройства {mac}")
+ logger.warning(f"Invalid last_seen format for device {mac}")
else:
filtered[mac] = device
return filtered
- def save_devices(self, devices: List[Device]) -> None:
- """Сохраняет список устройств в файл.
+ def save_devices(self, subnet: str, devices: List[Device]) -> None:
+ """Сохраняет устройства для указанной подсети.
+
+ Записывает данные в JSON-файл.
Args:
- devices (List[Device]): Список объектов Device для сохранения.
+ subnet (str): Подсеть в формате CIDR.
+ devices (List[Device]): Список устройств для сохранения.
Raises:
- Exception: Если произошла ошибка при записи в файл.
+ Exception: Если произошла ошибка при сохранении.
"""
+ file_path = self.get_subnet_file(subnet)
try:
data = SubnetScanResult(devices=devices).model_dump()
- with open(self.storage_file, "w", encoding="utf-8") as f:
+ with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
- logger.info(f"Сохранено {len(devices)} устройств в {self.storage_file}")
+ logger.info(f"Saved {len(devices)} devices to {file_path}")
except Exception as e:
- logger.error(f"Ошибка при сохранении устройств: {str(e)}")
+ logger.error(f"Error saving devices for subnet {subnet}: {str(e)}")
+ if self.translations:
+ raise Exception(self.translations.gettext("Ошибка при сохранении устройств: {error}").format(error=str(e)))
+ else:
+ raise Exception(f"Ошибка при сохранении устройств: {str(e)}")
class SubnetService:
"""Сервис для сканирования подсетей и управления устройствами.
- Объединяет функционал сканирования сети, портов и хранения данных об устройствах.
+ Роль сервиса:
+ Объединяет сетевое сканирование (ARP), сбор дополнительной информации
+ (вендор, хостнейм), слияние с кэшем предыдущих результатов и персистентное
+ хранение данных по подсетям. Предоставляет вспомогательные операции для
+ локального узла и работы с заметками устройств.
+
+ Поток выполнения скана (`scan_subnet`):
+ 1) Определение подсети (вручную или авто по интерфейсу).
+ 2) Загрузка ранее сохранённых устройств подсети.
+ 3) Добавление локального устройства (если принадлежит подсети).
+ 4) ARP-сканирование и параллельная обработка узлов (обогащение данных).
+ 5) Маркировка оффлайн-узлов из кэша, не встреченных в текущем скане.
+ 6) Сортировка по IP и сохранение объединённого списка в JSON.
"""
+ def __init__(self, translations: Translations):
+ """Инициализирует сервис подсети.
- def __init__(self):
- """Инициализирует сервис подсети с настройкой всех компонентов.
+ Настраивает интерфейс, сканеры и хранилище с текущей локализацией.
- Автоматически определяет интерфейс и инициализирует сканеры и хранилище.
+ Args:
+ translations (Translations): Объект переводов для текущего языка.
"""
self.interface = self._get_default_interface()
- self.network_scanner = NetworkScanner(self.interface)
- self.port_scanner = PortScanner(self.interface)
- self.device_info = DeviceInfoCollector()
- self.storage = DeviceStorage()
+ self.network_scanner = NetworkScanner(self.interface, translations)
+ self.port_scanner = PortScanner(self.interface, translations)
+ self.device_info = DeviceInfoCollector(translations)
+ self.storage = DeviceStorage(translations=translations)
+ self.translations = translations
+ logger.info(f"Initialized SubnetService with interface: {self.interface}")
def _get_default_interface(self) -> str:
- """Определяет имя сетевого интерфейса по умолчанию.
+ """Определяет интерфейс по умолчанию.
+
+ Использует информацию о шлюзе или первый доступный интерфейс.
Returns:
- str: Название интерфейса (например, "eth0").
+ str: Имя интерфейса (например, "wlp0s20f3" или "eth0").
- Использует информацию о шлюзе или первый доступный интерфейс, отличный от "lo".
+ Raises:
+ Exception: Если не удалось определить интерфейс.
"""
try:
gateways = netifaces.gateways()
default_gateway = gateways.get("default", {}).get(netifaces.AF_INET)
if default_gateway:
- return default_gateway[1]
+ iface = default_gateway[1]
+ logger.info(f"Default interface from gateway: {iface}")
+ return iface
interfaces = netifaces.interfaces()
for iface in interfaces:
if iface != "lo":
- return iface
+ addrs = netifaces.ifaddresses(iface)
+ if netifaces.AF_INET in addrs:
+ logger.info(f"Selected interface: {iface}")
+ return iface
+ logger.warning("No suitable interface found, defaulting to eth0")
return "eth0"
except Exception as e:
- logger.warning(f"Не удалось определить интерфейс: {str(e)}")
+ logger.warning(f"Failed to determine interface: {str(e)}, defaulting to eth0")
return "eth0"
- def scan_subnet(self, subnet: str, ports: List[int] = None) -> SubnetScanResult:
+ def _get_prefix_from_ip_cmd(self, iface: str) -> Optional[int]:
+ """Пытается получить префикс подсети из вывода `ip addr`, если netifaces дал неточную маску."""
+ try:
+ result = subprocess.run(
+ ["ip", "-o", "-f", "inet", "addr", "show", "dev", iface],
+ capture_output=True,
+ text=True,
+ check=False,
+ timeout=2,
+ )
+ if result.returncode != 0 or not result.stdout:
+ return None
+ # пример строки: "3: wlp0s20f3 inet 10.1.1.31/24 brd 10.1.1.255 scope global dynamic noprefixroute wlp0s20f3"
+ for part in result.stdout.split():
+ if "/" in part and part.count(".") == 3:
+ try:
+ return int(part.split("/")[1])
+ except ValueError:
+ continue
+ return None
+ except Exception as e:
+ logger.debug(f"ip addr prefix detection failed for {iface}: {e}")
+ return None
+
+ def _get_prefix_from_proc_route(self, iface: str) -> Optional[int]:
+ """Читает маску для интерфейса из /proc/net/route (не требует прав netlink)."""
+ try:
+ with open("/proc/net/route") as f:
+ for line in f:
+ parts = line.strip().split()
+ if len(parts) < 8 or parts[0] != iface:
+ continue
+ mask_hex = parts[7]
+ try:
+ mask_int = int(mask_hex, 16)
+ except ValueError:
+ continue
+ # mask_int в host byte order, например FF000000 для 255.0.0.0
+ mask_str = socket.inet_ntoa(mask_int.to_bytes(4, byteorder="little"))
+ return ipaddress.ip_network(f"0.0.0.0/{mask_str}", strict=False).prefixlen
+ return None
+ except Exception as e:
+ logger.debug(f"/proc/net/route prefix detection failed for {iface}: {e}")
+ return None
+
+ def _get_prefix_from_broadcast(self, ip: str, broadcast: Optional[str]) -> Optional[int]:
+ """Вычисляет префикс из пары IP + broadcast, если она присутствует (часто точнее для 10.x)."""
+ if not ip or not broadcast:
+ return None
+ try:
+ ip_int = int(ipaddress.IPv4Address(ip))
+ bcast_int = int(ipaddress.IPv4Address(broadcast))
+ mask_int = ~(ip_int ^ bcast_int) & 0xFFFFFFFF
+ mask = ipaddress.ip_network(f"0.0.0.0/{ipaddress.IPv4Address(mask_int)}", strict=False).prefixlen
+ return mask
+ except Exception as e:
+ logger.debug(f"Broadcast-based prefix detection failed for {ip}/{broadcast}: {e}")
+ return None
+
+ def _get_default_subnet(self) -> str:
+ """Автоматически определяет подсеть локального устройства.
+
+ Использует информацию о текущем интерфейсе для определения подсети.
+
+ Returns:
+ str: Подсеть в формате CIDR (например, "10.1.1.0/24").
+
+ Raises:
+ Exception: Если не удалось определить подсеть.
+ """
+ try:
+ interfaces = netifaces.interfaces()
+ for iface in interfaces:
+ if iface == "lo" or iface != self.interface:
+ continue
+ addrs = netifaces.ifaddresses(iface)
+ if netifaces.AF_INET in addrs:
+ ip_info = addrs[netifaces.AF_INET][0]
+ ip = ip_info["addr"]
+ netmask = ip_info["netmask"]
+ broadcast = ip_info.get("broadcast")
+ # netifaces иногда отдаёт неточную маску (например, 255.0.0.0 для 10.x),
+ # пробуем уточнить через `ip addr`, затем /proc/net/route, затем по broadcast, и только потом берём netmask.
+ mask = (
+ self._get_prefix_from_ip_cmd(iface)
+ or self._get_prefix_from_proc_route(iface)
+ or self._get_prefix_from_broadcast(ip, broadcast)
+ or ipaddress.ip_network(f"0.0.0.0/{netmask}", strict=False).prefixlen
+ )
+ network = ipaddress.ip_network(f"{ip}/{mask}", strict=False)
+ logger.info(f"Auto-detected subnet: {network} (IP: {ip}, Mask: {netmask} -> /{mask})")
+ return str(network)
+ logger.warning("No suitable subnet found, defaulting to 0.0.0.0/0")
+ return "0.0.0.0/0"
+ except Exception as e:
+ logger.error(f"Error determining default subnet: {str(e)}")
+ return "0.0.0.0/0"
+
+ def scan_subnet(self, subnet: Optional[str] = None) -> SubnetScanResult:
"""Сканирует указанную подсеть и возвращает результаты.
+ Выполняет ARP-сканирование, собирает информацию о устройствах и сохраняет данные.
+
Args:
- subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
- ports (List[int], optional): Список портов для сканирования.
- По умолчанию None (используются порты из config.ports).
+ subnet (Optional[str]): Подсеть в формате CIDR (например, "10.1.1.0/24").
+ Если None или пустая строка, определяется автоматически.
Returns:
SubnetScanResult: Объект с результатами сканирования, содержащий список
- устройств.
+ устройств или сообщение об ошибке.
- Сохраняет данные в хранилище и обновляет статус устройств.
+ Raises:
+ Exception: Если произошла ошибка сканирования.
"""
start_time = time.time()
devices = []
now = datetime.now().astimezone()
formatted_now = now.strftime("%d-%m-%Y, %H:%M")
try:
- saved_devices = self.storage.load_devices()
+ # Определяем подсеть, если не указана или пустая
+ if not subnet or subnet.strip() == "":
+ subnet = self._get_default_subnet()
+ if subnet == "0.0.0.0/0":
+ logger.warning("Default subnet is invalid, scan aborted")
+ return SubnetScanResult(devices=devices, error=self.translations.gettext("Недопустимая подсеть по умолчанию"))
+
+ saved_devices = self.storage.load_devices(subnet)
local_device = self._get_local_device(subnet)
if local_device:
@@ -377,19 +568,18 @@ class SubnetService:
devices.append(local_device)
if local_device.mac:
saved_devices[local_device.mac] = local_device
+ logger.info(f"Detected local device: {local_device.ip}, MAC: {local_device.mac}")
+ logger.info(f"Scanning subnet: {subnet} with interface: {self.interface}")
discovered_devices = self.network_scanner.scan(subnet)
+ logger.info(f"Discovered devices: {len(discovered_devices)}")
self.device_info.scan_nbtscan(subnet)
+ if not discovered_devices and not local_device:
+ logger.warning(f"No devices found in subnet {subnet}")
+ return SubnetScanResult(devices=[], error=self.translations.gettext("В данной подсети не обнаружено устройств"))
+
def process_device(ip_mac: Tuple[str, str]) -> Optional[Device]:
- """Обрабатывает устройство из пары IP и MAC.
-
- Args:
- ip_mac (Tuple[str, str]): Кортеж с IP-адресом и MAC-адресом.
-
- Returns:
- Optional[Device]: Объект Device или None, если устройство локальное.
- """
ip, mac = ip_mac
if local_device and ip == local_device.ip:
return None
@@ -401,20 +591,19 @@ class SubnetService:
mac=mac,
hostname=self.device_info.get_hostname(ip) or existing_device.hostname,
vendor=self.device_info.get_vendor(mac),
- open_ports=self.port_scanner.scan(ip) or existing_device.open_ports,
+ open_ports=existing_device.open_ports,
status="online",
last_seen=formatted_now,
note=existing_device.note,
)
vendor = self.device_info.get_vendor(mac)
hostname = self.device_info.get_hostname(ip)
- open_ports = self.port_scanner.scan(ip)
return Device(
ip=ip,
mac=mac,
hostname=hostname,
vendor=vendor,
- open_ports=open_ports,
+ open_ports=[],
status="online",
last_seen=formatted_now,
note=None,
@@ -435,26 +624,28 @@ class SubnetService:
for device in devices:
if device.mac:
saved_devices[device.mac] = device
- self.storage.save_devices(list(saved_devices.values()))
+ self.storage.save_devices(subnet, list(saved_devices.values()))
logger.info(
- f"Сканирование подсети {subnet} завершено за {time.time() - start_time:.2f} секунд, "
- f"найдено {len(devices)} устройств (активных: {len(active_macs)})"
+ f"Subnet scan {subnet} completed in {time.time() - start_time:.2f} seconds, "
+ f"found {len(devices)} devices (active: {len(active_macs)})"
)
return SubnetScanResult(devices=devices)
except Exception as e:
- logger.error(f"Ошибка при сканировании подсети {subnet}: {str(e)}")
- return SubnetScanResult(devices=devices)
+ logger.error(f"Error scanning subnet {subnet}: {str(e)}")
+ return SubnetScanResult(devices=devices, error=self.translations.gettext("Ошибка сканирования: {error}").format(error=str(e)))
def _get_local_device(self, subnet: str) -> Optional[Device]:
"""Определяет локальное устройство в указанной подсети.
+ Использует информацию об интерфейсе для обнаружения.
+
Args:
- subnet (str): Подсеть в формате CIDR (например, "192.168.1.0/24").
+ subnet (str): Подсеть в формате CIDR.
Returns:
- Optional[Device]: Объект локального устройства или None, если не найдено.
+ Optional[Device]: Объект устройства или None, если не найдено.
"""
now = datetime.now().astimezone()
formatted_now = now.strftime("%d-%m-%Y, %H:%M")
@@ -471,124 +662,132 @@ class SubnetService:
mac = addrs[netifaces.AF_LINK][0]["addr"]
vendor = self.device_info.get_vendor(mac)
hostname = self.device_info.get_hostname(ip)
- open_ports = self.port_scanner.scan(ip)
- logger.debug(
- f"Локальное устройство: IP={ip}, MAC={mac}, Ports={open_ports}, Hostname={hostname}"
- )
+ logger.info(f"Local device detected: IP={ip}, MAC={mac}, Interface={iface}")
return Device(
ip=ip,
mac=mac,
hostname=hostname,
vendor=vendor,
- open_ports=open_ports,
+ open_ports=[],
status="online",
last_seen=formatted_now,
note=None,
)
- logger.warning(
- f"Локальное устройство не найдено в подсети {subnet}")
+ logger.warning(f"Local device not found in subnet {subnet}")
return None
except Exception as e:
- logger.warning(
- f"Не удалось получить локальное устройство: {str(e)}")
+ logger.warning(f"Failed to get local device: {str(e)}")
return None
- def add_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]:
- """Добавляет заметку к устройству по MAC-адресу.
+ def add_note(self, mac: str, note: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]:
+ """Добавляет заметку для устройства по MAC-адресу.
+
+ Привязывает заметку к устройству, если оно существует.
Args:
mac (str): MAC-адрес устройства.
note (str): Текст заметки.
+ translations (Translations): Объект переводов для текущего языка.
Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении.
+ Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+ где результат содержит код ошибки/успеха и данные.
"""
try:
- saved_devices = self.storage.load_devices()
+ saved_devices = {}
+ for subnet in self.storage.base_path.glob("*.json"):
+ devices = self.storage.load_devices(subnet.stem.replace("_", "/"))
+ saved_devices.update(devices)
if mac not in saved_devices:
- logger.warning(f"Устройство с MAC {mac} не найдено")
- return False, f"Устройство с MAC {mac} не найдено"
+ logger.warning(f"Device with MAC {mac} not found")
+ return False, ("not_found", {"mac": mac})
if saved_devices[mac].note is not None:
- logger.error(
- f"Заметка для устройства с MAC {mac} уже существует")
- return False, f"Заметка для устройства с MAC {mac} уже существует"
- saved_devices[mac].note = note
- self.storage.save_devices(list(saved_devices.values()))
- logger.info(
- f"Добавлена заметка '{note}' для устройства с MAC {mac}")
- return True, None
+ logger.error(f"Note for device with MAC {mac} already exists")
+ return False, ("note_exists", {"mac": mac})
+ for subnet_file in self.storage.base_path.glob("*.json"):
+ subnet = subnet_file.stem.replace("_", "/")
+ devices = self.storage.load_devices(subnet)
+ if mac in devices:
+ devices[mac].note = note
+ self.storage.save_devices(subnet, list(devices.values()))
+ break
+ logger.info(f"Added note '{note}' for device with MAC {mac}")
+ return True, ("success", {"mac": mac, "note": note})
except Exception as e:
- logger.error(
- f"Ошибка при добавлении заметки для MAC {mac}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
+ logger.error(f"Error adding note for MAC {mac}: {str(e)}")
+ return False, ("internal_error", {"error": str(e)})
- def update_note(self, mac: str, note: str) -> Tuple[bool, Optional[str]]:
- """Обновляет заметку устройства по MAC-адресу.
+ def update_note(self, mac: str, note: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]:
+ """Обновляет заметку для устройства по MAC-адресу.
+
+ Изменяет существующую заметку, если она есть.
Args:
mac (str): MAC-адрес устройства.
note (str): Новый текст заметки.
+ translations (Translations): Объект переводов для текущего языка.
Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении.
+ Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+ где результат содержит код ошибки/успеха и данные.
"""
try:
- saved_devices = self.storage.load_devices()
+ saved_devices = {}
+ for subnet in self.storage.base_path.glob("*.json"):
+ devices = self.storage.load_devices(subnet.stem.replace("_", "/"))
+ saved_devices.update(devices)
if mac not in saved_devices:
- logger.warning(f"Устройство с MAC {mac} не найдено")
- return False, f"Устройство с MAC {mac} не найдено"
+ logger.warning(f"Device with MAC {mac} not found")
+ return False, ("not_found", {"mac": mac})
if saved_devices[mac].note is None:
- logger.error(
- f"Заметка для устройства с MAC {mac} не существует")
- return False, f"Заметка для устройства с MAC {mac} не существует"
- saved_devices[mac].note = note
- self.storage.save_devices(list(saved_devices.values()))
- logger.info(
- f"Обновлена заметка '{note}' для устройства с MAC {mac}")
- return True, None
+ logger.error(f"Note for device with MAC {mac} does not exist")
+ return False, ("note_not_exists", {"mac": mac})
+ for subnet_file in self.storage.base_path.glob("*.json"):
+ subnet = subnet_file.stem.replace("_", "/")
+ devices = self.storage.load_devices(subnet)
+ if mac in devices:
+ devices[mac].note = note
+ self.storage.save_devices(subnet, list(devices.values()))
+ break
+ logger.info(f"Updated note '{note}' for device with MAC {mac}")
+ return True, ("success", {"mac": mac, "note": note})
except Exception as e:
- logger.error(
- f"Ошибка при обновлении заметки для MAC {mac}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
+ logger.error(f"Error updating note for MAC {mac}: {str(e)}")
+ return False, ("internal_error", {"error": str(e)})
- def delete_note(self, mac: str) -> Tuple[bool, Optional[str]]:
- """Удаляет заметку устройства по MAC-адресу.
+ def delete_note(self, mac: str, translations: Translations) -> Tuple[bool, Optional[Tuple[str, Dict]]]:
+ """Удаляет заметку для устройства по MAC-адресу.
+
+ Удаляет привязанную заметку, если она существует.
Args:
mac (str): MAC-адрес устройства.
+ translations (Translations): Объект переводов для текущего языка.
Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении.
+ Tuple[bool, Optional[Tuple[str, Dict]]]: Кортеж (успех, результат),
+ где результат содержит код ошибки/успеха и данные.
"""
try:
- saved_devices = self.storage.load_devices()
+ saved_devices = {}
+ for subnet in self.storage.base_path.glob("*.json"):
+ devices = self.storage.load_devices(subnet.stem.replace("_", "/"))
+ saved_devices.update(devices)
if mac not in saved_devices:
- logger.warning(f"Устройство с MAC {mac} не найдено")
- return False, f"Устройство с MAC {mac} не найдено"
+ logger.warning(f"Device with MAC {mac} not found")
+ return False, ("not_found", {"mac": mac})
if saved_devices[mac].note is None:
- logger.error(
- f"Заметка для устройства с MAC {mac} не существует")
- return False, f"Заметка для устройства с MAC {mac} не существует"
- saved_devices[mac].note = None
- self.storage.save_devices(list(saved_devices.values()))
- logger.info(f"Удалена заметка для устройства с MAC {mac}")
- return True, None
+ logger.error(f"Note for device with MAC {mac} does not exist")
+ return False, ("note_not_exists", {"mac": mac})
+ for subnet_file in self.storage.base_path.glob("*.json"):
+ subnet = subnet_file.stem.replace("_", "/")
+ devices = self.storage.load_devices(subnet)
+ if mac in devices:
+ devices[mac].note = None
+ self.storage.save_devices(subnet, list(devices.values()))
+ break
+ logger.info(f"Deleted note for device with MAC {mac}")
+ return True, ("success", {"mac": mac})
except Exception as e:
- logger.error(
- f"Ошибка при удалении заметки для MAC {mac}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
\ No newline at end of file
+ logger.error(f"Error deleting note for MAC {mac}: {str(e)}")
+ return False, ("internal_error", {"error": str(e)})
diff --git a/SRV/services/ufw_service.py b/SRV/services/ufw_service.py
index f851ab3..b824288 100644
--- a/SRV/services/ufw_service.py
+++ b/SRV/services/ufw_service.py
@@ -8,9 +8,16 @@ DEBUG = True
class UFWService:
- """
- Сервис для взаимодействия с утилитой UFW (Uncomplicated Firewall) в Linux.
- Позволяет получить список правил UFW и управлять ими.
+ """Сервис взаимодействия с UFW (Uncomplicated Firewall).
+
+ Назначение:
+ - Чтение статуса и списка правил брандмауэра.
+ - Добавление, удаление и массовая обработка правил с поддержкой диапазонов портов,
+ комментариев и выбором протоколов.
+
+ Детали реализации:
+ - Команды выполняются через `sudo ufw ...`; вывод парсится в модели pydantic.
+ - Для операций удаления соблюдается порядок с конца, чтобы не нарушать нумерацию.
"""
def _run_command(self, command: str) -> str:
diff --git a/SRV/services/wifi_service.py b/SRV/services/wifi_service.py
index d82dccb..d5760b7 100644
--- a/SRV/services/wifi_service.py
+++ b/SRV/services/wifi_service.py
@@ -9,182 +9,149 @@ import pywifi
from models.wifi import WifiAccessPoint, WifiScanResult
from pathlib import Path
import json
+from utils.i18n import _load_translations
+from babel.support import Translations
+# Настройка логирования
logging.basicConfig(
level=logging.DEBUG,
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
logger = logging.getLogger(__name__)
class WifiStorage:
"""Класс для хранения данных о Wi-Fi сетях и их заметках по SSID.
- Предоставляет методы для загрузки и сохранения заметок, связанных с SSID сетей,
- в JSON-файле на диске.
+ Формат::
+
+ {
+ "networks": [{"ssid": str, "note": Optional[str]}]
+ }
+
+ Поведение:
+
+ - Создаёт директорию хранения при инициализации, ведёт логирование путей.
+ - Валидирует структуру при чтении и безопасно записывает JSON с Unicode и отступами.
"""
-
- def __init__(self, storage_path: str = "storage/wifi/wifi.json"):
- """Инициализирует объект хранилища Wi-Fi с указанным путём к файлу.
-
- Args:
- storage_path (str, optional): Путь к файлу для хранения заметок.
- По умолчанию "storage/wifi/wifi.json".
-
- Создаёт родительские директории, если они не существуют.
- """
+ def __init__(self, storage_path: str = "storage/wifi/wifi.json", translations: Translations = None):
self.storage_file = Path(storage_path)
self.storage_file.parent.mkdir(parents=True, exist_ok=True)
+ self.translations = translations or _load_translations("ru")
+ logger.info(f"Wi-Fi storage initialized at: {self.storage_file.absolute()}")
def load_notes(self) -> Dict[str, Optional[str]]:
- """Загружает заметки по SSID из файла хранения.
-
- Returns:
- Dict[str, Optional[str]]: Словарь, где ключ — SSID, значение — заметка
- или None, если заметки нет.
-
- Raises:
- Exception: Если произошла ошибка при чтении или парсинге файла.
- """
+ """Загружает заметки по SSID из файла хранения."""
try:
if self.storage_file.exists():
with open(self.storage_file, "r", encoding="utf-8") as f:
data = json.load(f)
+ if not isinstance(data, dict) or "networks" not in data:
+ logger.warning(f"Invalid JSON structure in {self.storage_file}, expected {'networks': [...]}, got {data}")
+ return {}
return {item["ssid"]: item.get("note") for item in data.get("networks", [])}
return {}
+ except json.JSONDecodeError as e:
+ logger.error(f"JSON decode error in {self.storage_file}: {str(e)}")
+ return {}
except Exception as e:
- logger.error(f"Ошибка при загрузке заметок: {str(e)}")
+ logger.error(f"Error loading notes from {self.storage_file}: {str(e)}")
return {}
def save_notes(self, notes: Dict[str, Optional[str]]) -> None:
- """Сохраняет заметки по SSID в файл.
-
- Args:
- notes (Dict[str, Optional[str]]): Словарь с SSID как ключами и заметками
- как значениями.
-
- Raises:
- Exception: Если произошла ошибка при записи в файл.
- """
+ """Сохраняет заметки по SSID в файл."""
try:
data = {"networks": [{"ssid": ssid, "note": note} for ssid, note in notes.items()]}
with open(self.storage_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
- logger.info(f"Сохранено {len(notes)} заметок в {self.storage_file}")
+ logger.info(f"Saved {len(notes)} notes to {self.storage_file.absolute()}")
+ except PermissionError as e:
+ logger.error(f"Permission denied writing to {self.storage_file}: {str(e)}")
+ raise
except Exception as e:
- logger.error(f"Ошибка при сохранении заметок: {str(e)}")
+ logger.error(f"Error saving notes to {self.storage_file}: {str(e)}")
+ raise Exception(self.translations.gettext("Ошибка при сохранении заметок: {error}").format(error=str(e)))
class WifiService:
- """Сервис для сканирования текущих Wi-Fi точек доступа.
+ """Сервис для сканирования текущих Wi-Fi точек доступа и заметок.
- Предоставляет функционал для обнаружения Wi-Fi сетей с использованием pywifi и iw,
- определения характеристик точек доступа (сигнал, шифрование, канал и т.д.),
- а также управления заметками.
+ Источники данных:
+ - `pywifi` для базового сканирования и получения списка AP.
+ - `iw dev scan` для уточнения частоты, сигнала и безопасности.
+ - `MacLookup` для определения вендора по BSSID.
+
+ Политики:
+ - Выбор лучшего AP по SSID по уровню сигнала.
+ - Кэширование определения вендора.
+ - Сохранение SSID даже без заметок для будущих операций.
"""
-
- def __init__(self):
- """Инициализирует сервис Wi-Fi с настройкой интерфейса и хранилища.
-
- Инициализирует объект MacLookup для определения вендоров по MAC-адресам,
- PyWiFi для работы с Wi-Fi-интерфейсами и объект хранилища заметок.
- """
+ def __init__(self, translations: Translations = None):
self.mac_lookup = MacLookup()
self.wifi = pywifi.PyWiFi()
self.interface = self._get_wifi_interface()
+ validated_translations = translations or _load_translations("ru")
+ if not hasattr(validated_translations, "gettext"):
+ logger.warning("Translations object does not support gettext, using fallback")
+ validated_translations = _load_translations("ru")
+ self.translations = validated_translations
self.storage = WifiStorage()
def _get_wifi_interface(self) -> Optional[object]:
- """Автоматически определяет активный Wi-Fi-интерфейс.
-
- Returns:
- Optional[object]: Объект интерфейса PyWiFi или None, если интерфейс не найден.
-
- Проверяет доступные интерфейсы, выполняет тестовое сканирование и возвращает
- первый активный интерфейс, исключая P2P-интерфейсы.
- """
+ """Автоматически определяет активный Wi-Fi-интерфейс."""
try:
interfaces = self.wifi.interfaces()
if not interfaces:
- logger.error("Не найдено сетевых интерфейсов")
+ logger.error(self.translations.gettext("Не найдено сетевых интерфейсов"))
return None
- logger.debug(
- f"Доступные интерфейсы: {[iface.name() for iface in interfaces]}"
- )
+ logger.debug(f"Available interfaces: {[iface.name() for iface in interfaces]}")
for iface in interfaces:
iface_name = iface.name()
if "p2p" in iface_name.lower():
- logger.debug(f"Пропущен интерфейс {iface_name} (P2P)")
+ logger.debug(f"Skipped interface {iface_name} (P2P)")
continue
try:
iface.scan()
time.sleep(1)
results = iface.scan_results()
if results:
- logger.info(f"Выбран Wi-Fi-интерфейс: {iface_name}")
+ logger.info(f"Selected Wi-Fi interface: {iface_name}")
return iface
- logger.debug(
- f"Интерфейс {iface_name} не вернул результатов сканирования"
- )
+ logger.debug(f"Interface {iface_name} returned no scan results")
except Exception as e:
- logger.debug(f"Интерфейс {iface_name} неактивен: {str(e)}")
+ logger.debug(f"Interface {iface_name} inactive: {str(e)}")
- logger.error("Не найден активный Wi-Fi-интерфейс")
+ logger.error(self.translations.gettext("Не найден активный Wi-Fi-интерфейс"))
return None
except Exception as e:
- logger.error(f"Ошибка при определении Wi-Fi-интерфейса: {str(e)}")
+ logger.error(f"Error determining Wi-Fi interface: {str(e)}")
return None
@lru_cache(maxsize=1000)
def get_vendor(self, mac: str) -> str:
- """Определяет вендора по MAC-адресу с использованием кэширования.
-
- Args:
- mac (str): MAC-адрес устройства в формате XX:XX:XX:XX:XX:XX.
-
- Returns:
- str: Название вендора или "Unknown", если вендор не определён.
-
- Raises:
- Exception: Если произошла ошибка при запросе к базе вендоров.
- """
+ """Определяет вендора по MAC-адресу с использованием кэширования."""
try:
return self.mac_lookup.lookup(mac)
except Exception as e:
- logger.warning(
- f"Не удалось определить производителя для MAC {mac}: {str(e)}")
- return "Unknown"
+ logger.warning(f"Failed to determine vendor for MAC {mac}: {str(e)}")
+ return self.translations.gettext("Неизвестный")
def _get_encryption_from_iw(self, ap_info: Dict) -> str:
- """Определяет тип шифрования из данных, полученных через iw.
-
- Args:
- ap_info (Dict): Словарь с данными точки доступа, включая поле "security".
-
- Returns:
- str: Тип шифрования ("WPA3", "WPA2", "WPA", "WEP" или "Open").
- """
+ """Определяет тип шифрования из данных, полученных через iw."""
security = ap_info.get("security", "")
- logger.debug(
- f"Шифрование для BSSID {ap_info.get('bssid')}: {security}")
+ logger.debug(f"Encryption for BSSID {ap_info.get('bssid')}: {security}")
if "WPA3" in security:
- return "WPA3"
+ return self.translations.gettext("WPA3")
elif "WPA2" in security or "RSN" in security:
- return "WPA2"
+ return self.translations.gettext("WPA2")
elif "WPA" in security:
- return "WPA"
+ return self.translations.gettext("WPA")
elif "WEP" in security:
- return "WEP"
- return "Open"
+ return self.translations.gettext("WEP")
+ return self.translations.gettext("Открытая")
def _get_channel(self, frequency: Optional[int]) -> Optional[int]:
- """Преобразует частоту в номер канала.
-
- Args:
- frequency (Optional[int]): Частота в МГц или None.
-
- Returns:
- Optional[int]: Номер канала или None, если частота вне диапазона.
- """
+ """Преобразует частоту в номер канала."""
if frequency is None:
return None
try:
@@ -192,66 +159,41 @@ class WifiService:
return (frequency - 2412) // 5 + 1
elif 5180 <= frequency <= 5825:
return (frequency - 5180) // 5 + 36
- logger.debug(f"Частота {frequency} вне диапазона 2.4/5 GHz")
+ logger.debug(f"Frequency {frequency} outside 2.4/5 GHz range")
return None
except Exception as e:
- logger.debug(
- f"Ошибка при определении канала для частоты {frequency}: {str(e)}"
- )
+ logger.debug(f"Error determining channel for frequency {frequency}: {str(e)}")
return None
def _get_frequency(self, frequency: Optional[int]) -> Optional[str]:
- """Определяет частотный диапазон на основе частоты.
-
- Args:
- frequency (Optional[int]): Частота в МГц или None.
-
- Returns:
- Optional[str]: Диапазон ("2.4 GHz", "5 GHz" или None).
- """
+ """Определяет частотный диапазон на основе частоты."""
if frequency is None:
return None
try:
if 2412 <= frequency <= 2472:
- return "2.4 GHz"
+ return self.translations.gettext("2.4 ГГц")
elif 5180 <= frequency <= 5825:
- return "5 GHz"
- logger.debug(f"Частота {frequency} вне диапазона 2.4/5 GHz")
+ return self.translations.gettext("5 ГГц")
+ logger.debug(f"Frequency {frequency} outside 2.4/5 GHz range")
return None
except Exception as e:
- logger.debug(
- f"Ошибка при определении диапазона для частоты {frequency}: {str(e)}"
- )
+ logger.debug(f"Error determining band for frequency {frequency}: {str(e)}")
return None
def _get_iw_scan_data(self) -> List[Dict]:
- """Получает данные сканирования Wi-Fi через команду iw.
-
- Returns:
- List[Dict]: Список словарей с данными точек доступа (BSSID, SSID, частота,
- сигнал, шифрование).
-
- Raises:
- subprocess.CalledProcessError: Если команда iw завершилась с ошибкой.
- subprocess.TimeoutExpired: Если превышен таймаут выполнения.
- Exception: Если произошла ошибка при парсинге вывода.
- """
+ """Получает данные сканирования Wi-Fi через команду iw."""
try:
if not self.interface:
- logger.error("Интерфейс не определён")
+ logger.error(self.translations.gettext("Интерфейс не определён"))
return []
cmd = ["sudo", "iw", "dev", self.interface.name(), "scan"]
- logger.debug(f"Выполняем команду: {' '.join(cmd)}")
- result = subprocess.run(cmd,
- capture_output=True,
- text=True,
- timeout=10)
+ logger.debug(f"Executing command: {' '.join(cmd)}")
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode != 0:
- logger.error(f"Ошибка iw scan: {result.stderr}")
+ logger.error(f"iw scan error: {result.stderr}")
return []
output = result.stdout
- logger.debug(
- f"Вывод iw scan (первые 500 символов): {output[:500]}...")
+ logger.debug(f"iw scan output (first 500 chars): {output[:500]}...")
aps = []
current_ap = None
@@ -268,15 +210,10 @@ class WifiService:
if bssid_match := bssid_pattern.match(line):
if current_ap:
aps.append(current_ap)
- current_ap = {
- "bssid": bssid_match.group(1).lower(),
- "security": ""
- }
+ current_ap = {"bssid": bssid_match.group(1).lower(), "security": ""}
elif current_ap:
if ssid_match := ssid_pattern.match(line):
- current_ap["ssid"] = (ssid_match.group(1)
- if ssid_match.group(1).strip()
- else "")
+ current_ap["ssid"] = ssid_match.group(1).strip() if ssid_match.group(1).strip() else ""
elif freq_match := freq_pattern.match(line):
current_ap["frequency"] = int(freq_match.group(1))
elif signal_match := signal_pattern.match(line):
@@ -285,58 +222,48 @@ class WifiService:
current_ap["security"] += "RSN;"
elif wpa_pattern.match(line):
current_ap["security"] += "WPA;"
- elif security_type_match := security_type_pattern.search(
- line):
- current_ap["security"] += security_type_match.group(
- 1) + ";"
+ elif security_type_match := security_type_pattern.search(line):
+ current_ap["security"] += security_type_match.group(1) + ";"
if current_ap:
aps.append(current_ap)
- logger.debug(
- f"Найдено {len(aps)} точек через iw: {[ap['bssid'] for ap in aps]}"
- )
- for ap in aps:
- logger.debug(f"Данные точки {ap['bssid']}: {ap}")
+ logger.debug(f"Found {len(aps)} access points via iw: {[ap['bssid'] for ap in aps]}")
return aps
except subprocess.CalledProcessError as e:
- logger.error(f"Ошибка при выполнении iw scan: {e.stderr}")
+ logger.error(f"Error executing iw scan: {e.stderr}")
return []
except subprocess.TimeoutExpired:
- logger.error("Таймаут при выполнении iw scan")
+ logger.error(self.translations.gettext("Таймаут при выполнении iw scan"))
return []
except Exception as e:
- logger.error(f"Ошибка при парсинге iw scan: {str(e)}")
+ logger.error(f"Error parsing iw scan: {str(e)}")
return []
def scan_wifi(self, timeout: int = 30) -> WifiScanResult:
- """Сканирует текущие Wi-Fi точки доступа.
-
- Args:
- timeout (int, optional): Таймаут ожидания в секундах. По умолчанию 30.
-
- Returns:
- WifiScanResult: Объект с результатами сканирования, содержащий список точек
- доступа.
-
- Сканирует Wi-Fi с использованием pywifi и iw, объединяет данные, определяет
- лучшие точки по сигналу и сохраняет заметки.
- """
+ """Сканирует текущие Wi-Fi точки доступа."""
start_time = time.time()
aps = []
if not self.interface:
- logger.error("Wi-Fi-интерфейс не доступен")
+ logger.error(self.translations.gettext("Wi-Fi-интерфейс не доступен"))
return WifiScanResult(access_points=aps)
try:
- logger.info(
- f"Начало сканирования Wi-Fi на интерфейсе {self.interface.name()}"
- )
- self.interface.scan()
- time.sleep(10)
- py_results = self.interface.scan_results()
- logger.debug(f"Найдено {len(py_results)} точек через pywifi")
+ logger.info(f"Starting Wi-Fi scan on interface {self.interface.name()}")
+ max_attempts = 3
+ for attempt in range(max_attempts):
+ self.interface.scan()
+ time.sleep(min(timeout, 5))
+ py_results = self.interface.scan_results()
+ if py_results:
+ logger.info(f"Found {len(py_results)} access points via pywifi after attempt {attempt + 1}")
+ break
+ else:
+ logger.warning(f"Scan attempt {attempt + 1} failed, trying again (FAIL-BUSY?)")
+ else:
+ logger.error("All scan attempts failed")
+ return WifiScanResult(access_points=aps)
iw_results = self._get_iw_scan_data()
iw_by_bssid = {ap["bssid"]: ap for ap in iw_results if ap.get("bssid")}
@@ -347,10 +274,10 @@ class WifiService:
for ap in py_results:
bssid = ap.bssid.lower() if ap.bssid else None
if not bssid:
- logger.debug("Пропущена точка без BSSID")
+ logger.debug("Skipped access point without BSSID")
continue
- ssid = ap.ssid if ap.ssid else ""
- hidden = not ssid
+ ssid = ap.ssid if ap.ssid else f"Hidden_{bssid}"
+ hidden = not ap.ssid
signal = ap.signal if ap.signal else None
iw_data = iw_by_bssid.get(bssid, {})
@@ -360,10 +287,6 @@ class WifiService:
encryption = self._get_encryption_from_iw(iw_data)
vendor = self.get_vendor(bssid)
- logger.debug(
- f"Точка {bssid}: freq={frequency}, channel={channel}, encryption={encryption}, signal={signal}"
- )
-
current_ap = WifiAccessPoint(
ssid=ssid,
bssid=bssid,
@@ -373,114 +296,82 @@ class WifiService:
frequency=freq_band,
vendor=vendor,
hidden=hidden,
+ note=saved_notes.get(ssid) if ap.ssid else None,
)
+ logger.debug(f"Processed AP: {current_ap.model_dump()}")
if ssid not in best_aps_by_ssid or (
current_ap.signal and best_aps_by_ssid[ssid].signal
and current_ap.signal > best_aps_by_ssid[ssid].signal):
- current_ap.note = saved_notes.get(ssid)
+ current_ap.note = saved_notes.get(ssid) if ap.ssid else None
best_aps_by_ssid[ssid] = current_ap
aps = list(best_aps_by_ssid.values())
+ # Сохраняем хотя бы SSID, если заметок нет
notes_to_save = {ap.ssid: ap.note for ap in aps if ap.ssid}
- self.storage.save_notes(notes_to_save)
+ if not notes_to_save and aps:
+ notes_to_save = {ap.ssid: None for ap in aps if ap.ssid} # Сохраняем SSID без заметок
+ if notes_to_save:
+ self.storage.save_notes(notes_to_save)
+ else:
+ logger.warning("No notes or SSIDs to save, skipping file write")
- logger.info(
- f"Сканирование Wi-Fi завершено за {time.time() - start_time:.2f} секунд, "
- f"найдено {len(aps)} уникальных сетей")
+ logger.info(f"Wi-Fi scan completed, found {len(aps)} unique networks")
return WifiScanResult(access_points=aps)
except Exception as e:
- logger.error(f"Ошибка при сканировании Wi-Fi: {str(e)}")
+ logger.error(f"Error scanning Wi-Fi: {str(e)}")
return WifiScanResult(access_points=aps)
def add_note(self, ssid: str, note: str) -> Tuple[bool, Optional[str]]:
- """Добавляет заметку к сети Wi-Fi по SSID.
-
- Args:
- ssid (str): SSID сети Wi-Fi.
- note (str): Текст заметки.
-
- Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении заметки.
- """
+ """Добавляет заметку к сети Wi-Fi по SSID."""
try:
saved_notes = self.storage.load_notes()
if ssid not in saved_notes or saved_notes[ssid] is None:
- logger.info(f"Добавлена новая заметка для сети с SSID {ssid}")
+ logger.info(f"Added new note for network with SSID {ssid}")
elif saved_notes[ssid] is not None:
- logger.error(f"Заметка для сети с SSID {ssid} уже существует")
- return False, f"Заметка для сети с SSID {ssid} уже существует"
+ logger.error(f"Note for network with SSID {ssid} already exists")
+ return False, self.translations.gettext("Заметка для сети с SSID {ssid} уже существует").format(ssid=ssid)
saved_notes[ssid] = note
self.storage.save_notes(saved_notes)
- logger.info(f"Добавлена заметка '{note}' для сети с SSID {ssid}")
+ logger.info(f"Added note '{note}' for network with SSID {ssid}")
return True, None
except Exception as e:
- logger.error(f"Ошибка при добавлении заметки для SSID {ssid}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
+ logger.error(f"Error adding note for SSID {ssid}: {str(e)}")
+ return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e))
def update_note(self, ssid: str, note: str) -> Tuple[bool, Optional[str]]:
- """Обновляет заметку сети Wi-Fi по SSID.
-
- Args:
- ssid (str): SSID сети Wi-Fi.
- note (str): Новый текст заметки.
-
- Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении заметки.
- """
+ """Обновляет заметку сети Wi-Fi по SSID."""
try:
saved_notes = self.storage.load_notes()
if ssid not in saved_notes:
- logger.warning(f"Сеть с SSID {ssid} не найдена")
- return False, f"Сеть с SSID {ssid} не найдена"
+ logger.warning(f"Network with SSID {ssid} not found")
+ return False, self.translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid)
if saved_notes[ssid] is None:
- logger.error(f"Заметка для сети с SSID {ssid} не существует")
- return False, f"Заметка для сети с SSID {ssid} не существует"
+ logger.error(f"Note for network with SSID {ssid} does not exist")
+ return False, self.translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid)
saved_notes[ssid] = note
self.storage.save_notes(saved_notes)
- logger.info(f"Обновлена заметка '{note}' для сети с SSID {ssid}")
+ logger.info(f"Updated note '{note}' for network with SSID {ssid}")
return True, None
except Exception as e:
- logger.error(f"Ошибка при обновлении заметки для SSID {ssid}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
+ logger.error(f"Error updating note for SSID {ssid}: {str(e)}")
+ return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e))
def delete_note(self, ssid: str) -> Tuple[bool, Optional[str]]:
- """Удаляет заметку сети Wi-Fi по SSID.
-
- Args:
- ssid (str): SSID сети Wi-Fi.
-
- Returns:
- Tuple[bool, Optional[str]]: Кортеж (успех, сообщение об ошибке).
- - Успех: (True, None).
- - Ошибка: (False, сообщение об ошибке).
-
- Raises:
- Exception: Если произошла ошибка при сохранении изменений.
- """
+ """Удаляет заметку сети Wi-Fi по SSID."""
try:
saved_notes = self.storage.load_notes()
if ssid not in saved_notes:
- logger.warning(f"Сеть с SSID {ssid} не найдена")
- return False, f"Сеть с SSID {ssid} не найдена"
+ logger.warning(f"Network with SSID {ssid} not found")
+ return False, self.translations.gettext("Сеть с SSID {ssid} не найдена").format(ssid=ssid)
if saved_notes[ssid] is None:
- logger.error(f"Заметка для сети с SSID {ssid} не существует")
- return False, f"Заметка для сети с SSID {ssid} не существует"
+ logger.error(f"Note for network with SSID {ssid} does not exist")
+ return False, self.translations.gettext("Заметка для сети с SSID {ssid} не существует").format(ssid=ssid)
saved_notes[ssid] = None
self.storage.save_notes(saved_notes)
- logger.info(f"Удалена заметка для сети с SSID {ssid}")
+ logger.info(f"Deleted note for network with SSID {ssid}")
return True, None
except Exception as e:
- logger.error(f"Ошибка при удалении заметки для SSID {ssid}: {str(e)}")
- return False, f"Внутренняя ошибка сервера: {str(e)}"
\ No newline at end of file
+ logger.error(f"Error deleting note for SSID {ssid}: {str(e)}")
+ return False, self.translations.gettext("Внутренняя ошибка сервера: {error}").format(error=str(e))
diff --git a/SRV/storage/json_storage.py b/SRV/storage/json_storage.py
index 1823706..e7988a9 100644
--- a/SRV/storage/json_storage.py
+++ b/SRV/storage/json_storage.py
@@ -2,20 +2,48 @@ import json
import os
import logging
from passlib.context import CryptContext
+import passlib.handlers.bcrypt # noqa: F401 # ensure Nuitka bundles bcrypt handlers
from typing import Optional
from models.auth import UserCreate, UserResponse
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
+DATA_DIR = os.getenv("GONETS_DATA_DIR", "/etc/gonets")
+DEFAULT_USERS_PATH = os.getenv("GONETS_USERS_PATH",
+ os.path.join(DATA_DIR, "users.json"))
+
+
class JsonStorage:
"""Хранилище пользователей в JSON-файле.
- Предоставляет методы для создания, чтения и проверки данных пользователей,
- используя шифрование паролей с помощью bcrypt.
+ Назначение:
+
+ - Обеспечивает простое файловое хранилище учетных записей без полноценной СУБД.
+ - Подходит для встраиваемых окружений, где критична минимальная зависимость от внешних сервисов.
+
+ Формат данных::
+
+ {
+ "users": [
+ {"id": int, "username": str, "password_hash": str, "is_admin": bool},
+ ...
+ ],
+ "next_id": int
+ }
+
+ Безопасность:
+
+ - Пароли никогда не сохраняются в открытом виде: используется `passlib.context.CryptContext` со схемой `bcrypt`.
+ - Файл создаётся с правами доступа ``0o600`` (чтение/запись только владельцу).
+
+ Ограничения:
+
+ - Нет блокировок для конкурентного доступа — в многопроцессной среде требуется внешняя синхронизация.
+ - Значение пути по умолчанию ``/etc/gonets/users.json`` предполагает наличие прав на запись и предварительную настройку окружения.
"""
- def __init__(self, file_path: str = "/etc/gonets/users.json"):
+ def __init__(self, file_path: Optional[str] = None):
"""Инициализирует хранилище пользователей.
Args:
@@ -25,7 +53,7 @@ class JsonStorage:
Создаёт объект CryptContext для хеширования паролей и проверяет/создаёт
файл, если он отсутствует.
"""
- self.file_path = file_path
+ self.file_path = file_path or DEFAULT_USERS_PATH
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
self._ensure_file_exists()
@@ -180,4 +208,4 @@ class JsonStorage:
Returns:
bool: True, если пароль верен, False в противном случае.
"""
- return self.pwd_context.verify(plain_password, hashed_password)
\ No newline at end of file
+ return self.pwd_context.verify(plain_password, hashed_password)
diff --git a/SRV/storage/session_storage.py b/SRV/storage/session_storage.py
index fac024f..7beb02f 100644
--- a/SRV/storage/session_storage.py
+++ b/SRV/storage/session_storage.py
@@ -8,14 +8,45 @@ from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
+DATA_DIR = os.getenv("GONETS_DATA_DIR", "/etc/gonets")
+DEFAULT_SESSIONS_PATH = os.getenv("GONETS_SESSIONS_PATH",
+ os.path.join(DATA_DIR, "sessions.json"))
+
+
class SessionStorage:
"""Хранилище сессий пользователей в JSON-файле.
- Предоставляет методы для создания, получения, удаления и очистки сессий с
- учётом срока действия.
+ Назначение:
+
+ - Лёгкое файловое хранилище сессионных токенов, сопоставленных с ``client_id`` (например, IP клиента).
+ - Предназначено для сред без БД и persistent-кэшей; обеспечивает простые операции и автоочистку истёкших сессий.
+
+ Формат данных::
+
+ {
+ "sessions": {
+ "": {
+ "username": str,
+ "token": str,
+ "client_id": str,
+ "created_at": ISO8601 str,
+ "expiry": ISO8601 str
+ },
+ ...
+ }
+ }
+
+ Политика TTL:
+
+ - Каждая сессия действует 30 минут; метод ``clean_expired_sessions`` удаляет записи с истёкшим сроком.
+
+ Ограничения:
+
+ - Отсутствуют файловые блокировки — в многопроцессной среде возможны гонки.
+ - Путь по умолчанию ``/etc/gonets/sessions.json`` требует прав на запись.
"""
- def __init__(self, file_path: str = "/etc/gonets/sessions.json"):
+ def __init__(self, file_path: Optional[str] = None):
"""Инициализирует хранилище сессий.
Args:
@@ -24,7 +55,7 @@ class SessionStorage:
Создаёт файл и директории, если они отсутствуют, с правами доступа 600.
"""
- self.file_path = file_path
+ self.file_path = file_path or DEFAULT_SESSIONS_PATH
self._ensure_file_exists()
def _ensure_file_exists(self):
@@ -238,4 +269,4 @@ class SessionStorage:
logger.error(
f"Ошибка при очистке истекших сессий: {type(e).__name__} - {str(e)}"
)
- raise
\ No newline at end of file
+ raise
diff --git a/SRV/utils/__init__.py b/SRV/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/SRV/utils/build_docs.py b/SRV/utils/build_docs.py
new file mode 100644
index 0000000..ee4f57f
--- /dev/null
+++ b/SRV/utils/build_docs.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+"""
+Утилита для сборки Sphinx-документации проекта GONETS.
+
+Позволяет из корня каталога `SRV/` одним запуском пересобрать HTML и Markdown
+версии документации. Пример использования:
+
+ poetry run python utils/build_docs.py
+
+По умолчанию собираются оба билда (`html` и `markdown`). Повторные вызовы
+перезаписывают существующий вывод Sphinx.
+"""
+from __future__ import annotations
+
+import argparse
+import sys
+from pathlib import Path
+from typing import Iterable, List
+
+from sphinx.cmd.build import build_main
+
+
+ROOT_DIR = Path(__file__).resolve().parent.parent
+DOCS_DIR = ROOT_DIR / "docs"
+BUILD_ROOT = DOCS_DIR / "_build"
+
+
+def build_docs(builders: Iterable[str]) -> None:
+ """
+ Вызывает Sphinx для каждого указанного билдера.
+
+ Args:
+ builders: последовательность имён билдов Sphinx (например, ["html"]).
+
+ Raises:
+ RuntimeError: если сборка для какого-либо билдера завершилась с ошибкой.
+ """
+ for builder in builders:
+ output_dir = BUILD_ROOT / builder
+ output_dir.parent.mkdir(parents=True, exist_ok=True)
+
+ args: List[str] = [
+ "-b",
+ builder,
+ str(DOCS_DIR),
+ str(output_dir),
+ ]
+ print(f"[build-docs] sphinx-build {' '.join(args)}")
+
+ exit_code = build_main(args)
+ if exit_code != 0:
+ raise RuntimeError(
+ f"Sphinx build failed for builder '{builder}' with exit code {exit_code}"
+ )
+
+
+def parse_args(argv: Iterable[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Сборка документации проекта GONETS (Sphinx)."
+ )
+ parser.add_argument(
+ "-b",
+ "--builder",
+ action="append",
+ choices=("html", "markdown"),
+ help="Ограничить сборку указанными билдерами (по умолчанию html и markdown).",
+ )
+ return parser.parse_args(argv)
+
+
+def main(argv: Iterable[str] | None = None) -> int:
+ args = parse_args(argv or sys.argv[1:])
+ builders = args.builder or ["html", "markdown"]
+
+ if not DOCS_DIR.exists():
+ print(f"[build-docs] docs directory not found: {DOCS_DIR}", file=sys.stderr)
+ return 1
+
+ try:
+ build_docs(builders)
+ except RuntimeError as exc:
+ print(f"[build-docs] {exc}", file=sys.stderr)
+ return 1
+
+ print("[build-docs] Documentation build completed successfully")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/SRV/utils/i18n.py b/SRV/utils/i18n.py
new file mode 100644
index 0000000..847fd50
--- /dev/null
+++ b/SRV/utils/i18n.py
@@ -0,0 +1,107 @@
+"""
+Утилиты локализации и зависимости переводов для FastAPI.
+
+Содержимое модуля:
+ - `get_translations`: зависимость FastAPI, считывающая локаль из query/cookie/header.
+ - `get_translations_ws`: аналог для WebSocket.
+ - `_load_translations`: низкоуровневая загрузка словаря переводов с fallback.
+ - `_()`: обёртка для gettext с дефолтом ru.
+"""
+from fastapi import Request, WebSocket
+from babel.support import Translations
+import logging
+from pathlib import Path
+import os
+from typing import Iterable, Optional
+
+logger = logging.getLogger(__name__)
+
+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:
+ """Подгружает словарь переводов с fallback на ru."""
+ last_error: Optional[str] = None
+ for directory in LOCALE_DIRS or [None]:
+ try:
+ if directory is None:
+ raise FileNotFoundError("Locales directory list is empty")
+ translations = Translations.load(str(directory), [locale], "messages")
+ if translations._catalog:
+ return translations
+ 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 _resolve_locale_from_request(request: Request) -> str:
+ locale = request.query_params.get("locale")
+ if not locale:
+ locale = request.cookies.get("locale")
+ if not locale:
+ locale = request.headers.get("Accept-Language", "ru").split(",")[0].split("-")[0]
+ return locale or "ru"
+
+def get_translations(request: Request) -> Translations:
+ """Зависимость FastAPI для HTTP-эндпоинтов."""
+ locale = _resolve_locale_from_request(request)
+ logger.debug(f"Determined locale (http): {locale}")
+ return _load_translations(locale)
+
+def get_translations_ws(websocket: WebSocket) -> Translations:
+ """Зависимость FastAPI для WebSocket-эндпоинтов."""
+ # WebSocket не имеет cookies/headers в виде HTTPRequest, но есть headers
+ header_locale = websocket.headers.get("accept-language", "ru").split(",")[0].split("-")[0] if websocket.headers else "ru"
+ logger.debug(f"Determined locale (ws): {header_locale}")
+ return _load_translations(header_locale or "ru")
+
+def _(msgid: str, request: Request = None) -> str:
+ """
+ Переводит строку msgid для текущей локали.
+
+ Args:
+ msgid (str): Строка для перевода.
+ request (Request, optional): Объект запроса FastAPI для получения локали.
+
+ Returns:
+ str: Переведенная строка.
+ """
+ if request is None:
+ translations = _load_translations("ru")
+ logger.debug(f"Using default ru translations for msgid: {msgid}")
+ else:
+ translations = get_translations(request)
+ logger.debug(f"Using translations for locale {request.cookies.get('locale', 'ru')} for msgid: {msgid}")
+ return translations.gettext(msgid)