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/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/DEV/index.tsx b/FRONT/DEV/index.tsx index eeb0635..eb6f456 100644 --- a/FRONT/DEV/index.tsx +++ b/FRONT/DEV/index.tsx @@ -3,7 +3,7 @@ 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.ts' +import { useCheckAdmin } from './src/utils/checkAdmin'; import { ModalProvider, @@ -11,28 +11,44 @@ import { ThemeProvider } from './src/store'; -import WindowComponent from './src/components/modalwindows/modalWindow.tsx'; -import Router from './src/router.tsx'; +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() + const checkAdmin = useCheckAdmin(); + const { ipAddress } = useIpStore(); useEffect(() => { const checkAdminStatus = async () => { - await checkAdmin() - } - checkAdminStatus() - }, [ipAddress]) - + await checkAdmin(); + }; + checkAdminStatus(); + }, [ipAddress]); const { isOpen, modalType, modalData } = useModalContext(); return ( - <> - - - + <> + + + ); }; @@ -40,13 +56,6 @@ 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 +root.render(); \ 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..7d98f05 --- /dev/null +++ b/FRONT/DEV/public/locales/en/common.json @@ -0,0 +1,22 @@ +{ + "userScanner-title": "Subnet nodes scanning", + "userScanner-visible": "Display mode", + "userScanner-loading": "Scanning nodes", + "userScanner-refresh": "Refresh list", + "userScanner-scan": "Scan nodes", + "userScanner-notFound": "No nodes found", + "tableScanner-online": "Online", + "tableScanner-name": "Name", + "tableScanner-vendor": "Manufacturer", + "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": "Please enter a valid IP address" +} \ No newline at end of file diff --git a/FRONT/DEV/public/locales/ru/common.json b/FRONT/DEV/public/locales/ru/common.json new file mode 100644 index 0000000..f4a33eb --- /dev/null +++ b/FRONT/DEV/public/locales/ru/common.json @@ -0,0 +1,22 @@ +{ + "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-адрес" +} diff --git a/FRONT/DEV/src/api/request.ts b/FRONT/DEV/src/api/request.ts index d0ad0b1..822930d 100644 --- a/FRONT/DEV/src/api/request.ts +++ b/FRONT/DEV/src/api/request.ts @@ -1,13 +1,13 @@ import axios, { AxiosResponse } from "axios"; const axiosWithoutCredentials = axios.create({ - baseURL: `https://localhost:8001`, + baseURL: `http://localhost:8001`, }) export class NetworkService { static createAxiosInstance(ipAddress: string) { return axios.create({ - baseURL: `https://${ipAddress}:8001`, + baseURL: `http://${ipAddress}:8001`, }); } @@ -83,11 +83,11 @@ export class NetworkService { } } - static async scanUser(ipAddress: string): Promise { + static async scanUser(ipAddress: string, ipScanning: string): Promise { try { const axiosInstance = this.createAxiosInstance(ipAddress); return await axiosInstance.post('/subnet/devices',{ - subnet: '' + subnet: ipScanning, }); } catch (error: any) { return error.response; @@ -180,7 +180,15 @@ export class NetworkService { static async exportDevices(ipAddress: string): Promise { try { const axiosInstance = this.createAxiosInstance(ipAddress); - return await axiosInstance.post(`/settings/export`); + 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; } diff --git a/FRONT/DEV/src/components/settings/Settings.tsx b/FRONT/DEV/src/components/settings/Settings.tsx index a086a0d..6cdc1cd 100644 --- a/FRONT/DEV/src/components/settings/Settings.tsx +++ b/FRONT/DEV/src/components/settings/Settings.tsx @@ -146,6 +146,20 @@ export const Settings = ({ onClose }: Props) => { } }; + // Обработка нажатий клавиш + useEffect(() => { + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') + handleSave(); + if (e.key === 'Escape') + handleCancel() + }; + document.addEventListener('keydown', handleKeyPress); + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; + }, []); + return ( 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 index 9c865bf..513691c 100644 --- a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx @@ -4,6 +4,7 @@ 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; @@ -27,6 +28,7 @@ interface ListUsersProps { } 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'); @@ -55,11 +57,10 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps setCurrentPage(page); } - return ( <> - {devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'} + {loading ? t('userScanner-loading') : devices.length > 0 ? t('userScanner-refresh') : t('userScanner-scan')} {devices.length > 0 ? ( @@ -70,7 +71,9 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps }}> {/* Колонка активных устройств */}
-

Активные узлы ({activeDevices.length})

+

+ {t('listUsers-activeNodes')} ({activeDevices.length}) +

