фронт

This commit is contained in:
test 2025-06-09 10:31:14 +05:00
parent eb1f18f53e
commit d67ea17fd7
160 changed files with 2611 additions and 5680 deletions

View file

@ -1,69 +1,213 @@
import styled from "styled-components"
import { useNavigate } from "react-router-dom"
const Container = styled.div`
height: 100%;
import { MainButton } from "../../../UI/buttons/mainButton"
import { workSpaceNames } from "../../../../const/workspace/names"
import { useWorkSpaceStore } from "../../../../store"
import styled from "styled-components"
import ThemeButton from "../../../UI/buttons/toggletheme"
type Props = {
from: 'reg' | 'helloWindow' | 'workSpace'
onBack?: () => void
}
const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
text-align: center;
h1, h2, h3, p{
margin: 0;
}
height: 100%;
width: 100%;
background: ${(props => props.theme.background)};
padding: 50px;
box-sizing: border-box;
`
const Content = styled.div`
max-width: 450px;
max-width: 1200px;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
box-sizing: border-box;
padding: 25px 50px;
background: ${(props => props.theme.secondary)};
border-radius: 18px;
`
const Listing = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
`
const List = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
margin-left: 10px;
`
const Title = styled.div`
font-size: 20px;
`
const SubTitle = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
p{
font-size: 18px;
}
`
const Links = styled.div`
display: flex;
flex-direction: column;
gap: 20px;
`
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: center;
margin-top: 50px;
`
const ButtonContainer = styled.div`
width: 100%;
display: flex;
justifi-content: flex-start;
align-items: center;
gap: 100px;
button{
width: 250px;
`
const MainTitle = styled.div`
width: 100%;
display: flex;
gap: 15px;
align-items: center;
justify-content: center;
padding-top: 50px;
box-sizing: border-box;
img{
width: 40px;
height: 40px;
}
`
const Row = styled.div`
display: flex;
gap: 10px;
align-items: flex-end;
justify-content: center;
margin-left: 50px;
`
const Column = styled.div`
const Text = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
gap: 30px;
`
export default function About() {
const navigate = useNavigate()
const redirect = (path: string) => {
navigate(path)
export default function About({ from, onBack }: Props) {
const { setWorkModule } = useWorkSpaceStore()
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 (
<Container>
<Wrapper>
<Content>
<Row>
<h1>GoNetMan</h1>
<p>v 0.0.1</p>
</Row>
<Column>
<h3>О программе</h3>
GoNetMan - Это приложение представляющее собой панель управления сетевыми настройками в удобном и понятном графическом виде.
</Column>
<MainTitle>
<img src="./assets/images/gossys.png" alt="" />
<div style={{width: '100%', display: "flex", justifyContent: 'center'}}><h1>О приложении</h1></div>
</MainTitle>
<p>Copyright © ООО "Гибридные офисные системы"</p>
<button onClick={()=>redirect('/')}>В меню</button>
<Text>
<Title>
<p>Приложение ГоNets является минимально работоспособным кроссплатформенным продуктом (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.</p>
</Title>
<Listing>
<p>Данное приложение умеет:</p>
<List>
<p>- Отображать в графическом виде активные сетевые ресурсы</p>
<p>- Настраивать сетевые параметры локального и удаленных сетевых ресурсов</p>
<p>- Отображать и изменять взаимодействие сетевых ресурсов с установленным Клиентом ГоNets</p>
</List>
</Listing>
<SubTitle>
<p>Программное обеспечение исключительно для ознакомления и домашнего использования.</p>
<p>Для коммерческого использования приобретите версию ГоNets-PRO (Подробная информация на странице продукта)</p>
</SubTitle>
<Links>
<LinkBlock>
<p>Ссылка на домашнюю страницу:</p>
<a href="https://gossys.dev/pages/projects/gonets">https://gossys.dev/pages/projects/gonets</a>
</LinkBlock>
<LinkBlock>
<p>Инструкции и информация:</p>
<a href="https://wiki.gossys.dev/ru/gonets">https://wiki.gossys.dev/ru/gonets</a>
</LinkBlock>
<LinkColumn>
<p>Распространяется под лицензией GosysPUB текущей версии.</p>
<LinkBlock>
<p>Полный текст актуальной лицензии:</p>
<a href="https://wiki.gossys.dev/ru/lic/GosysPUB">https://wiki.gossys.dev/ru/lic/GosysPUB</a>
</LinkBlock>
</LinkColumn>
</Links>
<Copyright>
<p>Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.</p>
</Copyright>
</Text>
<ButtonContainer>
{showBackButton && backHandler && (
<MainButton
title="Назад"
onClick={backHandler}
style={{ marginRight: '10px' }}
/>
)}
{from == 'reg' &&
<ThemeButton />
}
</ButtonContainer>
</Content>
</Container>
</Wrapper>
)
}

View file

@ -1,81 +1,82 @@
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 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"
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 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 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 ButtonWrapper = styled.div`
display: flex;
gap: 75px;
flex-direction: column;
`
const ButtonContainer = styled.div`
display: flex;
gap: 15px;
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;
const Title = styled.div`
display: flex;
align-items: flex-end;
gap: 10px;
h1{
font-size: 52px;
}
`
h1{
font-size: 52px;
}
`
export const HelloWindow = () => {
export const HelloWindow = () => {
const { setWorkModule } = useWorkSpaceStore()
const { setWorkModule } = useWorkSpaceStore()
return (
<Wrapper>
<Container className="unselectable">
<Title>
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
</Title>
return (
<Wrapper>
<Container className="unselectable">
<Title>
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
</Title>
<ButtonWrapper>
<ButtonContainer>
<MainButton
title="Настройки сети"
icon={buttonNames[3]}
iconSize={{ width: '32', height: '32' }}
onClick={() => setWorkModule(workSpaceNames[3])}
/>
<MainButton
title="О программе"
icon={buttonNames[4]}
iconSize={{ width: '32', height: '32' }}
/>
</ButtonContainer>
</ButtonWrapper>
</Container>
</Wrapper>
)
}
<ButtonWrapper>
<ButtonContainer>
<MainButton
title="Настройки сети"
icon={buttonNames[3]}
iconSize={{ width: '32', height: '32' }}
onClick={() => setWorkModule(workSpaceNames[3])}
/>
<MainButton
title="О программе"
icon={buttonNames[4]}
iconSize={{ width: '32', height: '32' }}
onClick={()=>setWorkModule(workSpaceNames[4])}
/>
</ButtonContainer>
</ButtonWrapper>
</Container>
</Wrapper>
)
}

View file

@ -0,0 +1,75 @@
import React from 'react';
import styled from 'styled-components';
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
import {DeviceUser} from "../../../../../../store/useDeviceStore";
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 activeDevices = devices.filter(device => device.status === 'online');
const inactiveDevices = devices.filter(device => device.status !== 'online');
return (
<>
<ScanBtn onClick={handleScan} disabled={loading}>
{devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
</ScanBtn>
{devices.length > 0 ? (
<div style={{
display: 'flex',
width: '70%',
gap: '20px',
}}>
{/* Колонка активных устройств */}
<div style={{ flex: 1 }}>
<h3 style={{ color: '#4CAF50', marginBottom:'12px' }}>Активные устройства ({activeDevices.length})</h3>
{activeDevices.map((device, index) => (
<DeviceCard
key={index}
status="online"
device={device}
/>
))}
</div>
{/* Колонка неактивных устройств */}
<div style={{ flex: 1 }}>
<h3 style={{ color: '#F44336', marginBottom:'12px' }}>Неактивные устройства ({inactiveDevices.length})</h3>
{inactiveDevices.map((device, index) => (
<DeviceCard
key={index}
status="offline"
device={device}
/>
))}
</div>
</div>
) : scanInitiated ? (
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
) : null}
</>
);
};
export default ListUsers;

View file

@ -0,0 +1,45 @@
import React from 'react';
import styled from 'styled-components';
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
import {DeviceUser} from "../../../../../../store/useDeviceStore";
import TableScanner from "../../../../../UI/tables/TableScanner";
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) => {
return (
<>
<ScanBtn onClick={handleScan} disabled={loading}>
{devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
</ScanBtn>
{devices.length > 0 ? (
<TableScanner devices={devices} />
) : scanInitiated ? (
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
) : null}
</>
);
};
export default ListUsers;

View file

@ -0,0 +1,185 @@
import { useIpStore } from "../../../../../store";
import styled, { keyframes } from "styled-components";
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";
type Option ={
value: number,
label: string,
}
// Анимации
const ring1 = keyframes`
0% { width: 250px; height: 250px; }
50% { width: 280px; height: 280px; }
100% { width: 250px; height: 250px; }
`;
const ring2 = keyframes`
0% { width: 400px; height: 400px; }
50% { width: 430px; height: 430px; }
100% { width: 400px; height: 400px; }
`;
const ring3 = keyframes`
0% { width: 550px; height: 550px; }
50% { width: 580px; height: 580px; }
100% { width: 550px; height: 550px; }
`;
const dotAnimation = keyframes`
0% { content: '.'; }
33% { content: '..'; }
66% { content: '...'; }
100% { content: '.'; }
`;
// Стили
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: 50px;
box-sizing: border-box;
gap: 24px;
transition: justify-content .4s ease;
overflow-y: auto;
overflow-x: hidden;
position: relative;
`;
const TitleWrapper = styled.h2`
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 32px;
`
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.accent};
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
&.center{
width: 60px;
height: 60px;
}
&.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;
}
&.ring3 {
opacity: 0.04;
width: 550px;
height: 550px;
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
}
}
`;
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 { ipAddress } = useIpStore();
const [loading, setLoading] = useState(false);
const { devices, setDevices } = useNetworkStore();
const [scanInitiated, setScanInitiated] = useState(devices.length > 0);
// Сканирование
const handleScan = async () => {
try {
setLoading(true);
setScanInitiated(true);
const res = await NetworkService.scanUser(ipAddress);
setDevices(res.data.devices || []);
console.log(res.data.devices);
} catch (err) {
console.error("Ошибка сканирования сети:", err);
setDevices([])
} finally {
setLoading(false);
}
};
// Способ отображения
const viewOptions: Option[] = [
{ value: 1, label: 'Таблица' },
{ value: 2, label: 'Список' },
];
const [selectedView, setSelectedView] = useState<Option>(viewOptions[0]);
const handleViewChange = (option: Option) => {
setSelectedView(option);
};
return (
<Wrapper scanInitiated={scanInitiated} loading={loading}>
{(!scanInitiated || loading) && (<TitleWrapper>Сканирование пользователей в сети</TitleWrapper>)}
<UserLocate loading={loading} scanInitiated={scanInitiated}>
<span className="center"></span>
<span className="ring1"></span>
<span className="ring2"></span>
<span className="ring3"></span>
</UserLocate>
{scanInitiated && !loading && (
<DropdownType options={viewOptions} value={selectedView} onChange={handleViewChange} label="Способ отображения" />
)}
{loading ? (
<LoadingText>
Сканирование сети<ScanningText />
</LoadingText>
) : selectedView.value == 1 ? (
<TableUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
):(
<ListUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
)}
</Wrapper>
);
}

View file

@ -0,0 +1,234 @@
import { useIpStore } from "../../../../../store";
import styled, { keyframes } from "styled-components";
import React, { useState } from "react";
import { NetworkService } from "../../../../../api/request";
import useDeviceStore from "../../../../../store/useDeviceStore";
import WifiDot, {WifiNetworkScanner} from "./modules/WifiDot";
import ModalWifiScaner from "./modules/ModalWifiScaner";
import useNetworkStore from "../../../../../store/useDeviceStore";
type Option ={
value: number,
label: string,
}
// Анимации
const ring1 = keyframes`
0% { width: 250px; height: 250px; }
50% { width: 280px; height: 280px; }
100% { width: 250px; height: 250px; }
`;
const ring2 = keyframes`
0% { width: 400px; height: 400px; }
50% { width: 430px; height: 430px; }
100% { width: 400px; height: 400px; }
`;
const ring3 = keyframes`
0% { width: 550px; height: 550px; }
50% { width: 580px; height: 580px; }
100% { width: 550px; height: 550px; }
`;
const dotAnimation = keyframes`
0% { content: '.'; }
33% { content: '..'; }
66% { content: '...'; }
100% { content: '.'; }
`;
// Стили
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%;
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;
`
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;
}
&.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;
}
&.ring3 {
opacity: 0.04;
width: 550px;
height: 550px;
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
}
}
`;
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<{loading: boolean}>`
position: relative;
padding: 14px 22px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 12px;
font-size: 18px;
transition: .4s ease;
transform: ${({loading}) => (!loading ? "translateY(240px)" : "translateY(0)")};
&:hover{
transform: ${({loading}) => (!loading ? "translateY(240px)" : "translateY(0)")} scale(1.03);
}
`
const data = [
{
ssid: "Huawei",
bssid: "wef",
channel: 0,
signal:2,
encryption: "wef",
frequency: "ewf",
vendor: "striwefwng",
hidden: false
},
{
ssid: "Gu-2",
bssid: "grgerger",
channel: 0,
signal: 2,
encryption: "weergref",
frequency: "eergewf",
vendor: "grgregerge",
hidden: false
},
{
ssid: "Huawei_wifi2",
bssid: "wqdqw",
channel: 2,
signal: 3,
encryption: "wefwe",
frequency: "wef",
vendor: "fwefw",
hidden: false
},
];
export function WifiScanner() {
const {ipAddress} = useIpStore();
const [loading, setLoading] = useState(false);
const [scanInitiated, setScanInitiated] = useState(false);
const {wifiNetworks, setWifiNetworks} = useNetworkStore();
const [currentNetwork, setCurrentNetwork] = useState<WifiNetworkScanner | null>(null);
const handleScan = async () => {
try {
setLoading(true);
setScanInitiated(true);
const res = await NetworkService.scanWifi(ipAddress);
const networks = res.data.access_points || [];
setWifiNetworks(networks);
} catch (err) {
console.error("Ошибка сканирования сети:", err);
setWifiNetworks([]);
} finally {
setLoading(false);
}
};
return (
<Wrapper scanInitiated={scanInitiated} loading={loading}>
{(!scanInitiated || loading) && (
<TitleWrapper>Сканирование Wi-Fi сети</TitleWrapper>
)}
<UserLocate loading={loading} scanInitiated={scanInitiated}>
<span className="center"></span>
<span className="ring1"></span>
<span className="ring2"></span>
<span className="ring3"></span>
{data.map((network) => (
<WifiDot
key={network.bssid}
network={network}
onSelect={setCurrentNetwork}
/>
))}
</UserLocate>
{currentNetwork && (
<ModalWifiScaner
onClose={() => setCurrentNetwork(null)}
network={currentNetwork}
/>
)}
{loading ? (
<LoadingText>
Сканирование сети <ScanningText/>
</LoadingText>
) : (
<ScanBtn onClick={handleScan} disabled={loading} loading={loading}>
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
</ScanBtn>
)}
</Wrapper>
);
}

View file

@ -0,0 +1,142 @@
import React from 'react';
import styled from 'styled-components';
import {WifiNetworkScanner} from "./WifiDot";
import {IconComponent} from "../../../../../../const/buttons/icons";
import {buttonNames} from "../../../../../../const/buttons/names";
const Modal = styled.div`
display: flex;
justify-content: center;
flex-direction: column;
background-color: ${(props) => props.theme.secondary};
border-radius: 24px;
padding: 20px 32px;
top: 14%;
right: 18%;
position: absolute;
z-index: 20;
min-width: 210px;
&:hover .close-btn{
opacity: 1;
visibility: visible;
}
`
const CloseBtn = styled.div`
position: absolute;
top: -20px;
right: -20px;
cursor: pointer;
background-color: white;
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: #d6d6d6;
}
`
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;
}
`
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: center;
gap: 12px;
width: 100%;
font-size: 14px;
h6{
margin: 0;
padding: 0;
font-size: 1em;
}
`
const SignalBarsWrapper = styled.div`
display: flex;
align-items: flex-end;
gap: 2px;
height: 26px;
margin-left: 8px;
`;
const SignalBar = styled.div<{ active: boolean }>`
width: 9px;
background: ${({ active, theme }) => active ? theme.accent : '#ccc'};
border-radius: 2px;
&:nth-child(1) { height: 8px; }
&:nth-child(2) { height: 14px; }
&:nth-child(3) { height: 22px; }
`;
const ModalWifiScaner = ({ network,onClose }: { network: WifiNetworkScanner, onClose: ()=>void }) => {
return (
<Modal>
<CloseBtn className={'close-btn'} onClick={onClose}>
<IconComponent name={buttonNames[12]} width="30" height="30" />
</CloseBtn>
<ModalTitle>
<h4>{network.ssid}</h4>
<SignalBarsWrapper>
<SignalBar active={network.signal >= 1} />
<SignalBar active={network.signal >= 2} />
<SignalBar active={network.signal >= 3} />
</SignalBarsWrapper>
</ModalTitle>
<ModalContent>
<ModalRow>
<h6>BSSID:</h6>
<p>{network.bssid}</p>
</ModalRow>
<ModalRow>
<h6>Производитель:</h6>
<p>{network.vendor}</p>
</ModalRow>
<ModalRow>
<h6>Сигнал:</h6>
<p>{network.signal} dBm</p>
</ModalRow>
<ModalRow>
<h6>Канал:</h6>
<p>{network.channel}</p>
</ModalRow>
<ModalRow>
<h6>Шифрование:</h6>
<p>{network.encryption}</p>
</ModalRow>
<ModalRow>
<h6>Частота:</h6>
<p>{network.frequency}</p>
</ModalRow>
<ModalRow>
<h6>Скрытая сеть:</h6>
<p>{network.hidden ? 'Да' : 'Нет'}</p>
</ModalRow>
</ModalContent>
</Modal>
);
};
export default ModalWifiScaner;

View file

@ -0,0 +1,69 @@
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;
}
//Точка вайфайл
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 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(randomAngle) * randomRadius,
y: Math.sin(randomAngle) * randomRadius
};
};
const getPosition = (signal: number, bssid: string) => {
const strength = Math.min(3, Math.max(1, signal));
const ringSizes = { 1: 550, 2: 400, 3: 250 };
const ringSize = ringSizes[strength as 1 | 2 | 3];
const offset = getPositionInRing(bssid, ringSize);
return {
top: `calc(50% - ${offset.y}px)`,
left: `calc(50% - ${offset.x}px)`
}
};
const position = getPosition(network.signal, network.bssid);
return (
<Dot top={position.top} left={position.left} onClick={() => onSelect(network)}>
{network.ssid}
</Dot>
);
};
export default WifiDot;

