diff --git a/.pc/.quilt_patches b/.pc/.quilt_patches
new file mode 100644
index 0000000..6857a8d
--- /dev/null
+++ b/.pc/.quilt_patches
@@ -0,0 +1 @@
+debian/patches
diff --git a/.pc/.quilt_series b/.pc/.quilt_series
new file mode 100644
index 0000000..c206706
--- /dev/null
+++ b/.pc/.quilt_series
@@ -0,0 +1 @@
+series
diff --git a/.pc/.version b/.pc/.version
new file mode 100644
index 0000000..0cfbf08
--- /dev/null
+++ b/.pc/.version
@@ -0,0 +1 @@
+2
diff --git a/.pc/1/SRV/services/ip_service.py b/.pc/1/SRV/services/ip_service.py
new file mode 100644
index 0000000..84c3cca
--- /dev/null
+++ b/.pc/1/SRV/services/ip_service.py
@@ -0,0 +1,75 @@
+import asyncio
+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
+
+class IPService:
+ """
+ Сервис проверки доступности IP-адреса и применения настроек сети
+ """
+ def __init__(self):
+ 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
+
+ async def check_ip_config(self, args: Dict[str, Any]) -> Dict[str, Any]:
+ """ Проверяет доступность IP-адреса перед его применением """
+ 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": f"IP address {new_ip} уже занят."}
+ return {"available": True, "message": "Network profile settings are available for use."}
+
+ async def apply_ip_config(self, args: Dict[str, Any]) -> None:
+ """ Применяет проверенные настройки IP """
+ self._reset_bus()
+ nm = NetworkManager()
+ settings = NetworkManagerSettings()
+ connection_paths = await settings.get_connections_by_id(args["connection_id"])
+
+ if not connection_paths:
+ raise ValueError(f"Подключение {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"Обновлены настройки для {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']}")
+ return
+
+ raise RuntimeError(f"Устройство для интерфейса {interface_name} не найдено")
diff --git a/.pc/2/SRV/services/ip_service.py b/.pc/2/SRV/services/ip_service.py
new file mode 100644
index 0000000..b0c35af
--- /dev/null
+++ b/.pc/2/SRV/services/ip_service.py
@@ -0,0 +1,112 @@
+from sdbus_async.networkmanager import NetworkManager, NetworkManagerSettings, NetworkConnectionSettings, NetworkDeviceGeneric, ActiveConnection
+import asyncio
+from sdbus_async.networkmanager.settings import ConnectionProfile
+from typing import Dict, Any
+import sdbus
+from services.arp_service import NetworkScanner
+
+class IPService:
+ """
+ Сервис проверки доступности IP-адреса и применения настроек сети
+ """
+ def __init__(self):
+ 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
+
+ async def check_ip_config(self, args: Dict[str, Any]) -> Dict[str, Any]:
+ """ Проверяет доступность IP-адреса перед его применением """
+ 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": f"IP address {new_ip} уже доступен."}
+ return {"available": True, "message": "Network profile settings are available for use."}
+
+ async def apply_ip_config(self, args: Dict[str, Any]) -> None:
+ """ Применяет проверенные настройки IP """
+ self._reset_bus()
+ nm = NetworkManager(self.bus)
+ settings = NetworkManagerSettings(self.bus)
+ connection_paths = await settings.get_connections_by_id(args["connection_id"])
+
+ if not connection_paths:
+ raise ValueError(f"Подключение {args['connection_id']} не найдено")
+
+ connection = NetworkConnectionSettings(connection_paths[0], self.bus)
+ profile = await connection.get_profile()
+ settings_dict = profile.to_settings_dict()
+
+ # Обновляем настройки IPv4
+ 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"Обновлены настройки для {args['connection_id']}:")
+
+ # Проверяем interface-name
+ interface_name = settings_dict['connection'].get('interface-name')
+ if not interface_name:
+ # Ищем устройство, связанное с connection_id через активное соединение
+ devices = await nm.get_devices()
+ for device_path in devices:
+ device = NetworkDeviceGeneric(device_path, self.bus)
+ active_connection = await device.active_connection
+ if active_connection != '/':
+ active_conn = ActiveConnection(active_connection, self.bus)
+ conn_path = await active_conn.connection # Путь к настройкам соединения
+ if conn_path != '/':
+ conn_settings = NetworkConnectionSettings(conn_path, self.bus)
+ active_profile = await conn_settings.get_profile()
+ if active_profile.to_settings_dict()['connection']['id'] == args["connection_id"]:
+ interface_name = await device.interface
+ settings_dict['connection']['interface-name'] = interface_name
+ new_profile = ConnectionProfile.from_settings_dict(settings_dict)
+ await connection.update(new_profile.to_dbus())
+ print(f"Добавлен interface-name: {interface_name} для {args['connection_id']}")
+ break
+
+ if not interface_name:
+ # Если не нашли активное соединение, пробуем получить интерфейс из настроек устройства
+ devices = await nm.get_devices()
+ for device_path in devices:
+ device = NetworkDeviceGeneric(device_path, self.bus)
+ applied_connection = await device.get_applied_connection()
+ connection_settings = applied_connection[0]
+ conn_id = connection_settings.get('connection', {}).get('id', ('s', ''))[1]
+ if conn_id == args["connection_id"]:
+ interface_name = await device.interface
+ settings_dict['connection']['interface-name'] = interface_name
+ new_profile = ConnectionProfile.from_settings_dict(settings_dict)
+ await connection.update(new_profile.to_dbus())
+ print(f"Добавлен interface-name: {interface_name} для {args['connection_id']}")
+ break
+
+ if not interface_name:
+ raise ValueError(f"Не удалось определить interface-name для {args['connection_id']}")
+
+ # Переподключаем устройство
+ devices = await nm.get_devices()
+ for device_path in devices:
+ device = NetworkDeviceGeneric(device_path, self.bus)
+ 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']}")
+ return
+
+ raise RuntimeError(f"Устройство для интерфейса {interface_name} не найдено")
\ No newline at end of file
diff --git a/.pc/applied-patches b/.pc/applied-patches
new file mode 100644
index 0000000..1191247
--- /dev/null
+++ b/.pc/applied-patches
@@ -0,0 +1,2 @@
+1
+2
diff --git a/.woodpecker/.woodpecker.yml b/.woodpecker/.woodpecker.yml
deleted file mode 100644
index 6220c93..0000000
--- a/.woodpecker/.woodpecker.yml
+++ /dev/null
@@ -1,137 +0,0 @@
-when:
- event: manual
-
-steps:
- checkout:
- image: debian:latest
- commands:
- # ПОДГОТОВКА ГИТА И КЛОНИРОВАНИЕ GONETS
- - apt update
- - apt install -y git git-lfs
- - git lfs install
-
- # УСТАНОВКА НЕОБХОДИМЫХ ЗАВИСИМОСТЕЙ НА СИСТЕМЕ
- - apt update
- - apt install -y python3 curl findutils grep sed openssh-server tree libnss3 libnss3-dev libnspr4 libnspr4-dev libasound2 libasound2-dev python3.11-venv python3-pip python3-poetry nodejs npm debhelper devscripts build-essential fakeroot dh-make dpkg-dev cmake libgirepository1.0-dev gobject-introspection libcairo2-dev pkg-config python3-dev libdbus-1-dev
- - python3 --version
- - node -v
- - npm -v
- raspberry
-
- # ПОДГОТОВКА СПИСКА PYTHON ЗАВИСИМОСТЕЙ GONETS
- - rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
- - rm -rf /root/.local/share/pypoetry
- - rm -rf /root/.poetry
- - rm -rf /usr/bin/poetry
- - pip install pipx
- - pipx ensurepath
- - pipx install poetry
- - echo 'export PATH="$HOME/.local/bin:$PATH"' >> /root/.bashrc
- - . /root/.bashrc
- - poetry --version
- - cd SRV
- - poetry env activate
- - poetry add poetry-plugin-export
- - poetry install
- - poetry self update
- - rm -f requirements.txt
- - poetry run poetry export -f requirements.txt --output requirements.txt --without-hashes
-
- # ПОДГОТОВКА И СБОРКА FRONTEND ЧАСТИ GONETS
- - pwd
- - cd ..
- - pwd
- - cd FRONT/DEV
- - pwd
- - npm install
- - npm run package
- - cd dist/gonets-linux-x64/resources/app
- - pwd
- - npm run build
- - sed -i 's#href="/assets/#href="./assets/#g' dist/index.html
- - sed -i 's#src="/assets/#src="./assets/#g' dist/index.html
- - cd ../../../../..
- - pwd
- - rm -rf BUILD/*
- - cp -r DEV/dist/gonets-linux-x64/* BUILD/
- - rm -rf DEV
-
- # ИНИЦИАЛИЗАЦИЯ DEBIAN КОНФИГУРАЦИЙ ДЛЯ СБОРКИ
- - cd ..
- - pwd
- - cd ..
- - pwd
- - mv gonets gonets-0.1
- - cd gonets-0.1
- - pwd
- - ls
- - DEBEMAIL="devteam@gossys.dev" DEBFULLNAME="GOSSYS LLC" dh_make --single -y --createorig
- - cd debian/
- - rm -f *.ex
- - rm -f README.*
-
- # КЛОНИРОВАНИЕ НЕОБХОДИМЫХ ШАБЛОНОВ КОНФИГОВ ДЕБИАНА ДЛЯ СБОРКИ
- - mkdir -p /woodpecker/src/dev.gossys.dev/HybrydMain/templates
- - git clone https://s.galanskiy:${GONETS}@dev.gossys.dev/s.galanskiy/gonets-debian-template.git /woodpecker/src/dev.gossys.dev/HybrydMain/templates
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/changelog /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/control /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/copyright /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/install /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/postinst /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/postrm /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
- - mv /woodpecker/src/dev.gossys.dev/HybrydMain/templates/rules /woodpecker/src/dev.gossys.dev/HybrydMain/gonets-0.1/debian/
-
- # СБОРКА ДЕБИАН ПАКЕТА
- - cd ..
- - pwd
- - dpkg-buildpackage -us -uc -b
-
- # ИЗВЛЕЧЕНИЕ ВЕРСИИ ПАКЕТА ДЛЯ КОММИТА
- - VERSION=$(dpkg-parsechangelog -l./debian/changelog --show-field Version)
- - echo "VERSION=$VERSION" > version.env
-
- # КОММИТ И ТЕГ НА ВЕТКУ DEVELOP
- - . ./version.env
- - git config user.name "s.galanskiy"
- - git config user.email "s.galanskiy@gossys.dev"
- - git fetch --all --prune
- - git branch -r
- - git checkout -b "release/$VERSION"
- - git commit -am "Релиз $VERSION"
- - git tag -f "v$VERSION"
- - git push "https://s.galanskiy:b4785198d18200dfed812dc4299575f736255bd7@dev.gossys.dev/HybrydMain/gonets.git" "release/$VERSION"
- - git push -f https://s.galanskiy:b4785198d18200dfed812dc4299575f736255bd7@dev.gossys.dev/HybrydMain/gonets.git "v$VERSION"
-
- # СОЗДАНИЕ И ЗАГРУЗКА РЕЛИЗА ЧЕРЕЗ API
- - |
- VERSION=$(dpkg-parsechangelog -l./debian/changelog --show-field Version)
- echo "VERSION=$VERSION" > version.env
-
- # Задать переменные
- . ./version.env
- TAG="v$VERSION"
-
- DEB_PATH=$(find .. -maxdepth 1 -type f -name "gonets_*_amd64.deb" | head -n 1)
-
- if [ ! -f "$DEB_PATH" ]; then
- echo ".deb файл не найден по пути: $DEB_PATH"
- exit 1
- fi
-
- RELEASE_JSON=$(curl -s -X POST "https://dev.gossys.dev/api/v1/repos/HybrydMain/gonets/releases" \
- -H "Authorization: token b4785198d18200dfed812dc4299575f736255bd7" \
- -H "Content-Type: application/json" \
- -d "{\"tag_name\": \"$TAG\", \"name\": \"Релиз $TAG\", \"body\": \"Сборка .deb пакета для версии $TAG\", \"draft\": false, \"prerelease\": true}")
-
- RELEASE_ID=$(echo "$RELEASE_JSON" | grep '"id":' | head -n1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
-
- if [ -z "$RELEASE_ID" ]; then
- echo "Не удалось получить ID релиза из ответа:"
- echo "$RELEASE_JSON"
- exit 1
- fi
-
- curl -s -X POST \
- -H "Authorization: token b4785198d18200dfed812dc4299575f736255bd7" \
- -F "attachment=@$DEB_PATH" \
- "https://dev.gossys.dev/api/v1/repos/HybrydMain/gonets/releases/$RELEASE_ID/assets" || echo "Ошибка загрузки файла"
\ No newline at end of file
diff --git a/FRONT/BUILD/index.html b/FRONT/BUILD/index.html
deleted file mode 100644
index 8f4d4d1..0000000
--- a/FRONT/BUILD/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- GoNets
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/FRONT/BUILD/resources/app/index.tsx b/FRONT/BUILD/resources/app/index.tsx
index eeb0635..38c8de5 100644
--- a/FRONT/BUILD/resources/app/index.tsx
+++ b/FRONT/BUILD/resources/app/index.tsx
@@ -1,8 +1,10 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
+import { NetworkService } from './src/api/request.ts';
import { useIpStore } from './src/store';
import { useEffect } from 'react';
+import { useAdminStore } from './src/store/adminStore.ts';
import {useCheckAdmin} from './src/utils/checkAdmin.ts'
import {
diff --git a/FRONT/BUILD/resources/app/main.js b/FRONT/BUILD/resources/app/main.js
index df09e91..4c3bd59 100644
--- a/FRONT/BUILD/resources/app/main.js
+++ b/FRONT/BUILD/resources/app/main.js
@@ -21,7 +21,7 @@ function createWindow() {
win.show();
if (process.env.NODE_ENV === 'development') {
- win.loadURL('https://localhost:8001');
+ win.loadURL('http://localhost:3000');
win.webContents.openDevTools();
} else {
win.loadFile(path.join(__dirname, 'dist/index.html'));
diff --git a/FRONT/BUILD/resources/app/package.json b/FRONT/BUILD/resources/app/package.json
index 304540a..0873f2e 100644
--- a/FRONT/BUILD/resources/app/package.json
+++ b/FRONT/BUILD/resources/app/package.json
@@ -11,7 +11,6 @@
},
"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",
diff --git a/FRONT/BUILD/resources/app/src/api/request.ts b/FRONT/BUILD/resources/app/src/api/request.ts
index 27dafd1..377d522 100644
--- a/FRONT/BUILD/resources/app/src/api/request.ts
+++ b/FRONT/BUILD/resources/app/src/api/request.ts
@@ -82,79 +82,4 @@ export class NetworkService {
return error.response;
}
}
-
- static async scanUser(ipAddress: string): Promise {
- try {
- const axiosInstance = this.createAxiosInstance(ipAddress);
- return await axiosInstance.get('/subnet/devices');
- } 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;
- }
- }
}
\ No newline at end of file
diff --git a/FRONT/BUILD/resources/app/src/components/UI/Dropdown/DeviceCard.tsx b/FRONT/BUILD/resources/app/src/components/UI/Dropdown/DeviceCard.tsx
deleted file mode 100644
index cfed6c4..0000000
--- a/FRONT/BUILD/resources/app/src/components/UI/Dropdown/DeviceCard.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-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";
-
-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 toggleExpanded = () => setIsExpanded(!isExpanded);
- const { openModal } = useModalContext();
-
- const openModalNote = (e: React.MouseEvent) => {
- e.stopPropagation();
- openModal(ModalMap['6'], {
- address: device.mac,
- currentNote: device.note || ''
- });
- };
-
-
- return (
-
-
-
- MAC: {device.mac}
-
-
-
-
-
-
-
-
- Имя:
- {device.hostname || 'Неизвестно'}
-
-
-
- Производитель:
- {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/BUILD/resources/app/src/components/UI/Dropdown/DropdownType.tsx b/FRONT/BUILD/resources/app/src/components/UI/Dropdown/DropdownType.tsx
deleted file mode 100644
index d1e90c5..0000000
--- a/FRONT/BUILD/resources/app/src/components/UI/Dropdown/DropdownType.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-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/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx b/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
index 55ca7e3..0afe0a2 100644
--- a/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
+++ b/FRONT/BUILD/resources/app/src/components/UI/buttons/mainButton.tsx
@@ -9,15 +9,15 @@ const Button = styled.button.withConfig({
border: none;
padding: ${(props) =>
props.size === "small"
- ? "10px 28px"
+ ? "4px 8px"
: props.size === "big"
? "10px 20px"
- : "12px 20px"}; // По умолчанию
+ : "6px 10px"}; // По умолчанию
border-radius: 8px;
color: white;
font-size: ${(props) =>
props.size === "small"
- ? "14px"
+ ? "12px"
: props.size === "big"
? "16px"
: "14px"}; // По умолчанию
@@ -30,7 +30,7 @@ const Button = styled.button.withConfig({
? 'row-reverse'
: 'row'};
gap: 10px;
- transition: all .3s ease;
+
&:hover{
background-color: ${(props) => props.$hoverсolor};
}
diff --git a/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx b/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx
index dd2b9c9..0fac915 100644
--- a/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx
+++ b/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx
@@ -5,7 +5,7 @@ type Props = {
placeholderText: string;
id: string;
value?: string;
- onChange?: (value: string) => void;
+ onChange?: (identifier: string, value: string) => void;
onFileChange?: (event: React.ChangeEvent) => void;
type?: string;
multiple?: boolean;
@@ -33,7 +33,7 @@ const StyledInput = styled.input`
border: 1px solid #adadad;
}
width: 100%;
- font-size: 18px;
+ font-size: 14px;
color: ${props=>props.theme.textColor}
`;
@@ -41,7 +41,7 @@ const Input = React.forwardRef(({ placeholderText, id,
const handleInputChange = (e: React.ChangeEvent) => {
const { value } = e.target;
if (onChange) {
- onChange(value);
+ onChange(id, value);
}
};
diff --git a/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx b/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx
index 9590433..a8c5c32 100644
--- a/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx
+++ b/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx
@@ -2,21 +2,19 @@ import React, { useEffect, useRef } from 'react';
interface SignalSVGProps {
signal: number; // Принимаем signal как пропс
- color: string;
- size: string;
}
-const SignalSVG: React.FC = ({ signal, color, size }) => {
+const SignalSVG: React.FC = ({ signal }) => {
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',
+ '--st1-color': signal >= 1 ? 'white' : 'black',
+ '--st2-color': signal >= 2 ? 'white' : 'black',
+ '--st3-color': signal >= 3 ? 'white' : 'black',
+ '--st4-color': signal >= 4 ? 'white' : 'black',
+ '--st5-color': signal === 5 ? 'white' : 'black',
};
// Применяем стили к SVG
@@ -37,8 +35,8 @@ const SignalSVG: React.FC = ({ signal, color, size }) => {
y="0px"
viewBox="0 0 296 235"
xmlSpace="preserve"
- width={size} // Фиксированная ширина
- height={size}
+ width="20px" // Фиксированная ширина
+ height="20px"
>
-
-
-
-
-
-
-
- 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 ( "Ошибка при проверке файла" ))
-
-
-
-
-
-
-
-
-
-
-
-
-
-