фронт

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,2 +1 @@
/node_modules
/dist
/node_modules

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 966 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

Before After
Before After

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GoNets</title>
<link rel="icon" type="image/x-icon" href="./assets/icon-4fEsvohP.png" />
<script type="module" crossorigin src="./assets/index-CAnb47c8.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DWFkiel1.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -1,28 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

View file

@ -1,12 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GoNets</title>
<link rel="icon" type="image/x-icon" href="assets/icon.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

View file

@ -1,54 +0,0 @@
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 {
ModalProvider,
useModalContext,
ThemeProvider
} from './src/store';
import WindowComponent from './src/components/modalwindows/modalWindow.tsx';
import Router from './src/router.tsx';
const MainComponent = () => {
const checkAdmin = useCheckAdmin()
const {ipAddress} = useIpStore()
useEffect(() => {
const checkAdminStatus = async () => {
await checkAdmin()
}
checkAdminStatus()
}, [ipAddress])
const { isOpen, modalType, modalData } = useModalContext();
return (
<>
<WindowComponent isOpen={isOpen} type={modalType} data={modalData} />
<Router />
</>
);
};
const container = document.getElementById('root');
if (!container) {
throw new Error('Root element with id "root" not found');
}
const root = createRoot(container);
root.render(
<ThemeProvider>
<HashRouter>
<ModalProvider>
<MainComponent />
</ModalProvider>
</HashRouter>
</ThemeProvider>
);

View file

@ -44,6 +44,7 @@ app.whenReady().then(() => {
}
callback(-2);
});
Menu.setApplicationMenu(null);
createWindow();
});

View file

@ -1,85 +0,0 @@
import axios, { AxiosResponse } from "axios";
const axiosWithoutCredentials = axios.create({
baseURL: `https://localhost:8001`,
})
export class NetworkService {
static createAxiosInstance(ipAddress: string) {
return axios.create({
baseURL: `https://${ipAddress}:8001`,
});
}
static async checkIp(ipAddress: string, data: any): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.post('/ip/check_ip', data);
} catch (error: any) {
return error.response;
}
}
static async saveIp(ipAddress: string, data: any): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.post('/ip/apply_ip', data);
} catch (error: any) {
return error.response;
}
}
static async getHostname(): Promise<AxiosResponse>{
try{
const response = await axiosWithoutCredentials.get('/network/get_hostname')
return response
} catch (error: any){
return error.response
}
}
static async createUser(ipAddress: string, data: any): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.post('/auth/register', data);
} catch (error: any) {
return error.response;
}
}
static async login(ipAddress: string, data: any): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.post('/auth/login', data);
} catch (error: any) {
return error.response;
}
}
static async logout(ipAddress: string): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.get('/auth/logout');
} catch (error: any) {
return error.response;
}
}
static async checkServerConf(ipAddress: string): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.get('/auth/check_json');
} catch (error: any) {
return error.response;
}
}
static async whoami(ipAddress: string): Promise<AxiosResponse> {
try {
const axiosInstance = this.createAxiosInstance(ipAddress);
return await axiosInstance.get('/auth/me');
} catch (error: any) {
return error.response;
}
}
}

View file

@ -1,108 +0,0 @@
.react-flow {
/* Custom Variables */
--xy-theme-selected: #F57DBD;
--xy-theme-hover: #C5C5C5;
--xy-theme-edge-hover: black;
--xy-theme-color-focus: #E8E8E8;
/* Built-in Variables see https://reactflow.dev/learn/customization/theming */
--xy-node-border-default: 1px solid #EDEDED;
/* --xy-node-boxshadow-default:
0px 3.54px 4.55px 0px #00000005,
0px 3.54px 4.55px 0px #0000000D,
0px 0.51px 1.01px 0px #0000001A; */
--xy-node-border-radius-default: 8px;
--xy-handle-background-color-default: #ffffff;
--xy-handle-border-color-default: #AAAAAA;
--xy-edge-label-color-default: #505050;
}
.react-flow.dark {
--xy-node-boxshadow-default:
0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.05), /* light shadow */
0px 3.54px 4.55px 0px rgba(255, 255, 255, 0.13), /* medium shadow */
0px 0.51px 1.01px 0px rgba(255, 255, 255, 0.2); /* smallest shadow */
--xy-theme-color-focus: #535353;
}
/* Customizing Default Theming */
.react-flow__node {
/* box-shadow: var(--xy-node-boxshadow-default);
border-radius: var(--xy-node-border-radius-default);
background-color: var(--xy-node-background-color-default); */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-size: 12px;
flex-direction: column;
/* border: var(--xy-node-border-default);
color: var(--xy-node-color, var(--xy-node-color-default)); */
pointer-events: all;
user-select: none;
}
.react-flow__node.selectable:focus {
box-shadow: 0px 0px 0px 4px var(--xy-theme-color-focus);
border-color: #d9d9d9;
}
.react-flow__node.selectable:focus:active {
box-shadow: var(--xy-node-boxshadow-default);
}
.react-flow__node.selectable:hover,
.react-flow__node.draggable:hover {
border-color: var(--xy-theme-hover);
}
.react-flow__node.selectable.selected {
border-color: var(--xy-theme-selected);
box-shadow: var(--xy-node-boxshadow-default);
}
.react-flow__node-group {
background-color: rgba(207, 182, 255, 0.4);
border-color: #9E86ED;
}
.react-flow__edge.selectable:hover .react-flow__edge-path,
.react-flow__edge.selectable.selected .react-flow__edge-path {
stroke: var(--xy-theme-edge-hover);
}
.react-flow__handle {
width: 15px;
height: 15px;
}
.react-flow__handle.connectionindicator:hover {
pointer-events: all;
border-color: var(--xy-theme-edge-hover);
background-color: white;
}
.react-flow__handle.connectionindicator:focus ,
.react-flow__handle.connectingfrom,
.react-flow__handle.connectingto {
border-color: var(--xy-theme-edge-hover);
}
.react-flow__node-resizer {
border-radius: 0;
border: none;
}
.react-flow__resize-control.handle {
background-color: #ffffff;
border-color: #9E86ED;
border-radius: 0;
width: 5px;
height: 5px;
}

View file

@ -1,36 +0,0 @@
html,
body {
margin: 0;
font-family: sans-serif;
box-sizing: border-box;
min-height: 100%;
height: 100%;
}
#root{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
button{
background-color: #006666;
border: none;
padding: 6px 10px;
border-radius: 8px;
color: white;
font-size: 14px;
cursor: pointer;
&:hover{
background-color: #255252;
}
}

View file

@ -1,29 +0,0 @@
import styled, { keyframes } from "styled-components";
const blink = keyframes`
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
`;
interface DotProps {
$delay: number;
}
const Dot = styled.span<DotProps>`
animation: ${blink} 1s infinite;
animation-delay: ${({ $delay }) => $delay}s;
margin: 0;
`;
const Wrapper = styled.div`
display: flex;
gap: 4px;
`;
export const AnimDot = () => (
<Wrapper>
<Dot $delay={0}>.</Dot>
<Dot $delay={0.2}>.</Dot>
<Dot $delay={0.4}>.</Dot>
</Wrapper>
);

View file

@ -1,75 +0,0 @@
import styled from "styled-components";
import { useTheme } from "styled-components";
import { IconComponent } from "../../../const/buttons/icons";
const Button = styled.button.withConfig({
shouldForwardProp: (prop) => !['color', '$hoverсolor', 'size', '$iconpos'].includes(prop),
})<{ color: string; size?: string, $hoverсolor: string, $iconpos: string }>`
background-color: ${(props) => props.color};
border: none;
padding: ${(props) =>
props.size === "small"
? "4px 8px"
: props.size === "big"
? "10px 20px"
: "6px 10px"}; // По умолчанию
border-radius: 8px;
color: white;
font-size: ${(props) =>
props.size === "small"
? "12px"
: props.size === "big"
? "16px"
: "14px"}; // По умолчанию
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
flex-direction: ${(props) =>
props.$iconpos === 'right'
? 'row-reverse'
: 'row'};
gap: 10px;
&:hover{
background-color: ${(props) => props.$hoverсolor};
}
`;
type Props = {
color?: string;
hoverColor?: string
size?: "small" | "big";
title: string
onClick?: () => void;
type?: "button" | "submit" | "reset" | undefined
icon?: string
iconPosition?: 'left' | 'right'
iconSize?: {
width: string,
height: string
}
style?: any
};
export const MainButton = ({ color, size, title, hoverColor, type, icon, iconPosition, iconSize, onClick, style }: Props) => {
const theme = useTheme();
const defaultColor: string = theme.accent;
const defaultHoverColor: string = theme.accentHover
return (
<Button
className="unselectable"
color={color || defaultColor} size={size}
$hoverсolor={hoverColor || defaultHoverColor}
onClick={onClick}
type={type}
$iconpos={iconPosition || 'left'}
style={style}
>
{icon && <IconComponent name={icon} width={iconSize?.width} height={iconSize?.height} setColor="white"/> }
{title}
</Button>
);
};