{currentActiveDevices.map((device, index) => ( {/* Колонка неактивных устройств */}
-

Неактивные узлы ({inactiveDevices.length})

+

+ {t('listUsers-inactiveNodes')} ({inactiveDevices.length}) +

{currentInactiveDevices.map((device, index) => (
- ) : scanInitiated ? ( -

Узлы не найдены

+ ) : scanInitiated && !loading ? ( +

{t('userScanner-notFound')}

) : null} {totalPages > 1 && ( ` ` export const TableScanner = ({ devices }: TableScannerProps) => { + const {t} = useTranslation('common') const { openModal } = useModalContext(); const nameSetting = useNameSetting(); const ordersColumns = useOrdersColumns(); @@ -96,14 +98,14 @@ export const TableScanner = ({ devices }: TableScannerProps) => { .filter(column => !disabledColumns.includes(ordersColumns.indexOf(column))) .map(column => { switch(column) { - case 'Онлайн': return 'status'; - case 'Имя': return 'name'; - case 'Производитель': return 'vendor'; - case 'IP адрес': return 'ip'; - case 'Открытые порты': return 'ports'; - case 'Последняя активность': return 'activity'; - case 'Заметка': return 'note'; - case 'Действие': return 'action'; + case t('tableScanner-online'): return 'status'; + case t('tableScanner-name'): return 'name'; + case t('tableScanner-vendor'): return 'vendor'; + case t('tableScanner-ip'): return 'ip'; + case t('tableScanner-ports'): return 'ports'; + case t('tableScanner-activity'): return 'activity'; + case t('tableScanner-note'): return 'note'; + case t('tableScanner-action'): return 'action'; default: return ''; } }) @@ -134,7 +136,7 @@ export const TableScanner = ({ devices }: TableScannerProps) => { case 'status': return ; case 'name': - return {item.hostname || (nameSetting === 'ip' ? item.ip : 'Неизвестно')}; + return {item.hostname || (nameSetting === 'ip' ? item.ip : t('tableScanner-unknown'))}; case 'vendor': return {item.vendor}; case 'ip': @@ -166,12 +168,12 @@ export const TableScanner = ({ devices }: TableScannerProps) => { {visibleColumns.map(column => { switch (column) { case 'status': return ; - case 'name': return Имя; - case 'vendor': return Производитель; - case 'ip': return IP адрес; - case 'ports': return Открытые порты; - case 'activity': return Последняя активность; - case 'note': return Заметка; + case 'name': return {t('tableScanner-name')}; + case 'vendor': return {t('tableScanner-vendor')}; + case 'ip': return {t('tableScanner-ip')}; + case 'ports': return {t('tableScanner-ports')}; + case 'activity': return {t('tableScanner-activity')}; + case 'note': return {t('tableScanner-note')}; case 'action': return ; default: return null; } 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 index 771292e..b548618 100644 --- a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx @@ -2,6 +2,7 @@ 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; @@ -25,17 +26,18 @@ interface ListUsersProps { } const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => { + const {t} = useTranslation('common') return ( <> - handleScan()} disabled={loading}> - {devices.length > 0 ? 'Обновить список' : 'Сканировать узлы'} + handleScan()} disabled={loading}> + {loading ? t('userScanner-loading') : devices.length > 0 ? t('userScanner-refresh') : t('userScanner-scan')} {devices.length > 0 ? ( - ) : scanInitiated ? ( -

Узлы не найдены

+ ) : scanInitiated && !loading ? ( +

{t('userScanner-notFound')}

) : null} ); diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx index dbb8821..6bcdc64 100644 --- a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx @@ -1,12 +1,15 @@ import { useIpStore } from "../../../../../store"; import styled, { keyframes } from "styled-components"; -import {useEffect, useState} from "react"; +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, @@ -52,7 +55,7 @@ const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>` height: 100%; width: 100%; background: ${(props) => props.theme.background}; - padding: 50px; + padding: 80px 50px 40px; box-sizing: border-box; gap: 24px; transition: justify-content .4s ease; @@ -60,13 +63,20 @@ const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>` overflow-x: hidden; position: relative; `; -const TitleWrapper = styled.h2` +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; @@ -131,24 +141,38 @@ const ScanningText = styled.span` 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) => { + const handleScan = async (replay: boolean) => { try { - if(!replay){ + if (ipScanning && !validateIp(ipScanning)) { + setIsValid(false); + return; + } + + setIsValid(true); + + if (!replay) { setLoading(true); } setScanInitiated(true); - const res = await NetworkService.scanUser(ipAddress); - console.log(res) + const res = await NetworkService.scanUser(ipAddress, ipScanning); setDevices(res.data.devices || []); } catch (err) { console.error("Ошибка сканирования сети:", err); - setDevices([]) + setDevices([]); } finally { setLoading(false); } @@ -173,8 +197,8 @@ export default function UserScanner() { // Способ отображения const viewOptions: Option[] = [ - { value: 1, label: 'Таблица' }, - { value: 2, label: 'Список' }, + { value: 1, label: t('userScanner-visible_table') }, + { value: 2, label: t('userScanner-visible_list') }, ]; const [selectedView, setSelectedView] = useState