View file

@ -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);
@ -93,7 +93,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
newSocket.onmessage = (event) => {
const newData = JSON.parse(event.data);
setData(() => [
newData.activation,
newData.deactivation,
@ -126,7 +126,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
const activationData = data[0].interfaces;
const wifi = activationData.find((el: any) => el.type === 'WIFI');
setInterfaces(newInterfaces);
setEdgeData(newEdgeData);
if (wifi) { setWifi(wifi); }
@ -192,13 +192,13 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
}, [nodes, loader])
const onEdgesChange = useCallback(
(changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
(changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
);
const onConnect = useCallback(
(connection: any) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
(connection: any) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
const handleNodesChange = (changes: any) => {
@ -254,29 +254,29 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
}, [isNullPos]);
return (
<>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
style={{ background: '#F7F9FB' }}
minZoom={0}
maxZoom={4}
fitView
fitViewOptions={{ padding: 0.1 }}
attributionPosition="bottom-left"
snapToGrid
snapGrid={[20, 20]}
panOnDrag={!isPanDisabled}
nodeTypes={nodeTypes}
proOptions={proOptions}
>
<Background gap={20} size={1} bgColor={theme.background} />
<Controls fitViewOptions={{ padding: 0.1 }} />
</ReactFlow>
</>
<>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
style={{ background: '#F7F9FB' }}
minZoom={0}
maxZoom={4}
fitView
fitViewOptions={{ padding: 0.1 }}
attributionPosition="bottom-left"
snapToGrid
snapGrid={[20, 20]}
panOnDrag={!isPanDisabled}
nodeTypes={nodeTypes}
proOptions={proOptions}
>
<Background gap={20} size={1} bgColor={theme.background} />
<Controls fitViewOptions={{ padding: 0.1 }} />
</ReactFlow>
</>
);
};

View file

@ -41,8 +41,8 @@ export const ToolBar = () => {
const toolBarMenu = toolBar?.toolSpace
const { openModal } = useModalContext()
const {isAdmin} = useAdminStore()
const {setWorkModule} = useWorkSpaceStore()
const { isAdmin } = useAdminStore()
const { setWorkModule, workModule } = useWorkSpaceStore()
return (
<Container>
@ -50,13 +50,22 @@ export const ToolBar = () => {
{toolBarMenu && toolBarMenu.map((menu) => (
<ToolBarButton menuId={menu.id} key={menu.id} />
))}
<Button>
<IconContainer>
<IconComponent name={buttonNames[14]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[5])} />
</IconContainer>
</Button>
<Button>
<IconContainer>
<IconComponent name={buttonNames[14]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[6])} />
</IconContainer>
</Button>
</ButtonContainer>
<ButtonContainer>
<Button>
<IconContainer>
<IconComponent name={isAdmin ? buttonNames[10] : buttonNames[13]} width="30" height="30" onClick={() => openModal(ModalMap['5'])}/>
<IconComponent name={isAdmin ? buttonNames[10] : buttonNames[13]} width="30" height="30" onClick={() => openModal(ModalMap['5'])} />
</IconContainer>
</Button>
@ -64,7 +73,12 @@ export const ToolBar = () => {
<Button>
<IconContainer>
<IconComponent name={buttonNames[4]} width="30" height="30" onClick={()=>setWorkModule(workSpaceNames[2])}/>
<IconComponent name={buttonNames[4]} width="30" height="30"
onClick={() => workModule === workSpaceNames[3]
? setWorkModule(workSpaceNames[2])
: setWorkModule(workSpaceNames[4])
}
/>
</IconContainer>
</Button>
</ButtonContainer>

View file

@ -18,7 +18,7 @@ const Container = styled.div`
align-items: center;
gap: 15px;
padding: 20px;
width: 210px;
p{
margin: 0;
}
@ -115,12 +115,12 @@ export const DevicesBar = () => {
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
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);
}, 500
);
testSocket.close();
resolve(false);
}, 500
);
testSocket.onopen = () => {
clearTimeout(timeout);
@ -156,8 +156,8 @@ export const DevicesBar = () => {
};
const handleAddDeviceComplete = (data: string) => {
addDevice(data)
setIpAddress(data)
addDevice(data);
switchWs(data);
};
const handleDelete = (number: number, adress: string) => {
@ -170,7 +170,6 @@ export const DevicesBar = () => {
};
const switchWs = async (ip: string) => {
// Проверяем доступность WebSocket
const isAvailable = await checkWebSocketAvailability(ip);
if (!isAvailable) {
openModal(ModalMap['3'], {
@ -179,7 +178,6 @@ export const DevicesBar = () => {
return;
}
// Если WebSocket доступен, обновляем IP и переключаем модуль
setIpAddress(ip);
setWorkModule(workSpaceNames[3]);
};
@ -198,12 +196,12 @@ export const DevicesBar = () => {
<DeviceBlock>
<DeviceControl className="unselectable" onClick={() => switchWs(currentDevice.address)}>
<p>Текущее устройство</p>
<p>Корневой адрес</p>
</DeviceControl>
{currentDevice.hostname == '' ? (
<Text className="paddingP">Адрес: Загрузка<AnimDot /></Text>
<Text className="paddingP">Название: Загрузка<AnimDot /></Text>
) : (
<p className="paddingP">Адрес: {currentDevice.hostname}</p>
<p className="paddingP">Название: {currentDevice.hostname}</p>
)}
</DeviceBlock>

View file

@ -3,11 +3,15 @@ import { useToolBar } from "../../../../hooks/useToolBar"
import { Space } from "../../../../const/menus/sideBar/components"
import styled from "styled-components"
const Container = styled.div`
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 = () => {
@ -15,12 +19,12 @@ export const ToolSpace = () => {
const name = toolBar?.current || ''
const { isActive, isOpen } = useToolBar(name)
if (!isActive || !isOpen) {
if (!isActive) {
return null
}
return (
<Container>
<Container isOpen={isOpen}>
<Space name={name} />
</Container>
)

View file

@ -8,6 +8,9 @@ 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;
@ -54,16 +57,32 @@ export default function WorkSpace() {
setIpAddress(currentWs);
}, [currentWs]);
console.log('aboba');
localStorage.getItem('workSpace-storage')
return (
<Section>
<FlowContainer>
<SideBar />
{workModule === workSpaceNames[1] && <HelloWindow /> }
{workModule === workSpaceNames[1] && <HelloWindow />}
{workModule === workSpaceNames[2] && (
<About
from={"workSpace"}
/>
)}
{workModule === workSpaceNames[4] && (
<About
from={"helloWindow"}
/>
)}
{workModule === workSpaceNames[5] && (
<UserScanner />
)}
{workModule === workSpaceNames[6] && (
<WifiScanner />
)}
{workModule === workSpaceNames[3] &&
<WorkSection>
<Header />