View file

@ -1,45 +0,0 @@
import { MainButton } from "./mainButton";
import styled from "styled-components";
import { buttonNames } from "../../../const/buttons/names";
import { IconComponent as Icon } from '../../../const/buttons/icons';
import { useWorkSpaceStore } from "../../../store";
type Props = {
size: "small" | "big";
};
const Container = styled.div<{ size: "small" | "big" }>`
${(props) => props.size === "small" && "width: 100%;"}
`;
const Button = styled.div`
width: 100%;
cursor: pointer;
color: ${(props) => props.theme.textColor};
`;
const IconContainer = styled.div`
padding: 10px;
`;
export const QuitButton = ({ size }: Props) => {
const handleExit = () => {
window.electron.quitApp();
};
const { setWorkModule } = useWorkSpaceStore()
return (
<Container size={size} onClick={()=>setWorkModule('helloWindow')}>
{size === "big" ? (
<MainButton title="Выйти" size="big" />
) : (
<Button>
<IconContainer>
<Icon name={buttonNames[12]} width="30" height="30"/>
</IconContainer>
</Button>
)}
</Container>
);
};

View file

@ -1,26 +0,0 @@
import styled, { useTheme } from "styled-components";
import { IconComponent as Icon } from '../../../const/theme/icons';
import { ThemName } from "../../../const/theme/names";
const Button = styled.div`
width: 100%;
cursor: pointer;
`
const IconContainer = styled.div`
padding: 10px;
color: ${props => props.theme.text};
`
export default function ThemeButton() {
const theme = useTheme();
const name = localStorage.getItem('theme') as ThemName
return (
<Button onClick={theme.toggleTheme}>
<IconContainer>
<Icon name={name} width="30" height="30"/>
</IconContainer>
</Button>
);
}

View file

@ -1,65 +0,0 @@
import React from 'react';
import styled from 'styled-components';
type Props = {
placeholderText: string;
id: string;
value?: string;
onChange?: (identifier: string, value: string) => void;
onFileChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
type?: string;
multiple?: boolean;
style?: React.CSSProperties;
search?: boolean;
onFocus?: any
onBlur?: any
$size?: string
}
const InputContainer = styled.div`
width: 100%;
display: flex;
flex: 3;
`;
const StyledInput = styled.input`
padding: 8px 10px;
color: black;
border-radius: 8px;
border: 1px solid #A1A1A1;
background-color: transparent;
&:focus{
border: 1px solid #adadad;
}
width: 100%;
font-size: 14px;
color: ${props=>props.theme.textColor}
`;
const Input = React.forwardRef<HTMLInputElement, Props>(({ placeholderText, id, value, onChange, onFileChange, type = "text", multiple, style, onFocus, onBlur }, ref) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
if (onChange) {
onChange(id, value);
}
};
return (
<InputContainer>
<StyledInput
type={type}
placeholder={placeholderText}
value={value}
multiple={multiple}
onChange={type === "file" && onFileChange ? onFileChange : handleInputChange}
ref={ref}
style={style}
onFocus={onFocus}
onBlur={onBlur}
/>
</InputContainer>
);
});
export default Input;

View file

@ -1,101 +0,0 @@
type Props = {
loaderText: string; //Тип передаваемых данных loaderText - string
}
import styled, { keyframes } from 'styled-components'
import { AnimDot } from './animDot';
//Константа которая устанавливает кейфвреймы для вращения
const spinAnimation = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
//Стиль экрана загрузки
export const LoaderContainer = styled.div`
top: 0px;
z-index:998;
background-color: rgba(0,0,0, 0.7);
position: fixed;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow-y: hidden;
`
//Стиль блока с элементами загрузки
export const Loaderblock = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
p{
font-size: 24px;
color: white;
}
`
//Стиль для блока с кольцом загрузки
export const LoadingSpinner = styled.div`
position: relative;
width: 65px;
height: 65px;
`
//Стиль для кольца загрузки
export const LoadingRing = styled.div`
position: absolute;
top: 0;
left: 0;
width: 65px;
height: 65px;
border-radius: 50%;
border: 12px solid white;
`
//Стиль для части кольца загрузки, которая будет вращаться
export const LoadingPart = styled.div`
position: absolute;
top: 0;
left: 0;
width: 65px;
height: 65px;
border-radius: 50%;
border: 12px solid transparent;
border-top: 12px solid ${props => props.theme.accent};
// Добавление аниманции и использование кейфремов
animation: ${spinAnimation} 1s linear infinite;
`
const Text = styled.div`
display: flex;
gap: 3px;
font-size: 24px;
color: white;
margin-top: 30px;
margin-left: 25px;
`
export default function Loader({ loaderText }: Props) {
return (
<Loaderblock>
<LoadingSpinner>
<LoadingRing />
<LoadingPart />
</LoadingSpinner>
{/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
<Text>{loaderText}<AnimDot/></Text>
</Loaderblock>
)
}

View file

@ -1,103 +0,0 @@
type Props = {
loaderText: string; //Тип передаваемых данных loaderText - string
}
import styled, { keyframes } from 'styled-components'
import { AnimDot } from './animDot';
import { ModalWindow } from '../modalwindows/style';
//Константа которая устанавливает кейфвреймы для вращения
const spinAnimation = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
//Стиль экрана загрузки
export const LoaderContainer = styled.div`
top: 0px;
z-index:998;
background-color: rgba(0,0,0, 0.7);
position: fixed;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow-y: hidden;
`
//Стиль блока с элементами загрузки
export const Loaderblock = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
p{
font-size: 24px;
color: white;
}
`
//Стиль для блока с кольцом загрузки
export const LoadingSpinner = styled.div`
position: relative;
width: 65px;
height: 65px;
`
//Стиль для кольца загрузки
export const LoadingRing = styled.div`
position: absolute;
top: 0;
left: 0;
width: 65px;
height: 65px;
border-radius: 50%;
border: 12px solid white;
`
//Стиль для части кольца загрузки, которая будет вращаться
export const LoadingPart = styled.div`
position: absolute;
top: 0;
left: 0;
width: 65px;
height: 65px;
border-radius: 50%;
border: 12px solid transparent;
border-top: 12px solid ${props => props.theme.accent};
// Добавление аниманции и использование кейфремов
animation: ${spinAnimation} 1s linear infinite;
`
const Text = styled.div`
display: flex;
gap: 3px;
font-size: 24px;
color: white;
margin-top: 30px;
margin-left: 25px;
`
export default function RouterLoader({ loaderText }: Props) {
return (
<ModalWindow>
<Loaderblock>
<LoadingSpinner>
<LoadingRing />
<LoadingPart />
</LoadingSpinner>
{/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */}
<Text>{loaderText}<AnimDot/></Text>
</Loaderblock>
</ModalWindow>
)
}

View file

@ -1,61 +0,0 @@
import React, { useEffect, useRef } from 'react';
interface SignalSVGProps {
signal: number; // Принимаем signal как пропс
}
const SignalSVG: React.FC<SignalSVGProps> = ({ signal }) => {
const svgRef = useRef<SVGSVGElement | null>(null);
useEffect(() => {
// Устанавливаем цвета в зависимости от signal
const colors = {
'--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
if (svgRef.current) {
Object.entries(colors).forEach(([key, value]) => {
svgRef.current?.style.setProperty(key, value);
});
}
}, [signal]);
return (
<svg
ref={svgRef}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 296 235"
xmlSpace="preserve"
width="20px" // Фиксированная ширина
height="20px"
>
<style type="text/css">
{`
.st1 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st1-color, white); }
.st2 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st2-color, white); }
.st3 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st3-color, white); }
.st4 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st4-color, white); }
.st5 { fill-rule: evenodd; clip-rule: evenodd; fill: var(--st5-color, white); }
`}
</style>
<g>
<polygon className="st1" points="47,138 47,188 0,188 0,164" />
<polygon className="st2" points="109,104 109,188 62,188 62,130" />
<polygon className="st3" points="171,69 171,188 124,188 124,95" />
<polygon className="st4" points="234,35 234,188 187,188 187,61" />
<polygon className="st5" points="296,0 296,188 249,188 249,26" />
</g>
</svg>
);
};
export default SignalSVG;

View file

@ -1,38 +0,0 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Circle } from './style';
interface CustomNodeProps {
data: {
label: string;
style:{
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const CircleNode = ({ data }: CustomNodeProps) => {
return (
<Circle width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>{/* */}
{/* <div style={{transform: 'rotate(-45deg)', width: '100%', height: '100%'}}> */}
{data.label}
{/* Узлы для подключения */}
<Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} />
{/* Для входящих подключений */}
<Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} />
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
<Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} />
{/* </div> */}
</Circle>
);
};

View file

@ -1,20 +0,0 @@
import styled from "styled-components";
type Props={
width: string,
height: string,
bordertype: string,
bordercolor: string
}
export const Circle = styled.div<Props>`
background-color: white;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
`

View file

