Инициализация в новом репозитории

This commit is contained in:
commit eb1f18f53e
374 changed files with 226678 additions and 0 deletions

View file

@ -0,0 +1,69 @@
import styled from "styled-components"
import { useNavigate } from "react-router-dom"
const Container = styled.div`
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 25px;
text-align: center;
h1, h2, h3, p{
margin: 0;
}
`
const Content = styled.div`
max-width: 450px;
display: flex;
flex-direction: column;
align-items: center;
gap: 100px;
button{
width: 250px;
}
`
const Row = styled.div`
display: flex;
gap: 10px;
align-items: flex-end;
justify-content: center;
margin-left: 50px;
`
const Column = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`
export default function About() {
const navigate = useNavigate()
const redirect = (path: string) => {
navigate(path)
}
return (
<Container>
<Content>
<Row>
<h1>GoNetMan</h1>
<p>v 0.0.1</p>
</Row>
<Column>
<h3>О программе</h3>
GoNetMan - Это приложение представляющее собой панель управления сетевыми настройками в удобном и понятном графическом виде.
</Column>
<p>Copyright © ООО "Гибридные офисные системы"</p>
<button onClick={()=>redirect('/')}>В меню</button>
</Content>
</Container>
)
}

View file

@ -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 (
<Container>
<ButtonContainer>
<IconContainer>
<IconComponent name={buttonNames[11]} width="30" height="30" onClick={()=>setNullPos(true)}/>
</IconContainer>
<QuitButton size="small"/>
</ButtonContainer>
</Container>
)
}

View file

@ -0,0 +1,81 @@
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 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 { setWorkModule } = useWorkSpaceStore()
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>
)
}

View file

@ -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<WebSocket | null>(null);
const [data, setData] = useState<ResponseData[]>([]);
const [wifiData, setWifi] = useState<ResponseNetwork>()
const [hasInited, setHasInited] = useState(false);
const { initedNodes, initedEdges, handleInit } = useInitCanvas();
const [interfaces, setInterfaces] = useState<Interfaces[]>([]);
const [edgeData, setEdgeData] = useState<IEdge[]>([]);
const [nodes, setNodes, onNodesChange] = useNodesState([] as any[]);
const [edges, setEdges] = useEdgesState([] as any[]);
const { isPanDisabled } = usePannableStore()
const [changeArray, setChangeArray] = useState<any[]>([]);
const { groups } = useGroupStore();
const {isNullPos, setNullPos} = usePosStore()
useEffect(() => {
if (startConnection) {
openModal(ModalMap['2'], 'Идет загрузка данных');
setNodes([])
setEdges([])
setHasInited(false)
initializationAttempt.current = 0;
const newSocket = new WebSocket(`wss://${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 (
<>
<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

@ -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(
<Container>
<ToolBar/>
<ToolSpace/>
</Container>
)
}

View file

@ -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 (
<Button onClick={collapseMenu}>
<IconContainer>
<Icon name={menuId} width="30" height="30" setColor={iconColor} />
</IconContainer>
</Button>
)
}

View file

@ -0,0 +1,73 @@
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} = useWorkSpaceStore()
return (
<Container>
<ButtonContainer>
{toolBarMenu && toolBarMenu.map((menu) => (
<ToolBarButton menuId={menu.id} key={menu.id} />
))}
</ButtonContainer>
<ButtonContainer>
<Button>
<IconContainer>
<IconComponent name={isAdmin ? buttonNames[10] : buttonNames[13]} width="30" height="30" onClick={() => openModal(ModalMap['5'])}/>
</IconContainer>
</Button>
<ThemeButton />
<Button>
<IconContainer>
<IconComponent name={buttonNames[4]} width="30" height="30" onClick={()=>setWorkModule(workSpaceNames[2])}/>
</IconContainer>
</Button>
</ButtonContainer>
</Container>
)
}

View file

@ -0,0 +1,246 @@
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"
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
padding: 20px;
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 [currentDevice, setCurrentDevice] = useState<CurrentDevice>({
address: 'localhost',
hostname: ''
})
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
return new Promise((resolve) => {
const testSocket = new WebSocket(`wss://${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)
setIpAddress(data)
};
const handleDelete = (number: number, adress: string) => {
deleteDevice(number)
if(adress == ipAddress){
setIpAddress('localhost')
}
};
const switchWs = async (ip: string) => {
// Проверяем доступность WebSocket
const isAvailable = await checkWebSocketAvailability(ip);
if (!isAvailable) {
openModal(ModalMap['3'], {
message: `Не удалось подключиться к серверу по адресу ${ip}`,
});
return;
}
// Если WebSocket доступен, обновляем IP и переключаем модуль
setIpAddress(ip);
setWorkModule(workSpaceNames[3]);
};
useEffect(() => {
if (devices.length === 0) {
setIpAddress('localhost');
}
}, [devices]);
return (
<Container>
<Title>
<p className="unselectable">Доступные узлы</p>
</Title>
<DeviceBlock>
<DeviceControl className="unselectable" onClick={() => switchWs(currentDevice.address)}>
<p>Текущее устройство</p>
</DeviceControl>
{currentDevice.hostname == '' ? (
<Text className="paddingP">Адрес: Загрузка<AnimDot /></Text>
) : (
<p className="paddingP">Адрес: {currentDevice.hostname}</p>
)}
</DeviceBlock>
<DevicesContainer>
<DevicesHeader className="unselectable" onClick={collapseMenu}>
<p>Дополнительные узлы</p>
<IconComponent name={isOpen ? buttonNames[5] : buttonNames[6]} />
</DevicesHeader>
<DevicesContent $isactive={isOpen.toString()} >
{devices.length > 0 &&
devices.map((device, index) => (
<DeviceBlock key={index}>
<DeviceControl className="unselectable" onClick={() => switchWs(device.address)}>
<p>Узел {device.number}</p>
<IconComponent
width="20"
height="20"
name={buttonNames[2]}
setColor="white"
onClick={(e: any) => {
e.stopPropagation(); // Предотвращаем всплытие события
handleDelete(device.number, device.address);
}}
/>
</DeviceControl>
<p className="paddingP">Адрес: {device.address}</p>
</DeviceBlock>
))
}
<PlusBlock className="unselectable" onClick={handleAdd}>
+
</PlusBlock>
</DevicesContent>
</DevicesContainer>
</Container>
)
}

View file

@ -0,0 +1,27 @@
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`
background-color: ${(props) => props.theme.secondary};
width: 250px;
font-size: 14px;
flex: 3;
`
export const ToolSpace = () => {
const { toolBar } = useToolBarStore()
const name = toolBar?.current || ''
const { isActive, isOpen } = useToolBar(name)
if (!isActive || !isOpen) {
return null
}
return (
<Container>
<Space name={name} />
</Container>
)
}

View file

@ -0,0 +1,82 @@
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";
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]);
console.log('aboba');
return (
<Section>
<FlowContainer>
<SideBar />
{workModule === workSpaceNames[1] && <HelloWindow /> }
{workModule === workSpaceNames[3] &&
<WorkSection>
<Header />
<ReactFlowProvider>
<NetworkInterfaces
key={currentWs}
startConnection={true}
ws={currentWs}
/>
</ReactFlowProvider>
</WorkSection>
}
</FlowContainer>
</Section>
)
}