@ -1,37 +0,0 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Default } from './style';
interface CustomNodeProps {
data: {
label: string;
style:{
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const DefaultNode = ({ data }: CustomNodeProps) => {
return (
<Default width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>
{data.label}
{/* Узлы для подключения */}
{/* <Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} /> */}
{/* Для входящих подключений */}
{/* <Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} /> */}
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
{/* <Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} /> */}
{/* </div> */}
</Default>
);
};

View file

@ -1,20 +0,0 @@
import styled from "styled-components";
type Props={
width: string,
height: string,
bordertype: string,
bordercolor: string
}
export const Default = styled.div<Props>`
background-color: white;
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
visibility: visible;
`

View file

@ -1,45 +0,0 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Diamond, InnerDiamond, Border } from './style';
interface CustomNodeProps {
data: {
label: string;
style:{
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const DiamondNode = ({ data }: CustomNodeProps) => {
const baseSize = Math.max(data.style.width, data.style.height);
return (
<Diamond width={data.style.width} height={data.style.height}>{/* */}
<Border width={data.style.width} height={data.style.height} bordercolor={data.style.borderColor}>
<InnerDiamond width={data.style.width} height={data.style.height}>
{data.label}
</InnerDiamond>
</Border>
<div style={{ width: '100%', height: '100%' }}>
{/* Узлы для подключения */}
<Handle type="source" position={Position.Top} id="source-top" />
<Handle type="source" position={Position.Right} id="source-right" />
<Handle type="source" position={Position.Bottom} id="source-bottom" />
<Handle type="source" position={Position.Left} id="source-left" />
{/* Для входящих подключений */}
<Handle type="target" position={Position.Top} id="target-top" />
<Handle type="target" position={Position.Right} id="target-right" />
<Handle type="target" position={Position.Bottom} id="target-bottom" />
<Handle type="target" position={Position.Left} id="target-left" />
</div>
</Diamond >
);
};

View file

@ -1,32 +0,0 @@
import styled from "styled-components";
interface StyledDefaultProps {
width: any;
height: any;
}
export const Diamond = styled.div<StyledDefaultProps>`
position: relative;
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
`;
export const InnerDiamond = styled.div<StyledDefaultProps>`
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
width: ${(props) => props.width - 6}px;
height: ${(props) => props.height - 4}px;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
`
export const Border = styled.div<{ width: number; height: number, bordercolor: any }>`
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
background-color: ${(props) => props.bordercolor};
display: flex;
align-items: center;
justify-content: center;
`;

View file

@ -1,5 +0,0 @@
export {DefaultNode} from './default/defaultNode'
export {PillNode} from './pill/pillNode'
export {ParalelorgamNode} from './paralelogram/paralelogramNode'
export {CircleNode} from './circle/circleNode'
export {DiamondNode} from './diamond/diamondNode'

View file

@ -1,35 +0,0 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Paralelorgam } from './style';
interface CustomNodeProps {
data: {
label: string;
style:{
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const ParalelorgamNode = ({ data }: CustomNodeProps) => {
return (
<Paralelorgam width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>
{data.label}
{/* Узлы для подключения */}
<Handle type="source" position={Position.Top} id="source-top"/>
<Handle type="source" position={Position.Right} id="source-right"/>
<Handle type="source" position={Position.Bottom} id="source-bottom"/>
<Handle type="source" position={Position.Left} id="source-left"/>
{/* Для входящих подключений */}
<Handle type="target" position={Position.Top} id="target-top"/>
<Handle type="target" position={Position.Right} id="target-right"/>
<Handle type="target" position={Position.Bottom} id="target-bottom"/>
<Handle type="target" position={Position.Left} id="target-left"/>
</Paralelorgam>
);
};

View file

@ -1,43 +0,0 @@
import styled from "styled-components";
type Props={
width: string,
height: string,
bordertype: string,
bordercolor: string
}
export const Paralelorgam = styled.div<Props>`
transform: skewX(-20deg);
background-color: white;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
.react-flow__handle-top{
transform: skewX(20deg);
top: -5px;
}
.react-flow__handle-bottom{
transform: skewX(20deg);
bottom: -5px;
}
.react-flow__handle-right{
transform: skewX(20deg);
right: -5px;
top: 25px;
}
.react-flow__handle-left{
transform: skewX(20deg);
left: -5px;
top: 25px;
}
`

View file

@ -1,40 +0,0 @@
import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Pill } from './style';
interface CustomNodeProps {
data: {
label: string;
style:{
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const PillNode = ({ data }: CustomNodeProps) => {
return (
<Pill width={data.style.width} height={data.style.height} bordertype={data.style.borderType} bordercolor={data.style.borderColor}>{/* */}
{/* <div style={{transform: 'rotate(-45deg)', width: '100%', height: '100%'}}> */}
{data.label}
{/* Узлы для подключения */}
<Handle type="source" position={Position.Top} id="source-top" style={{ top: 0, }} />
<Handle type="source" position={Position.Right} id="source-right" style={{ right: 0 }} />
<Handle type="source" position={Position.Bottom} id="source-bottom" style={{ bottom: 0 }} />
<Handle type="source" position={Position.Left} id="source-left" style={{ left: 0 }} />
{/* Для входящих подключений */}
<Handle type="target" position={Position.Top} id="target-top" style={{ top: 0 }} />
<Handle type="target" position={Position.Right} id="target-right" style={{ right: 0 }} />
<Handle type="target" position={Position.Bottom} id="target-bottom" style={{ bottom: 0 }} />
<Handle type="target" position={Position.Left} id="target-left" style={{ left: 0 }} />
{/* </div> */}
</Pill>
);
};
export default PillNode;

View file

@ -1,19 +0,0 @@
import styled from "styled-components";
type Props={
width: string,
height: string,
bordertype: string,
bordercolor: string
}
export const Pill = styled.div<Props>`
background-color: white;
border-radius: 50px;
display: flex;
align-items: center;
justify-content: center;
border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
`

View file

@ -1,5 +0,0 @@
export {NodeGroupNode} from './networkGroup/nodeGroup'
export {NetWorkNode} from './networkNode/networkNode'
export {IpGroupeNode} from './ipGroup/ipGroup'
export {IpNode} from './ipNode/ipNode'
export {PortNode} from './portNode/portNode'

View file

@ -1,18 +0,0 @@
import { IpGroup } from "./style";
interface CustomNodeProps {
data: {
style: {
width: any,
height: any,
}
};
}
export const IpGroupeNode = ({ data }: CustomNodeProps) => {
return (
<IpGroup $width={data.style.width} $height={Number(data.style.height)} />
);
};

View file

@ -1,12 +0,0 @@
import styled from "styled-components"
type Props={
$width: string,
$height?: any
}
export const IpGroup = styled.div<Props>`
width: ${(props) => props.$width}px;
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
`

View file

@ -1,245 +0,0 @@
import { useState } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Ip } from './style';
import { Row, IpTitle, IpContent } from './style';
import Input from '../../../../UI/inputs/input';
import { useModalContext, usePannableStore } from '../../../../../store';
import { ModalMap } from '../../../../modalwindows/windowsType';
import { validateIPAddress, validateSameSubnet } from '../../../../../utils/nodeController/validator';
import {changeIp} from '../../../../../const/types/types'
import { IconComponent } from "../../../../../const/buttons/icons";
import { buttonNames } from "../../../../../const/buttons/names";
interface IIp {
ipType: string
ipAdress: string,
ipGate: string,
ipDns: string[]
ipVersion: string
}
type StateType = Record<string, string>;
const typeMap: StateType = {
'auto': "DHCP",
'manual': "Static"
}
interface CustomNodeProps {
data: {
ipData: IIp,
interfaceData: {
device: string,
connection: string
},
style: {
width: any,
height: any,
borderType: string,
borderColor: string,
}
route: boolean
}
}
export const IpNode = ({ data }: CustomNodeProps) => {
const { toggleEditingMode } = usePannableStore();
const [isInput, setIsInput] = useState(false)
const { openModal } = useModalContext();
const [ipData, setIpData] = useState<changeIp>(
{
currentIp: data.ipData.ipAdress,
newIp: data.ipData.ipAdress,
currentGate: data.ipData.ipGate,
newGate: data.ipData.ipGate,
currentDns: data.ipData.ipDns,
newDns: data.ipData.ipDns,
device: data.interfaceData.device,
connection: data.interfaceData.connection,
version: data.ipData.ipVersion,
prefix: 0
}
)
const handleFocus = () => {
toggleEditingMode();
};
const handleBlur = () => {
toggleEditingMode();
};
const handleClick = () => {
if (isInput) {
setIsInput(false)
setIpData(prevData => ({
...prevData,
newIp: data.ipData.ipAdress,
newGate: data.ipData.ipGate,
newDns: data.ipData.ipDns
}));
}
else {
setIsInput(true)
}
}
const handleChange = (id: string, value: string) => {
if (id === 'newDns') {
const dnsArray = value.split(',').map(dns => dns.trim());
setIpData(prevData => ({
...prevData,
[id]: dnsArray
}));
} else {
setIpData(prevData => ({
...prevData,
[id]: value
}));
}
};
function getSubnetMask(ipAddress: string, ipVersion: string): number {
if (ipVersion === "6") {
return 64;
}
const firstOctet = parseInt(ipAddress.split(".")[0], 10);
if (firstOctet >= 0 && firstOctet <= 127) {
return 8;
} else if (firstOctet >= 128 && firstOctet <= 191) {
return 16;
} else if (firstOctet >= 192 && firstOctet <= 223) {
return 24;
} else {
return 24;
}
}
const handleConfirm = () => {
if (
!validateIPAddress(ipData.newIp) ||
!validateIPAddress(ipData.newGate) ||
!ipData.newDns.every(dns => validateIPAddress(dns))
) {
alert('Один или несколько IP-адресов некорректны.');
return;
}
if (!validateSameSubnet(ipData.newIp, ipData.newGate, ipData.newDns)) {
alert('Адрес, шлюз и хотя бы один DNS должны находиться в одной подсети.');
return;
}
const calculatedPrefix = getSubnetMask(ipData.newIp, data.ipData.ipVersion);
setIpData(prevData => ({
...prevData,
prefix: calculatedPrefix,
}));
openModal(ModalMap['1'], {
...ipData,
prefix: calculatedPrefix,
});
setIsInput(false)
};
return (
<>
<Ip $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
<IpTitle>
{/* <Row>
<p>Версия IP:</p>
<p>v{data.ipData.ipVersion}</p>
</Row> */}
<Row>
<p>Метод:</p>
<p>{typeMap[data.ipData.ipType]}</p>
</Row>
{isInput ? (
<Row>
<IconComponent name={buttonNames[8]} onClick={handleConfirm} width='18' height='18' setColor='white'/>
<IconComponent name={buttonNames[9]} onClick={handleClick} width='18' height='18' setColor='white'/>
</Row>
) : (
<IconComponent name={buttonNames[7]} onClick={handleClick} width='18' height='18' setColor='white'/>
)}
</IpTitle>
<IpContent>
<Row className='row'>
<p className='row-label'>IPv{data.ipData.ipVersion}-Addr:</p>
{isInput ? (
<Input
type="text"
placeholderText={data.ipData.ipAdress}
id="newIp"
value={ipData.newIp}
onChange={(id, value) => handleChange(id, value)}
onBlur={handleBlur}
onFocus={handleFocus}
style={{fontSize: '12px', padding: '4px 10px'}}
/>
) : (
<p className='row-text'>{data.ipData.ipAdress}</p>
)}
</Row>
<Row className='row'>
<p className='row-label'>IPv{data.ipData.ipVersion}-GW:</p>
{isInput ? (
<Input
type="text"
placeholderText={data.ipData.ipGate}
id="newGate"
value={ipData.newGate}
onChange={(id, value) => handleChange(id, value)}
onBlur={handleBlur}
onFocus={handleFocus}
style={{fontSize: '12px', padding: '4px 10px'}}
/>
) : (
<p className='row-text'>{data.ipData.ipGate}</p>
)}
</Row>
<Row className='row'>
<p className='row-label'>IPv{data.ipData.ipVersion}-DNS:</p>
{isInput ? (
<Input
type="text"
placeholderText={data.ipData.ipDns.join(', ')}
id="newDns"
value={ipData.newDns.join(', ')}
onChange={(id, value) => handleChange(id, value)}
onBlur={handleBlur}
onFocus={handleFocus}
style={{fontSize: '12px', padding: '4px 10px'}}
/>
) : (
<p className='row-text'>{data.ipData.ipDns.join(', ')}</p>
)}
</Row>
{data.route &&
<>
<Handle type="source" position={Position.Right} id={`source-right`} style={{ right: 0, top: 80 }} />
<Handle type="target" position={Position.Right} id={`target-right`} style={{ right: 0, top: 80 }} />
</>
}
</IpContent>
</Ip>
</>
);
};

View file

@ -1,74 +0,0 @@
import styled from "styled-components";
type Props={
$width: string,
$height?: any
$bordertype: string,
$bordercolor: string
}
export const Ip = styled.div<Props>`
background-color: ${(props)=>props.theme.secondary};
display: flex;
flex-direction: column;
width: ${(props) => props.$width}px;
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
// box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px;
p{
margin: 0;
}
img{
width: 15px;
height: 15px;
}
`
export const IpTitle = styled.div`
background: ${(props)=>props.theme.accent};
border-top-left-radius: 6px;
border-top-right-radius: 6px;
color: white;
display: flex;
justify-content: space-between;
padding: 10px;
`
export const IpContent = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
padding: 10px;
.row{
height: 24px;
.row-label{
flex: 1;
}
.row-text{
flex: 3;
}
p{
text-wrap: nowrap;
text-align: left;
}
}
`
export const Row = styled.div`
display: flex;
align-items: center;
gap: 5px;
p{
margin: 0;
}
`

View file

@ -1,64 +0,0 @@
import { useState } from "react";
import { NodeGroup, Row, CollapseButton } from "./style";
import { useGroupStore } from "../../../../../store";
import { MainButton } from "../../../../UI/buttons/mainButton";
import { IconComponent } from "../../../../../const/buttons/icons";
import { buttonNames } from "../../../../../const/buttons/names";
interface CustomNodeProps {
data: {
id: string;
label: string;
height: any
style: {
width: any;
height: any;
borderType: any;
borderColor: any;
};
};
}
export const NodeGroupNode = ({ data }: CustomNodeProps) => {
const [click, setClick] = useState(false);
const { groups, setGroups } = useGroupStore();
const handleClick = () => {
const newClickState = !click;
setClick(newClickState);
const updatedNodes = groups.map((groups) =>
groups.id === data.id ? { ...groups, click: newClickState } : groups
);
if (!groups.some((groups) => groups.id === data.id)) {
updatedNodes.push({ id: data.id, click: newClickState });
}
setGroups(updatedNodes);
};
return (
<NodeGroup
$width={data.style.width}
$height={Number(data.style.height)}
$bordertype={data.style.borderType}
$bordercolor={data.style.borderColor}
>
<Row $bordertype={data.style.borderType} $bordercolor={data.style.borderColor} $width={data.style.width}>
<p>{data.label}</p>
{/* <MainButton title={click ? 'Показать' : 'Скрыть'} size="small" onClick={handleClick}/> */}
<CollapseButton onClick={handleClick}>
{click ?(
'Развернуть'
):(
'Свернуть'
)}
{/* <IconComponent name={click ? buttonNames[5] : buttonNames[6]}/> */}
</CollapseButton>
</Row>
</NodeGroup>
);
};

View file

@ -1,47 +0,0 @@
import styled from "styled-components"
type Props={
$width?: string,
$height?: any
$bordertype?: string,
$bordercolor?: string
$backgroundcolor?: string
}
export const NodeGroup = styled.div<Props>`
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
width: ${(props) => props.$width}px;
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
visibility: visible;
`
export const Row = styled.div<Props>`
position: absolute;
top: -25px;
left: 10px;
display: flex;
justify-content: space-between;
align-items: center;
width: ${props=>props.$width}px;
gap: 5px;
padding-right: 10px;
p{
margin: 0;
}
`
export const CollapseButton = styled.p`
padding: unset;
background: none;
color: ${(props)=>props.theme.textColor};
margin-right: 15px !important;
&:hover{
background: none;
}
`

View file

@ -1,110 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Network, TitleContainer, Device, Status, Content, Row, MAC, LabelColumn } from './style';
import SignalSVG from '../../../../UI/signalSvg';
interface CustomNodeProps {
data: {
networkData: {
device: string
type: string
mac: string
mtu: number
state: string
connection: string
id: string,
strength: string
}
style: {
width: any,
height: any,
borderType: string,
borderColor: string,
backgroundColor: string
}
height: string
visible: true
};
}
export const NetWorkNode = ({ data }: CustomNodeProps) => {
const [signal, setSignal] = useState(1);
useEffect(() => {
const perc = Number(data.networkData.strength);
switch (true) {
case perc < 20:
setSignal(1);
break;
case perc < 40:
setSignal(2);
break;
case perc < 60:
setSignal(3);
break;
case perc < 80:
setSignal(4);
break;
default:
setSignal(5);
break;
}
}, [data.networkData.strength])
return (
<Network $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
<TitleContainer $backgroundcolor={data.style.backgroundColor} {...(!data.visible ? { $height: "100%" } : {})}>
{data.networkData.type == "WIFI" &&
<img src="./assets/images/wifi.svg" alt="" />
}
{data.networkData.type == "ETHERNET" &&
<img src="./assets/images/ethernet.svg" alt="" />
}
{data.networkData.type == "TUN" &&
<img src="./assets/images/vpn.svg" alt="" />
}
<Device>{data.networkData.device}</Device>
{data.networkData.type == 'WIFI' && data.networkData.state === "Подключено" ? (
<Status>
<SignalSVG signal={signal} />
</Status>
) : (
<div></div>
)
}
</TitleContainer>
{data.visible &&
<Content>
<LabelColumn>
<p className='networkNode-label'>Конфигурация</p>
<MAC>
<Row className='networkNode-row'>
<p>MAC:</p>
<p>{data.networkData.mac}</p>
</Row>
<Row className='networkNode-row'>
<p>MTU:</p>
<p>{data.networkData.mtu}</p>
</Row>
</MAC>
</LabelColumn>
</Content>
}
{data.networkData.connection !== 'unknown' &&
<Handle type="source" position={Position.Left} id={`g0_${data.networkData.id}-source-left`} style={{ left: 0 }} />
}
</Network>
);
};
export default NetWorkNode

View file

@ -1,95 +0,0 @@
import styled from "styled-components";
type Props = {
$width?: string,
$height?: any
$bordertype?: string,
$bordercolor?: string
$backgroundcolor?: string
}
export const Network = styled.div<Props>`
background-color: ${(props) => props.theme.primary};
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
min-width: ${(props) => props.$width}px;
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 10px;
visibility: visible;
p{
margin: 0;
}
`
export const TitleContainer = styled.div<Props>`
display: flex;
justify-content: space-between;
align-items: center;
background: ${(props) => props.$backgroundcolor};
border-top-left-radius: 8px;
border-top-right-radius: 8px;
color: white;
padding: 15px;
height: ${(props) => props.$height ? props.$height : 'unset'};
img{
width: 20px
}
`
export const Device = styled.p`
font-size: 18px;
`
export const Status = styled.p`
font-size: 14px;
img{
width: 20px;
height: 20px;
}
`
export const Content = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
padding: 15px;
`
export const Row = styled.div`
display: flex;
gap: 5px;
`
export const MAC = styled.div`
display: flex;
justify-content: space-between;
background: ${(props) => props.theme.secondary};
border-radius: 8px;
// box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1);
width: 100%;
.networkNode-row{
padding: 10px;
}
`
export const LabelColumn = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
width: 100%;
.networkNode-label{
font-size: 10px;
width: 100%;
text-align: left;
margin-left: 15px;
}
`

View file

@ -1,48 +0,0 @@
import { Handle, Position } from '@xyflow/react';
import { Port, Column } from './style';
interface CustomNodeProps {
data: {
label: string;
port: string
id: string;
style: {
width: any;
height: any;
borderType: any;
borderColor: any;
}
};
}
export const PortNode = ({ data }: CustomNodeProps) => {
return (
<Port $width={data.style.width} $height={Number(data.style.height)} $bordertype={data.style.borderType} $bordercolor={data.style.borderColor}>
<Column>
<p>{data.label}</p>
{data.port &&
<p>{data.port}</p>
}
</Column>
{data.port ? (
<>
<Handle type="target" position={Position.Left} id={`${data.id}_target-left`} style={{ left: 0 }} />
<Handle type="source" position={Position.Left} id={`${data.id}_target-left`} style={{ left: 0 }} />
</>
) : (
<>
<Handle type="target" position={Position.Right} id={`${data.id}_target-right`} style={{ right: 0 }} />
<Handle type="source" position={Position.Right} id={`${data.id}source-right`} style={{ right: 0 }} />
</>
)}
</Port>
);
};

View file

@ -1,30 +0,0 @@
import styled from "styled-components"
type Props={
$width: string,
$height?: any
$bordertype: string,
$bordercolor: string
}
export const Port = styled.div<Props>`
background-color: ${(props)=>props.theme.primary};
border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor};
width: ${(props) => props.$width}px;
height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height};
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
visibility: visible;
`
export const Column = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
p{
margin: 0;
}
`

View file

@ -1,11 +0,0 @@
type ColorType = Record<string, string>;
export const NodeColors: ColorType = {
'0': '#c0c0c0',
'1': '#ef6e7d',
'2': '#f19e4c',
'3': '#e9c44c',
'4': '#51ce82',
'5': '#4cd2d0',
'6': '#a085f3',
};

View file

@ -1,16 +0,0 @@
import { DefaultNode, PillNode, ParalelorgamNode, DiamondNode, CircleNode } from '../nodes/baseForm'
import { NodeGroupNode, NetWorkNode, IpGroupeNode, IpNode, PortNode } from '../nodes/networkInterface';
export const nodeTypes = {
custom: DefaultNode,
pill: PillNode,
parallelogram: ParalelorgamNode,
diamond: DiamondNode,
circle: CircleNode,
nodegroup: NodeGroupNode,
portNode: PortNode,
network: NetWorkNode,
ipgroup: IpGroupeNode,
ip: IpNode,
};

View file

@ -1,10 +0,0 @@
import { ModalWindow, ModalContainer } from "./style"
import { modalNames } from "../../const/modals/names"
export const WindowComponent = ()=>{
return(
<ModalWindow>
</ModalWindow>
)
}

View file

@ -1,58 +0,0 @@
import Input from "../../UI/inputs/input"
import { MainButton } from "../../UI/buttons/mainButton";
import { useState } from "react";
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
import { validateIPAddress } from "../../../utils/nodeController/validator";
interface Props {
onSave?: (data: string) => void;
onClose: () => void;
}
export const AddDevice = ({ onSave, onClose }: Props) => {
const [ipAddress, setIpAddress] = useState('')
const [error, setError] = useState(false)
const handleSubmit = () => {
if (!validateIPAddress(ipAddress)) {
setError(true)
return;
}
// e.preventDefault(); // Предотвращаем перезагрузку страницы
if (onSave && ipAddress) {
setError(false)
onSave(ipAddress);
onClose();
}
};
return (
<ModalContainer style={{ height: '230px' }}>
{/* <form onSubmit={handleSubmit}> */}
<ModalTitle>
<h3>Добавление узла</h3>
</ModalTitle>
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
<Input
type="text"
placeholderText='Введите адрес узла...'
id="newIp"
value={ipAddress}
onChange={(id, value) => setIpAddress(value)}
/>
{error && (
<p style={{ color: 'red', marginTop: '5px' }}>
Некорректный адрес узла
</p>
)}
</ModalContent>
<ButtonContainer>
<MainButton type="button" size="small" title="Отмена" onClick={onClose} />
<MainButton type="submit" size="small" title="Сохранить" onClick={handleSubmit} />
</ButtonContainer>
{/* </form> */}
</ModalContainer>
)
}

View file

@ -1,38 +0,0 @@
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
import { useModalContext } from "../../../store"
import { useIpStore } from "../../../store"
import { MainButton } from "../../UI/buttons/mainButton"
type Props={
message: string
}
export const ConnectionError = ({message}: Props)=>{
const {closeModal} = useModalContext()
const { setIpAddress } = useIpStore();
const close = ()=>{
setIpAddress('localhost')
closeModal()
}
return(
<ModalContainer>
<ModalTitle>
<h3>Соединение прервано</h3>
</ModalTitle>
<ModalContent>
<ModalText>
<h3>Произошёл разрыв с сервером</h3>
<p>Пожалуйста, проверьте доступ к адресу и попробуйте снова.</p>
</ModalText>
</ModalContent>
<ButtonContainer>
<MainButton title="Закрыть" onClick={close} size="small"/>
<MainButton title="Повторить" onClick={closeModal} size="small"/>
</ButtonContainer>
</ModalContainer>
)
}

View file

@ -1,4 +0,0 @@
export {IpConfirm} from './ipConfirm'
export {ConnectionError} from './connectionError'
export {AddDevice} from './addDevice'
export {RightsConfirm} from './rightsConfirm'

View file

@ -1,209 +0,0 @@
import styled from "styled-components";
import Loader from "../../UI/loader";
import { useEffect, useState } from "react";
import { changeIp } from '../../../const/types/types'
import { NetworkService } from '../../../api/request'
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer, ErrorMessage } from "../style"
import { useIpStore } from "../../../store";
import { MainButton } from "../../UI/buttons/mainButton";
import { useAdminStore } from "../../../store/adminStore";
import { useDevicesStore } from "../../../store";
type Props = {
ipData: changeIp
onClose: () => void;
smoothClose: () => void;
}
const Information = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
`
const Info = styled.div`
display: flex;
gap: 10px;
`
const DataContainer = styled.div`
display: flex;
gap: 35px;
`
const CurrentData = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`
const NewData = styled.div`
display: flex;
flex-direction: column;
gap: 10px;
`
export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => {
const [loader, setLoader] = useState(true)
const [newIP, setNewIp] = useState(null)
const [currentIp, setCurrentIP] = useState({})
const [checkStatus, setcheckStatus] = useState(false)
const [message, setMessgae] = useState('')
const { ipAddress, setIpAddress } = useIpStore();
const {isAdmin} = useAdminStore()
const { devices, addDevice, deleteDevice } = useDevicesStore();
useEffect(() => {
if (ipData && isAdmin) {
const data = {
"connection_id": ipData.connection,
"ipv4_updates": {
"ipv4.method": "manual",
"ipv4.address-data": [
{
"address": ipData.newIp,
"prefix": ipData.prefix
}
],
"ipv4.gateway": ipData.newGate,
"ipv4.dns-data": ipData.newDns
}
};
const oldData = {
"connection_id": ipData.connection,
"ipv4_updates": {
"ipv4.method": "manual",
"ipv4.address-data": [
{
"address": ipData.currentIp,
"prefix": ipData.prefix
}
],
"ipv4.gateway": ipData.currentGate,
"ipv4.dns-data": ipData.currentDns
}
}
setCurrentIP(oldData)
checkIp(data);
}
}, [ipData, isAdmin]);
const checkIp = async (data: any) => {
try {
const result = await NetworkService.checkIp(ipAddress, data);
setcheckStatus(result.data.available)
setMessgae(result.data.message)
if (result.data.available) {
setNewIp(data)
}
} catch (err) {
console.error('Error:', err);
} finally {
setLoader(false);
}
};
const saveIp = async (data: any) => {
try {
const result = await NetworkService.saveIp(ipAddress, data);
if (result.status === 200) {
setIpAddress(ipData.newIp)
const updatedDevices = devices.map(device =>
device.address === ipData.currentIp
? { ...device, address: ipData.newIp }
: device
);
useDevicesStore.setState({ devices: updatedDevices });
onClose()
}
} catch (err) {
console.error('Error:', err);
} finally {
setLoader(false);
}
};
const cancelIp = async (data: any) => {
try {
const result = await NetworkService.saveIp(ipAddress, data);
if (result.status === 200) {
onClose()
}
} catch (err) {
console.error('Error:', err);
} finally {
setLoader(false);
}
};
return (
<>
{loader && isAdmin ? (
<Loader loaderText="Идет проверка корректности IP адреса" />
) : (
<ModalContainer {...(checkStatus ? { height: "unset" } : {})}>
<ModalTitle>
<h3>Изменение IP-Адреса</h3>
</ModalTitle>
{checkStatus ? (
<ModalContent>
<Information>
<h3>Общие сведения</h3>
<Info>
<p>Интерфейс: {ipData.device}</p>
<p>--{'>'}</p>
<p>{ipData.connection}</p>
</Info>
<h3>Сведения об IP</h3>
<DataContainer>
<CurrentData>
<p>Текущий ip: {ipData.currentIp}</p>
<p>Текущий шлюз: {ipData.currentGate}</p>
<p>Текущий DNS: {ipData.currentDns}</p>
</CurrentData>
<NewData>
<p>Новый ip: {ipData.newIp}</p>
<p>Новый шлюз: {ipData.newGate}</p>
<p>Новый DNS: {ipData.newDns}</p>
</NewData>
</DataContainer>
</Information>
<ButtonContainer>
<MainButton title="Отмена" size='small' onClick={() => cancelIp(currentIp)}/>
<MainButton title="Сохранить" size='small' onClick={() => saveIp(newIP)}/>
</ButtonContainer>
</ModalContent>
) : (
<>
<ModalContent>
<ModalText>
{isAdmin ? (
<>
<h3>Произошла ошибка проверки корректности</h3>
<ErrorMessage>{message}</ErrorMessage>
</>
):(
<h3>Для изменения сетевых настроек необходимо авторизироваться под администратором</h3>
)}
</ModalText>
</ModalContent>
<ButtonContainer>
<MainButton title="Закрыть" size='small' onClick={smoothClose}/>
</ButtonContainer>
</>
)}
</ModalContainer>
)}
</>
)
}

View file

@ -1,135 +0,0 @@
import { useEffect, useState } from "react"
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
import Input from "../../UI/inputs/input"
import { MainButton } from "../../UI/buttons/mainButton"
import { NetworkService } from "../../../api/request"
import { useIpStore } from "../../../store"
import { useAdminStore } from "../../../store/adminStore"
import { useCheckAdmin } from "../../../utils/checkAdmin"
import { buttonNames } from "../../../const/buttons/names"
import { IconComponent } from "../../../const/buttons/icons"
interface Props {
onClose: () => void;
}
export const RightsConfirm = ({ onClose }: Props) => {
const { ipAddress } = useIpStore();
const { isAdmin, setAdmin } = useAdminStore()
const checkAdmin = useCheckAdmin();
const [error, setError] = useState(false)
const [success, setSuccess] = useState({
message: '',
status: false
})
const [user, setUser] = useState({
username: 'operator',
password: ""
})
const handleChange = (id: string, value: string) => {
setUser(prevData => ({
...prevData,
[id]: value
}));
}
const login = async (user: object) => {
try {
const response = await NetworkService.login(ipAddress, user);
if(response.status === 200){
await checkAdmin();
setError(false)
setSuccess(prevData => ({
...prevData,
message: 'Успешный вход',
status: true
}));
await new Promise(resolve => setTimeout(resolve, 3000));
onClose();
}else{
setError(true)
}
} catch (err) {
console.error('Error:', err);
}
}
const logout = async () => {
await NetworkService.logout(ipAddress);
await checkAdmin();
setSuccess(prevData => ({
...prevData,
message: 'Успешный выход',
status: true
}));
await new Promise(resolve => setTimeout(resolve, 3000));
onClose();
}
return (
<ModalContainer style={!isAdmin ? {height: '250px'} : {height: 'unset'}}>
{!success.status ? (
!isAdmin ? (
<>
<ModalTitle>
<h3>Получение прав доступа</h3>
</ModalTitle>
<ModalContent style={{display: 'flex', flexDirection: 'column', gap: '10px'}}>
<h3 style={{marginLeft: '5px', fontSize: '14px'}}>Включить административный доступ</h3>
<Input
type="password"
placeholderText='Введите пароль администратора...'
id="password"
value={user.password}
onChange={(id, value) => handleChange(id, value)}
/>
{error && (
<p style={{ color: 'red', marginLeft: '10px', fontSize: '14px', textAlign: 'right' }}>
Неверный пароль оператора
</p>
)}
</ModalContent>
<ButtonContainer>
<MainButton size="small" title="Отмена" onClick={onClose} />
<MainButton size="small" title="Отправить" onClick={() => login(user)} />
</ButtonContainer>
</>
) : (
<>
<ModalTitle>
<h3>Переключиться на ограниченный доступ</h3>
</ModalTitle>
<ModalContent>
<p>В данном режиме доступа ограничиваются полномочия администратора.</p>
</ModalContent>
<ButtonContainer>
<MainButton size="small" title="Отмена" onClick={onClose} />
<MainButton size="small" title="Ограничить доступ" onClick={logout} />
</ButtonContainer>
</>
)
):(
<ModalContainer style={{display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', marginTop: '15px'}}>
<IconComponent name={buttonNames[8]} width="50" height="50"/>
<h3>{success.message}</h3>
<p style={{fontSize: '12px'}}>Это окно можно закрыть, либо оно пропадёт само</p>
<MainButton size="small" title="Закрыть" onClick={onClose} style={{width: '30%'}}/>
</ModalContainer>
)}
</ModalContainer>
)
}

View file

@ -1,50 +0,0 @@
import { useModalContext } from "../../store";
import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalContainers";
import { ModalMap } from "./windowsType";
import Loader from "../UI/loader";
import { ModalWindow } from "./style";
type Props = {
isOpen: boolean,
type: string
data: any;
}
export default function WindowComponent({ isOpen, type, data }: Props) {
const { closeModal, closeLoader } = useModalContext()
return (
<>
{isOpen &&
<ModalWindow>
{type == ModalMap['1'] && data &&
<IpConfirm
ipData={data}
onClose={closeModal}
smoothClose={closeLoader}
/>
}
{type == ModalMap['2'] &&
<Loader loaderText={data} />
}
{type === ModalMap['3'] &&
<ConnectionError
message={data.message}
/>
}
{type === ModalMap['4'] && (
<AddDevice onSave={data?.onSave} onClose={closeLoader} />
)}
{type === ModalMap['5'] && (
<RightsConfirm onClose={closeLoader} />
)}
</ModalWindow >
}
</>
)
}

View file

@ -1,70 +0,0 @@
import styled from "styled-components";
export const ModalWindow = styled.div`
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 9999;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
height: 100%;
`
export const ModalContainer = styled.div<{height?: string}>`
border-radius: 12px;
background: ${(props)=>props.theme.secondary};
color: ${(props=>props.theme.textColor)};
position: relative;
max-height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 15px;
height: ${(props) => props.height ? props.height : '200px'};
width: 520px;
`
export const ModalTitle = styled.div`
padding: 20px;
border-bottom: 1px solid #9D9D9D;
display: flex;
justify-content: space-between;
h3{
font-size: 18px;
}
`
export const ModalContent = styled.div`
padding: 0 20px;
h3{
font-size: 14px;
}
p{
font-size: 12px;
}
`
export const ModalText = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
`
export const ErrorMessage = styled.p`
color: #d95454;
`
export const ButtonContainer = styled.div`
justify-content: flex-end;
display: flex;
gap: 10px;
padding: 20px;
`

View file

@ -1,9 +0,0 @@
export type ModalType = Record<string, string>;
export const ModalMap: ModalType = {
'1': 'ipConfirm',
'2': 'loader',
'3': 'connectionError',
'4': 'addDevice',
'5': 'sudo'
}

View file

@ -1,142 +0,0 @@
import { ModalContainer, ModalTitle, ModalContent, ModalText } from "./modalwindows/style"
import Input from "./UI/inputs/input"
import { MainButton } from "./UI/buttons/mainButton"
import styled from "styled-components"
import { useEffect, useState } from "react"
import { useIpStore } from "../store"
import { NetworkService } from "../api/request"
import { IconComponent } from "../const/buttons/icons"
import { buttonNames } from "../const/buttons/names"
const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: ${(props)=> props.theme.background};
`
const Container = styled.form`
display: flex;
flex-direction: column;
gap: 25px;
align-items: center;
background: ${(props) => props.theme.primary};
padding: 25px;
border-radius: 18px;
width: 350px;
`
const Title = styled.div`
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
text-align: center;
font-size: 25px;
`
const Content = styled.div`
display: flex;
flex-direction: column;
gap: 25px;
text-align: center;
`
const ButtonContainer = styled.div`
width: 100%;
display: flex;
justify-content: center;
`
export const CreateUser = () => {
const { ipAddress } = useIpStore();
const [repeatPassword, setRepeatPassword] = useState('');
const [passwordsMatch, setPasswordsMatch] = useState(true);
const [user, setUser] = useState({
username: 'operator',
password: '',
is_admin: true
})
const handleChange = (id: string, value: string) => {
if (id === 'repeatPassword') {
setRepeatPassword(value);
setPasswordsMatch(user.password === value);
} else {
setUser(prevData => ({
...prevData,
[id]: value
}));
if (id === 'password') {
setPasswordsMatch(value === repeatPassword);
}
}
}
const handleSave = async (user: object) => {
try {
const result = await NetworkService.createUser(ipAddress, user);
if (result.status === 200) { window.location.reload() }
} catch (err) {
console.error('Error:', err);
} finally {
}
}
const handleSubmit = () => {
if (passwordsMatch) {
handleSave(user);
}
};
return (
<Wrapper>
<Container onSubmit={handleSubmit}>
<Title>
<IconComponent name={buttonNames[3]} width="52" height="52"></IconComponent>
<h3>Первоначальная настройка приложения</h3>
</Title>
<Content>
<div>
<p>Перед началом работы необходимо создать пароль оператора приложения</p>
</div>
<div>
<Input
type="password"
placeholderText='Введите пароль...'
id="password"
value={user.password}
onChange={(id, value) => handleChange(id, value)}
/>
<Input
type="password"
placeholderText='Повторите пароль...'
id="repeatPassword"
value={repeatPassword}
onChange={(id, value) => handleChange(id, value)}
style={{ marginTop: '10px' }}
/>
{!passwordsMatch && (
<p style={{ color: 'red', marginTop: '5px' }}>
Пароли не совпадают!
</p>
)}
</div>
</Content>
<ButtonContainer>
<MainButton style={{ width: '100%' }} title="Отправить" type="submit" />
</ButtonContainer>
</Container>
</Wrapper>
)
}

View file

@ -1,69 +0,0 @@
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

@ -1,46 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,282 +0,0 @@
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

@ -1,17 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,73 +0,0 @@
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

@ -1,246 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,82 +0,0 @@
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>
)
}

View file

@ -1,58 +0,0 @@
import React from "react";
import { buttonNames } from "./names";
import { useTheme } from "styled-components";
import { Icon } from '@iconify/react';
import exitToApp from '@iconify/icons-ic/twotone-exit-to-app';
import deleteIcon from '@iconify/icons-ic/baseline-delete';
import settings from '@iconify/icons-ic/baseline-settings';
import info from '@iconify/icons-ic/baseline-info';
import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down';
import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right';
import edit from '@iconify/icons-ic/baseline-edit';
import checkCircle from '@iconify/icons-ic/round-check-circle';
import cancel from '@iconify/icons-ic/baseline-cancel';
import admin from '@iconify/icons-ic/baseline-admin-panel-settings';
import noAdmin from '@iconify/icons-ic/baseline-remove-moderator'
import refresh from '@iconify/icons-ic/baseline-refresh';
import close from '@iconify/icons-ic/round-close';
export const ICONS = {
[buttonNames[1]]: exitToApp,
[buttonNames[2]]: deleteIcon,
[buttonNames[3]]: settings,
[buttonNames[4]]: info,
[buttonNames[5]]: arrowDown,
[buttonNames[6]]: arrowRight,
[buttonNames[7]]: edit,
[buttonNames[8]]: checkCircle,
[buttonNames[9]]: cancel,
[buttonNames[10]]: admin,
[buttonNames[11]]: refresh,
[buttonNames[12]]: close,
[buttonNames[13]]: noAdmin
} as const;
type IconName = keyof typeof ICONS;
interface IconsProps {
name: IconName,
width?: string
height?: string
setColor?: string
onClick?: (event?: React.MouseEvent<SVGSVGElement>) => void;
}
export const IconComponent = ({ name, width = "24", height = "24", setColor, onClick}: IconsProps) => {
const theme = useTheme();
return (
<Icon
icon={ICONS[name]}
width={width}
height={height}
color={setColor || theme.textColor}
onClick={onClick}
/>
);
}

View file

@ -1,17 +0,0 @@
import { names } from "../types/types"
export const buttonNames: names ={
1: 'powerOff',
2: 'delete',
3: 'settings',
4: 'information',
5: 'arrow-down',
6: 'arrow-right',
7: 'edit',
8: 'accept',
9: 'cancel',
10: 'admin',
11: 'reset',
12: 'close',
13: 'noAdmin'
}

View file

@ -1,9 +0,0 @@
import { Menu } from "../../types/types";
import { menusNames } from "./names";
export const menusData: Menu[] = [
{
id: menusNames[1],
isOpen: false
}
]

View file

@ -1,5 +0,0 @@
import { names } from "../../types/types"
export const menusNames: names ={
1: 'collapse-devices',
}

View file

@ -1,20 +0,0 @@
import React from "react"
import { toolBarNames } from "./names"
import { DevicesBar } from "../../../components/workSpace/sideBar/toolSpace/devicesBar"
export const COMPONENTS = {
[toolBarNames[1]]:(
<DevicesBar/>
),
} as const
type ComponentName = keyof typeof COMPONENTS
interface SpaceProps {
name: ComponentName,
}
export const Space = ({name}: SpaceProps) => {
const Component = COMPONENTS[name]
return React.cloneElement(Component);
}

View file

@ -1,13 +0,0 @@
import { ToolBar } from "../../types/types";
import { toolBarNames } from "./names";
export const toolBarData: ToolBar = {
current: toolBarNames[1],
toolSpace: [
{
id: toolBarNames[1],
isOpen: true,
isActive: true
},
]
};

View file

@ -1,32 +0,0 @@
import React from "react"
import { toolBarNames } from "./names"
import { useTheme } from "styled-components"
import { Icon } from '@iconify/react';
import desktop from '@iconify/icons-ic/round-desktop-windows';
export const ICONS = {
[toolBarNames[1]]: desktop,
} as const
type IconName = keyof typeof ICONS
interface IconsProps {
name: IconName,
width?: string
height?: string
setColor?: string
}
export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => {
const theme = useTheme();
return (
<Icon
icon={ICONS[name]}
width={width}
height={height}
color={setColor || theme.textColor}
/>
);
}

View file

@ -1,5 +0,0 @@
import { names } from "../../types/types"
export const toolBarNames: names ={
1: 'toolSpace-devices',
}

View file

@ -1,8 +0,0 @@
import { names } from "../types/types"
export const modalNames: names ={
1: 'loader',
2: 'ipConfirm',
3: 'connectionError',
4: 'addDevice'
}

View file

@ -1,55 +0,0 @@
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
body {
background-color: ${(props) => props.theme.backgroundColor};
color: ${(props) => props.theme.textColor};
box-sizing: border-box;
font-family: sans-serif;
box-sizing: border-box;
min-height: 100%;
height: 100%;
margin: 0;
h1, h2, h3, h4, P{
margin: 0;
display: unset;
}
}
#root{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.react-flow__controls{
border-radius: 12px;
background-color: ${(props) => props.theme.secondary};
button{
background-color: ${(props) => props.theme.secondary};
border: none;
border-radius: 12px;
&:hover{
background-color: ${(props) => props.theme.primary};
}
}
.react-flow__controls-button{
border-radius: 12px;
}
}
.react-flow__handle{
background-color: ${(props) => props.theme.nodeColor};
}
.unselectable {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
`;

View file

@ -1,36 +0,0 @@
import React from 'react';
import { Icon } from '@iconify/react';
import { useTheme } from 'styled-components';
import { themeNames } from './names';
import light from '@iconify/icons-ic/baseline-brightness-7';
import dark from '@iconify/icons-ic/baseline-brightness-5';
import system from '@iconify/icons-ic/baseline-brightness-6';
export const ICONS = {
[themeNames[1]]: light,
[themeNames[2]]: dark,
[themeNames[3]]: system,
} as const;
type IconName = keyof typeof ICONS;
interface IconsProps {
name: IconName,
width?: string
height?: string
setColor?: string
}
export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => {
const theme = useTheme();
return (
<Icon
icon={ICONS[name]}
width={width}
height={height}
color={setColor || theme.textColor}
/>
);
}

View file

@ -1,10 +0,0 @@
import { names } from "../types/types"
export const themeNames: names ={
1: 'light',
2: 'dark',
3: 'system'
}
const themeType = themeNames[1] || themeNames[2] || themeNames[3]
export type ThemName = typeof themeType

View file

@ -1,20 +0,0 @@
const baseTheme = {
accent: '#007ACC',
accentHover: '#4A90E2',
}
export const light = {
...baseTheme,
primary: '#F3F3F3',
secondary: '#EDEDED',
background: '#E8E8E8',
textColor: '#000000',
};
export const dark = {
...baseTheme,
primary: '#1E1E1E',
secondary: '#252525',
background: '#3A3A3A',
textColor: '#FFFFFF',
};

View file

@ -1,213 +0,0 @@
export interface IEdge {
source: string
sourceHandle: string
target: string
targetHandle: string
id: string
type: string
style?: any
}
export interface ICanvas {
nodes: any[],
edges: IEdge[]
}
export interface IpRoutes {
destination: string
prefix: number,
next_hop: string
metric: number
}
export interface ResponseIpConf {
address: string,
method: string,
gateway: string,
dns: string[]
routes: IpRoutes[]
}
export interface ResponseNetIp {
configurations: ResponseIpConf[]
}
export interface ResponseNetwork {
name: string,
type: string,
mac_address: string,
device_state: string,
active_connection: string,
device_mtu: string,
strength: string,
ipv4: ResponseNetIp
ipv6: ResponseNetIp
}
export interface ResponseData {
interfaces: ResponseNetwork[],
container_x: number,
container_y: number,
id: number | string
}
export interface OutputIpData {
ipType: string,
ipAdress: string,
ipGate: string,
ipDns: string[]
ipRoutese: IpRoutes[]
ipVersion: string
}
export interface IPosition {
x: number;
y: number;
}
export interface IStyle {
width: number,
height: number,
borderType?: string;
borderColor?: string;
backgroundColor?: string
}
export interface NetworkData {
device: string
type: string
mac: string
mtu: string,
state: string,
connection: string
id?: string,
strength?: string
}
export interface IpData {
ipType: string,
ipAdress: string,
ipGate: string,
ipDns: string[]
ipRoutese: IpRoutes[]
ipVersion: string
}
export interface NetworkNode {
id: string;
position: IPosition;
data: {
networkData: NetworkData,
style: IStyle,
height: any,
visible: boolean
}
type: string;
parentId: string,
extent: string,
}
export interface PortNode {
id: string
position: IPosition
data: {
id: string
label: string
port?: string
style: IStyle
}
type: string,
parentId?: string,
extent?: string,
}
export interface IpNode {
id: string;
position: IPosition;
parentId: string
extent: string
draggable: boolean,
data: {
ipData: IpData;
interfaceData:{
device: string,
connection: string
}
route: boolean
style: IStyle
};
hidden?: boolean
type: string;
}
export interface NodeGroup {
id: string
position: IPosition
parentId?: string
extent?: string
draggable?: boolean,
data: {
id?: string
label?: string
height?: any
style: IStyle
},
hidden?: boolean
type: string
}
export interface OutputIpArray {
route: boolean,
ipData: OutputIpData[]
}
export interface MainData {
networkData: NetworkData
ipArrays: OutputIpArray[]
}
export interface Interfaces {
mainData: MainData[]
container_x: number,
container_y: number
}
export interface IData{
activation: ResponseData
deactivation: ResponseData
}
export interface changeIp {
currentIp: string
newIp: string
currentGate: string
newGate: string
currentDns: string[]
newDns: string[]
device: string
connection: string
version: string
prefix: number
}
export interface WorkSpace {
id: string,
isActive: boolean
}
export interface Menu {
id: string,
isOpen: boolean,
isActive?: boolean
}
export interface ToolBar{
toolSpace: Menu[]
current: string
}
export type names = Record<number, string>;
export type NetworkInterface = NetworkNode | PortNode | NodeGroup | IpNode

View file

@ -1,7 +0,0 @@
import { names } from "../types/types"
export const workSpaceNames: names ={
1: 'helloWindow',
2: 'aboutApp',
3: 'networkInterfaces'
}

View file

@ -1,5 +0,0 @@
interface Window {
electron: {
quitApp: () => void;
};
}

View file

@ -1,15 +0,0 @@
import { useMenuStore } from "../store"
export const useMenu = (menuId: string)=>{
const { menus, toggleMenu } = useMenuStore();
const menu = menus.find((m)=>m.id === menuId)
const isOpen = menu ? menu.isOpen : false;
const collapseMenu = ()=>{
if(menu){
toggleMenu(menuId)
}
}
return { isOpen, collapseMenu };
}

View file

@ -1,16 +0,0 @@
import { useToolBarStore } from "../store";
export const useToolBar = (menuId: string) => {
const { toolBar, toggleMenu } = useToolBarStore();
const menu = toolBar.toolSpace.find((m) => m.id === menuId);
const isOpen = menu ? menu.isOpen : false;
const isActive = menu ? menu.isActive : false;
const collapseMenu = () => {
toggleMenu(menuId);
};
return { isOpen, isActive, collapseMenu };
};

View file

@ -1,44 +0,0 @@
import { Routes, Route, Outlet } from 'react-router-dom';
import WorkSpace from './components/workSpace/workSpace';
import { useIpStore } from './store';
import { useEffect, useState } from 'react';
import { NetworkService } from './api/request';
import { CreateUser } from './components/register';
import RouterLoader from './components/UI/routerLoader';
export default function Router() {
const { ipAddress } = useIpStore();
const [configExists, setConfigExists] = useState<boolean | null>(null);
useEffect(() => {
const fetchServerConf = async () => {
try {
let response = await NetworkService.checkServerConf(ipAddress);
if(response.status === 200) {
console.log(response.data.message);
setConfigExists(response.data.has_users);
}
} catch (error) {
console.error('Error checking server config:', error);
setConfigExists(false);
}
};
fetchServerConf();
}, [ipAddress]);
if (configExists === null) {
return <RouterLoader loaderText='Идёт загрузка данных'/>
}
if (configExists === false) {
return <CreateUser/>;
}
return (
<Routes>
<Route path='/' element={<WorkSpace />} />
</Routes>
);
}

View file

@ -1,11 +0,0 @@
import { create } from 'zustand'
interface AdminState {
isAdmin: boolean;
setAdmin: (isAdmin: boolean) => void;
}
export const useAdminStore = create<AdminState>()((set) => ({
isAdmin: false,
setAdmin: (isAdmin: boolean) => set({ isAdmin }),
}))

View file

@ -1,39 +0,0 @@
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
export interface Device {
number: number
address: string
}
interface DevicesState {
devices: Device[]
addDevice: (address: string) => void
deleteDevice: (number: number) => void
}
export const useDevicesStore = create<DevicesState>()(
persist(
(set) => ({
devices: [],
addDevice: (address) =>
set((state) => {
const newDevice = {
number: state.devices.length + 1,
address
}
return { devices: [...state.devices, newDevice] }
}),
deleteDevice: (number) =>
set((state) => ({
devices: state.devices
.filter(device => device.number !== number)
.map((device, index) => ({ ...device, number: index + 1 }))
}))
}),
{
name: 'connectedDevices-storage',
storage: createJSONStorage(() => localStorage)
}
)
)

View file

@ -1,22 +0,0 @@
import { create } from 'zustand';
interface NodesStore {
nodes: any[];
edges: any[];
setNodes: (nodes: any[]) => void;
setEdges: (edges: any[]) => void;
updateNode: (nodeId: string, newData: any) => void;
}
export const useNodesStore = create<NodesStore>((set) => ({
nodes: [],
edges: [],
setNodes: (nodes) => set({ nodes }),
setEdges: (edges) => set({ edges }),
updateNode: (nodeId, newData) =>
set((state) => ({
nodes: state.nodes.map((node) =>
node.id === nodeId ? { ...node, data: { ...node.data, ...newData } } : node
),
})),
}));

View file

@ -1,13 +0,0 @@
import {create} from 'zustand';
interface GroupState {
groups: { id: string; click: boolean }[];
setGroups: (groups: { id: string; click: boolean }[]) => void;
}
const useGroupStore = create<GroupState>((set) => ({
groups: [],
setGroups: (groups) => set({ groups }),
}));
export { useGroupStore };

Some files were not shown because too many files have changed in this diff Show more