Инициализация в новом репозитории
This commit is contained in:
commit
eb1f18f53e
374 changed files with 226678 additions and 0 deletions
85
FRONT/DEV/src/api/request.ts
Normal file
85
FRONT/DEV/src/api/request.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
108
FRONT/DEV/src/assets/css/flow.css
Normal file
108
FRONT/DEV/src/assets/css/flow.css
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
.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;
|
||||
}
|
||||
36
FRONT/DEV/src/assets/css/index.css
Normal file
36
FRONT/DEV/src/assets/css/index.css
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
29
FRONT/DEV/src/components/UI/animDot.tsx
Normal file
29
FRONT/DEV/src/components/UI/animDot.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
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>
|
||||
);
|
||||
75
FRONT/DEV/src/components/UI/buttons/mainButton.tsx
Normal file
75
FRONT/DEV/src/components/UI/buttons/mainButton.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
45
FRONT/DEV/src/components/UI/buttons/quitButton.tsx
Normal file
45
FRONT/DEV/src/components/UI/buttons/quitButton.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
26
FRONT/DEV/src/components/UI/buttons/toggletheme.tsx
Normal file
26
FRONT/DEV/src/components/UI/buttons/toggletheme.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
65
FRONT/DEV/src/components/UI/inputs/input.tsx
Normal file
65
FRONT/DEV/src/components/UI/inputs/input.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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;
|
||||
101
FRONT/DEV/src/components/UI/loader.tsx
Normal file
101
FRONT/DEV/src/components/UI/loader.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
103
FRONT/DEV/src/components/UI/routerLoader.tsx
Normal file
103
FRONT/DEV/src/components/UI/routerLoader.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
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>
|
||||
|
||||
)
|
||||
}
|
||||
61
FRONT/DEV/src/components/UI/signalSvg.tsx
Normal file
61
FRONT/DEV/src/components/UI/signalSvg.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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;
|
||||
`
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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;
|
||||
`
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
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 >
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
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;
|
||||
`;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
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'
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
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;
|
||||
}
|
||||
`
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
`
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
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'
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
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)} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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};
|
||||
`
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
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>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
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;
|
||||
}
|
||||
`
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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;
|
||||
}
|
||||
`
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
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;
|
||||
}
|
||||
`
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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;
|
||||
}
|
||||
`
|
||||
11
FRONT/DEV/src/components/customeNodes/types/colors.ts
Normal file
11
FRONT/DEV/src/components/customeNodes/types/colors.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
type ColorType = Record<string, string>;
|
||||
|
||||
export const NodeColors: ColorType = {
|
||||
'0': '#c0c0c0',
|
||||
'1': '#ef6e7d',
|
||||
'2': '#f19e4c',
|
||||
'3': '#e9c44c',
|
||||
'4': '#51ce82',
|
||||
'5': '#4cd2d0',
|
||||
'6': '#a085f3',
|
||||
};
|
||||
16
FRONT/DEV/src/components/customeNodes/types/types.ts
Normal file
16
FRONT/DEV/src/components/customeNodes/types/types.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
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,
|
||||
};
|
||||
10
FRONT/DEV/src/components/modalwindows/modal.tsx
Normal file
10
FRONT/DEV/src/components/modalwindows/modal.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { ModalWindow, ModalContainer } from "./style"
|
||||
import { modalNames } from "../../const/modals/names"
|
||||
|
||||
export const WindowComponent = ()=>{
|
||||
return(
|
||||
<ModalWindow>
|
||||
|
||||
</ModalWindow>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export {IpConfirm} from './ipConfirm'
|
||||
export {ConnectionError} from './connectionError'
|
||||
export {AddDevice} from './addDevice'
|
||||
export {RightsConfirm} from './rightsConfirm'
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
50
FRONT/DEV/src/components/modalwindows/modalWindow.tsx
Normal file
50
FRONT/DEV/src/components/modalwindows/modalWindow.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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 >
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
70
FRONT/DEV/src/components/modalwindows/style.ts
Normal file
70
FRONT/DEV/src/components/modalwindows/style.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
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;
|
||||
`
|
||||
9
FRONT/DEV/src/components/modalwindows/windowsType.ts
Normal file
9
FRONT/DEV/src/components/modalwindows/windowsType.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export type ModalType = Record<string, string>;
|
||||
|
||||
export const ModalMap: ModalType = {
|
||||
'1': 'ipConfirm',
|
||||
'2': 'loader',
|
||||
'3': 'connectionError',
|
||||
'4': 'addDevice',
|
||||
'5': 'sudo'
|
||||
}
|
||||
142
FRONT/DEV/src/components/register.tsx
Normal file
142
FRONT/DEV/src/components/register.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
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>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import styled from "styled-components"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
const Container = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
text-align: center;
|
||||
|
||||
h1, h2, h3, p{
|
||||
margin: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
max-width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 100px;
|
||||
button{
|
||||
width: 250px;
|
||||
}
|
||||
`
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
margin-left: 50px;
|
||||
`
|
||||
|
||||
const Column = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
`
|
||||
|
||||
export default function About() {
|
||||
const navigate = useNavigate()
|
||||
const redirect = (path: string) => {
|
||||
navigate(path)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Content>
|
||||
<Row>
|
||||
<h1>GoNetMan</h1>
|
||||
<p>v 0.0.1</p>
|
||||
</Row>
|
||||
|
||||
<Column>
|
||||
<h3>О программе</h3>
|
||||
GoNetMan - Это приложение представляющее собой панель управления сетевыми настройками в удобном и понятном графическом виде.
|
||||
</Column>
|
||||
|
||||
<p>Copyright © ООО "Гибридные офисные системы"</p>
|
||||
|
||||
<button onClick={()=>redirect('/')}>В меню</button>
|
||||
</Content>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import styled from "styled-components"
|
||||
import { IconComponent } from "../../../../const/buttons/icons"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
import { useModalContext } from "../../../../store"
|
||||
import { useAdminStore } from "../../../../store/adminStore"
|
||||
import { ModalMap } from "../../../modalwindows/windowsType"
|
||||
import { QuitButton } from "../../../UI/buttons/quitButton"
|
||||
import { usePosStore } from "../../../../store/posStore"
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
background-color: ${(props) => props.theme.primary};
|
||||
padding: 0 5px;
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const IconContainer = styled.div`
|
||||
padding: 10px;
|
||||
|
||||
svg{
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export const Header = () => {
|
||||
const {setNullPos} = usePosStore()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ButtonContainer>
|
||||
<IconContainer>
|
||||
<IconComponent name={buttonNames[11]} width="30" height="30" onClick={()=>setNullPos(true)}/>
|
||||
</IconContainer>
|
||||
|
||||
<QuitButton size="small"/>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import styled from "styled-components"
|
||||
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${(props => props.theme.background)};
|
||||
background-image: url('./assets/images/back1.svg');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 52px;
|
||||
margin-bottom: 100px;
|
||||
`
|
||||
|
||||
const ButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 75px;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
|
||||
h1{
|
||||
font-size: 52px;
|
||||
}
|
||||
`
|
||||
|
||||
export const HelloWindow = () => {
|
||||
|
||||
const { setWorkModule } = useWorkSpaceStore()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Container className="unselectable">
|
||||
<Title>
|
||||
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
|
||||
</Title>
|
||||
|
||||
|
||||
<ButtonWrapper>
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
title="Настройки сети"
|
||||
icon={buttonNames[3]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
onClick={() => setWorkModule(workSpaceNames[3])}
|
||||
/>
|
||||
<MainButton
|
||||
title="О программе"
|
||||
icon={buttonNames[4]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
</ButtonWrapper>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import { useTheme } from 'styled-components';
|
||||
import { usePosStore } from '../../../../store/posStore';
|
||||
|
||||
import { ReactFlow, useNodesState, useEdgesState, Background, addEdge, applyEdgeChanges, Controls } from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import '../../../../assets/css/flow.css'
|
||||
|
||||
import { nodeTypes } from '../../../customeNodes/types/types';
|
||||
import { ModalMap } from '../../../modalwindows/windowsType';
|
||||
|
||||
import {
|
||||
useGroupStore,
|
||||
useModalContext,
|
||||
usePannableStore
|
||||
} from '../../../../store'
|
||||
|
||||
import {
|
||||
autosavePosition,
|
||||
resetPosition,
|
||||
processNodeChanges,
|
||||
initNetworkIntData,
|
||||
groupSort,
|
||||
collapseGroup,
|
||||
updateWifiData,
|
||||
useInitCanvas
|
||||
} from '../../../../utils/nodeController';
|
||||
|
||||
import {
|
||||
ResponseData,
|
||||
ResponseNetwork,
|
||||
IEdge,
|
||||
Interfaces,
|
||||
} from '../../../../const/types/types';
|
||||
|
||||
type Props = {
|
||||
startConnection: boolean
|
||||
ws: string
|
||||
}
|
||||
|
||||
const proOptions = { hideAttribution: true, };
|
||||
|
||||
export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
||||
|
||||
const initializationAttempt = useRef(0);
|
||||
const [requestError, setRequestError] = useState({ status: false, message: '' })
|
||||
|
||||
const { openModal, closeLoader } = useModalContext();
|
||||
const [loader, setLoader] = useState(true)
|
||||
const theme = useTheme();
|
||||
|
||||
const [socket, setSocket] = useState<WebSocket | null>(null);
|
||||
const [data, setData] = useState<ResponseData[]>([]);
|
||||
const [wifiData, setWifi] = useState<ResponseNetwork>()
|
||||
const [hasInited, setHasInited] = useState(false);
|
||||
const { initedNodes, initedEdges, handleInit } = useInitCanvas();
|
||||
|
||||
const [interfaces, setInterfaces] = useState<Interfaces[]>([]);
|
||||
const [edgeData, setEdgeData] = useState<IEdge[]>([]);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([] as any[]);
|
||||
const [edges, setEdges] = useEdgesState([] as any[]);
|
||||
const { isPanDisabled } = usePannableStore()
|
||||
|
||||
const [changeArray, setChangeArray] = useState<any[]>([]);
|
||||
const { groups } = useGroupStore();
|
||||
const {isNullPos, setNullPos} = usePosStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (startConnection) {
|
||||
|
||||
openModal(ModalMap['2'], 'Идет загрузка данных');
|
||||
|
||||
setNodes([])
|
||||
setEdges([])
|
||||
setHasInited(false)
|
||||
initializationAttempt.current = 0;
|
||||
|
||||
const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
|
||||
|
||||
let connectionTimeout;
|
||||
setSocket(newSocket);
|
||||
|
||||
connectionTimeout = setTimeout(() => {
|
||||
if (newSocket.readyState !== WebSocket.OPEN) {
|
||||
newSocket.close();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
newSocket.onopen = () => {
|
||||
console.log('WebSocket connection established');
|
||||
};
|
||||
|
||||
newSocket.onmessage = (event) => {
|
||||
const newData = JSON.parse(event.data);
|
||||
|
||||
setData(() => [
|
||||
newData.activation,
|
||||
newData.deactivation,
|
||||
]);
|
||||
};
|
||||
|
||||
newSocket.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
newSocket.onclose = () => {
|
||||
console.log('WebSocket connection closed');
|
||||
setRequestError({
|
||||
status: true,
|
||||
message: 'Соединение с сервером разорвано'
|
||||
})
|
||||
};
|
||||
|
||||
return () => {
|
||||
newSocket.close();
|
||||
closeLoader()
|
||||
};
|
||||
}
|
||||
}, [startConnection, ws]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data.length > 0) {
|
||||
const { interfaces: newInterfaces, edgeData: newEdgeData } = initNetworkIntData(data);
|
||||
const activationData = data[0].interfaces;
|
||||
|
||||
const wifi = activationData.find((el: any) => el.type === 'WIFI');
|
||||
|
||||
setInterfaces(newInterfaces);
|
||||
setEdgeData(newEdgeData);
|
||||
if (wifi) { setWifi(wifi); }
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (interfaces.length > 0 && !hasInited) {
|
||||
handleInit(edgeData, interfaces)
|
||||
setHasInited(true)
|
||||
}
|
||||
}, [interfaces, edgeData, hasInited])
|
||||
|
||||
useEffect(() => {
|
||||
if (initializationAttempt.current === 0 && !requestError.status) {
|
||||
const checkInterval = setInterval(() => {
|
||||
|
||||
if(initedNodes.length> 0 && initedNodes.length<=4){
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
if (initedNodes.length > 0 && initedEdges.length > 0) {
|
||||
setNodes(initedNodes);
|
||||
setEdges(initedEdges);
|
||||
console.log("Ноды и эджи успешно инициализированы");
|
||||
initializationAttempt.current = 1;
|
||||
clearInterval(checkInterval);
|
||||
clearTimeout(timeoutId);
|
||||
} else {
|
||||
console.log("Попытка инициализации нод и эджей...");
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.error("Ошибка: слишком долгая загрузка данных");
|
||||
clearInterval(checkInterval);
|
||||
setRequestError({
|
||||
status: true,
|
||||
message: 'Превышено время ожидания'
|
||||
})
|
||||
}, 60000);
|
||||
|
||||
return () => {
|
||||
clearInterval(checkInterval);
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
}, [initedNodes, initedEdges, requestError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodes.length === 0) {
|
||||
initializationAttempt.current = 0;
|
||||
} else {
|
||||
initializationAttempt.current = 1;
|
||||
}
|
||||
}, [nodes]);
|
||||
|
||||
useEffect(() => {
|
||||
if ((nodes.length > 0 && loader == true)) {
|
||||
setLoader(false)
|
||||
closeLoader()
|
||||
}
|
||||
}, [nodes, loader])
|
||||
|
||||
const onEdgesChange = useCallback(
|
||||
(changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: any) => setEdges((eds) => addEdge(connection, eds)),
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const handleNodesChange = (changes: any) => {
|
||||
onNodesChange(changes);
|
||||
|
||||
const newData = processNodeChanges(changes, data);
|
||||
|
||||
if (newData) {
|
||||
setChangeArray((prevArray) => [...prevArray, newData]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
autosavePosition(changeArray, socket);
|
||||
}
|
||||
setChangeArray([]);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [changeArray, socket]);
|
||||
|
||||
useEffect(() => {
|
||||
const sortedGroups = groupSort(groups, nodes)
|
||||
setNodes(collapseGroup(sortedGroups, nodes));
|
||||
}, [groups]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wifiData) {
|
||||
setNodes(updateWifiData(wifiData, nodes));
|
||||
}
|
||||
}, [wifiData])
|
||||
|
||||
useEffect(() => {
|
||||
if (requestError.status) {
|
||||
setTimeout(() => {
|
||||
openModal(ModalMap['3'], {
|
||||
message: requestError.message,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}, [requestError]);
|
||||
|
||||
useEffect(() => {
|
||||
if(isNullPos){
|
||||
if(socket && socket.readyState === WebSocket.OPEN){
|
||||
resetPosition(socket, setNullPos, nodes, setNodes)
|
||||
}
|
||||
}
|
||||
}, [isNullPos]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
style={{ background: '#F7F9FB' }}
|
||||
minZoom={0}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.1 }}
|
||||
attributionPosition="bottom-left"
|
||||
snapToGrid
|
||||
snapGrid={[20, 20]}
|
||||
panOnDrag={!isPanDisabled}
|
||||
nodeTypes={nodeTypes}
|
||||
proOptions={proOptions}
|
||||
>
|
||||
<Background gap={20} size={1} bgColor={theme.background} />
|
||||
<Controls fitViewOptions={{ padding: 0.1 }} />
|
||||
</ReactFlow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
17
FRONT/DEV/src/components/workSpace/sideBar/sideBar.tsx
Normal file
17
FRONT/DEV/src/components/workSpace/sideBar/sideBar.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { ToolBar } from "./toolBar/toolBar"
|
||||
import { ToolSpace } from "./toolSpace/toolSpace"
|
||||
import styled from "styled-components"
|
||||
|
||||
const Container = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
`
|
||||
|
||||
export const SideBar = ()=>{
|
||||
return(
|
||||
<Container>
|
||||
<ToolBar/>
|
||||
<ToolSpace/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import styled from "styled-components"
|
||||
import { IconComponent as Icon } from "../../../../const/menus/sideBar/icons"
|
||||
import { useToolBar } from "../../../../hooks/useToolBar"
|
||||
import { useTheme } from "styled-components"
|
||||
|
||||
type Props = {
|
||||
menuId: string
|
||||
}
|
||||
|
||||
const Button = styled.div`
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const IconContainer = styled.div`
|
||||
padding: 10px;
|
||||
`
|
||||
|
||||
export const ToolBarButton = ({ menuId }: Props) => {
|
||||
const { isActive, collapseMenu } = useToolBar(menuId);
|
||||
const theme = useTheme();
|
||||
|
||||
const iconColor = isActive ? theme.textColor : theme.active;
|
||||
|
||||
return (
|
||||
<Button onClick={collapseMenu}>
|
||||
<IconContainer>
|
||||
<Icon name={menuId} width="30" height="30" setColor={iconColor} />
|
||||
</IconContainer>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import { useToolBarStore } from "../../../../store"
|
||||
import { ToolBarButton } from "./toolBar-button"
|
||||
import styled from "styled-components"
|
||||
import ThemeButton from "../../../UI/buttons/toggletheme"
|
||||
import { IconComponent } from "../../../../const/buttons/icons"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||
|
||||
import { useModalContext } from "../../../../store"
|
||||
import { useAdminStore } from "../../../../store/adminStore"
|
||||
import { ModalMap } from "../../../modalwindows/windowsType"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: ${(props) => props.theme.primary};
|
||||
padding: 5px 0;
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Button = styled.div`
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const IconContainer = styled.div`
|
||||
padding: 10px;
|
||||
color: ${props => props.theme.text};
|
||||
`
|
||||
|
||||
export const ToolBar = () => {
|
||||
const { toolBar } = useToolBarStore()
|
||||
const toolBarMenu = toolBar?.toolSpace
|
||||
|
||||
const { openModal } = useModalContext()
|
||||
const {isAdmin} = useAdminStore()
|
||||
const {setWorkModule} = useWorkSpaceStore()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ButtonContainer>
|
||||
{toolBarMenu && toolBarMenu.map((menu) => (
|
||||
<ToolBarButton menuId={menu.id} key={menu.id} />
|
||||
))}
|
||||
</ButtonContainer>
|
||||
|
||||
<ButtonContainer>
|
||||
|
||||
<Button>
|
||||
<IconContainer>
|
||||
<IconComponent name={isAdmin ? buttonNames[10] : buttonNames[13]} width="30" height="30" onClick={() => openModal(ModalMap['5'])}/>
|
||||
</IconContainer>
|
||||
</Button>
|
||||
|
||||
<ThemeButton />
|
||||
|
||||
<Button>
|
||||
<IconContainer>
|
||||
<IconComponent name={buttonNames[4]} width="30" height="30" onClick={()=>setWorkModule(workSpaceNames[2])}/>
|
||||
</IconContainer>
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
import styled from "styled-components"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useModalContext } from "../../../../store"
|
||||
import { ModalMap } from "../../../modalwindows/windowsType"
|
||||
import { NetworkService } from "../../../../api/request"
|
||||
import { AnimDot } from "../../../UI/animDot"
|
||||
import { useMenu } from "../../../../hooks/useMenu"
|
||||
import { useIpStore, useDevicesStore } from "../../../../store"
|
||||
import { menusNames } from "../../../../const/menus/othersMenus/names"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||
import { IconComponent } from "../../../../const/buttons/icons"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
|
||||
p{
|
||||
margin: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
padding: 5px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid ${props => props.theme.text};
|
||||
`
|
||||
|
||||
const DeviceBlock = styled.div`
|
||||
border-radius: 8px;
|
||||
border: 2px solid ${props => props.theme.accent};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.paddingP{
|
||||
padding: 7px 5px;
|
||||
}
|
||||
`
|
||||
|
||||
const DeviceControl = styled.div`
|
||||
padding: 5px 8px;
|
||||
background: ${props => props.theme.accent};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const PlusBlock = styled.div`
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
border: 2px dotted ${props => props.theme.accent};
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
`
|
||||
|
||||
const Text = styled.div`
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const DevicesContainer = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
`
|
||||
|
||||
const DevicesHeader = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.hControl{
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const DevicesContent = styled.div<{ $isactive: string }>`
|
||||
${props => props.$isactive === 'true' && `display: flex;`}
|
||||
${props => props.$isactive === 'false' && `display: none;`}
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
`
|
||||
|
||||
interface CurrentDevice {
|
||||
hostname: string
|
||||
address: string
|
||||
}
|
||||
|
||||
export const DevicesBar = () => {
|
||||
const { openModal } = useModalContext();
|
||||
const { setIpAddress, ipAddress } = useIpStore();
|
||||
const { devices, addDevice, deleteDevice } = useDevicesStore()
|
||||
const { isOpen, collapseMenu } = useMenu(menusNames[1]);
|
||||
const { setWorkModule } = useWorkSpaceStore()
|
||||
|
||||
const [currentDevice, setCurrentDevice] = useState<CurrentDevice>({
|
||||
address: 'localhost',
|
||||
hostname: ''
|
||||
})
|
||||
|
||||
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
|
||||
const timeout = setTimeout(() => {
|
||||
testSocket.close();
|
||||
resolve(false);
|
||||
}, 500
|
||||
);
|
||||
|
||||
testSocket.onopen = () => {
|
||||
clearTimeout(timeout);
|
||||
testSocket.close();
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
testSocket.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
testSocket.close();
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchHostame = async () => {
|
||||
let response = await NetworkService.getHostname();
|
||||
if (response.status === 200) {
|
||||
setCurrentDevice(prevData => ({
|
||||
...prevData,
|
||||
hostname: response.data.hostname
|
||||
}))
|
||||
}
|
||||
};
|
||||
fetchHostame();
|
||||
}, [])
|
||||
|
||||
const handleAdd = () => {
|
||||
openModal(ModalMap['4'], {
|
||||
onSave: handleAddDeviceComplete,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddDeviceComplete = (data: string) => {
|
||||
addDevice(data)
|
||||
setIpAddress(data)
|
||||
};
|
||||
|
||||
const handleDelete = (number: number, adress: string) => {
|
||||
deleteDevice(number)
|
||||
|
||||
|
||||
if(adress == ipAddress){
|
||||
setIpAddress('localhost')
|
||||
}
|
||||
};
|
||||
|
||||
const switchWs = async (ip: string) => {
|
||||
// Проверяем доступность WebSocket
|
||||
const isAvailable = await checkWebSocketAvailability(ip);
|
||||
if (!isAvailable) {
|
||||
openModal(ModalMap['3'], {
|
||||
message: `Не удалось подключиться к серверу по адресу ${ip}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Если WebSocket доступен, обновляем IP и переключаем модуль
|
||||
setIpAddress(ip);
|
||||
setWorkModule(workSpaceNames[3]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (devices.length === 0) {
|
||||
setIpAddress('localhost');
|
||||
}
|
||||
}, [devices]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Title>
|
||||
<p className="unselectable">Доступные узлы</p>
|
||||
</Title>
|
||||
|
||||
<DeviceBlock>
|
||||
<DeviceControl className="unselectable" onClick={() => switchWs(currentDevice.address)}>
|
||||
<p>Текущее устройство</p>
|
||||
</DeviceControl>
|
||||
{currentDevice.hostname == '' ? (
|
||||
<Text className="paddingP">Адрес: Загрузка<AnimDot /></Text>
|
||||
) : (
|
||||
<p className="paddingP">Адрес: {currentDevice.hostname}</p>
|
||||
)}
|
||||
</DeviceBlock>
|
||||
|
||||
<DevicesContainer>
|
||||
<DevicesHeader className="unselectable" onClick={collapseMenu}>
|
||||
<p>Дополнительные узлы</p>
|
||||
<IconComponent name={isOpen ? buttonNames[5] : buttonNames[6]} />
|
||||
</DevicesHeader>
|
||||
|
||||
<DevicesContent $isactive={isOpen.toString()} >
|
||||
{devices.length > 0 &&
|
||||
devices.map((device, index) => (
|
||||
<DeviceBlock key={index}>
|
||||
<DeviceControl className="unselectable" onClick={() => switchWs(device.address)}>
|
||||
<p>Узел {device.number}</p>
|
||||
<IconComponent
|
||||
width="20"
|
||||
height="20"
|
||||
name={buttonNames[2]}
|
||||
setColor="white"
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation(); // Предотвращаем всплытие события
|
||||
handleDelete(device.number, device.address);
|
||||
}}
|
||||
/>
|
||||
</DeviceControl>
|
||||
<p className="paddingP">Адрес: {device.address}</p>
|
||||
</DeviceBlock>
|
||||
))
|
||||
}
|
||||
|
||||
<PlusBlock className="unselectable" onClick={handleAdd}>
|
||||
+
|
||||
</PlusBlock>
|
||||
</DevicesContent>
|
||||
|
||||
</DevicesContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { useToolBarStore } from "../../../../store"
|
||||
import { useToolBar } from "../../../../hooks/useToolBar"
|
||||
import { Space } from "../../../../const/menus/sideBar/components"
|
||||
import styled from "styled-components"
|
||||
|
||||
const Container = styled.div`
|
||||
background-color: ${(props) => props.theme.secondary};
|
||||
width: 250px;
|
||||
font-size: 14px;
|
||||
flex: 3;
|
||||
`
|
||||
|
||||
export const ToolSpace = () => {
|
||||
const { toolBar } = useToolBarStore()
|
||||
const name = toolBar?.current || ''
|
||||
const { isActive, isOpen } = useToolBar(name)
|
||||
|
||||
if (!isActive || !isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Space name={name} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
82
FRONT/DEV/src/components/workSpace/workSpace.tsx
Normal file
82
FRONT/DEV/src/components/workSpace/workSpace.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import { HelloWindow } from "./modules/appModules/helloWindow";
|
||||
import { NetworkInterfaces } from "./modules/workModules/networkInterfaces";
|
||||
import styled from "styled-components"
|
||||
import { useIpStore, useToolBarStore } from "../../store";
|
||||
import { SideBar } from "./sideBar/sideBar";
|
||||
import { useWorkSpaceStore } from "../../store";
|
||||
import { workSpaceNames } from "../../const/workspace/names";
|
||||
import { Header } from "./modules/appModules/header";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
|
||||
const Section = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const FlowContainer = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const WorkSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
export default function WorkSpace() {
|
||||
const { ipAddress, setIpAddress } = useIpStore();
|
||||
const { workModule } = useWorkSpaceStore()
|
||||
const [currentWs, setCurrentWs] = useState(ipAddress);
|
||||
const initMenus = useToolBarStore((state) => state.initialize);
|
||||
const initModule = useWorkSpaceStore((state) => state.initialize)
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('toolBar-storage')) {
|
||||
initMenus();
|
||||
}
|
||||
}, [initMenus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage.getItem('workSpace-storage')) {
|
||||
initModule();
|
||||
}
|
||||
}, [initModule]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentWs(ipAddress);
|
||||
}, [ipAddress]);
|
||||
|
||||
useEffect(() => {
|
||||
setIpAddress(currentWs);
|
||||
}, [currentWs]);
|
||||
|
||||
console.log('aboba');
|
||||
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<FlowContainer>
|
||||
<SideBar />
|
||||
|
||||
{workModule === workSpaceNames[1] && <HelloWindow /> }
|
||||
|
||||
{workModule === workSpaceNames[3] &&
|
||||
<WorkSection>
|
||||
<Header />
|
||||
<ReactFlowProvider>
|
||||
<NetworkInterfaces
|
||||
key={currentWs}
|
||||
startConnection={true}
|
||||
ws={currentWs}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
</WorkSection>
|
||||
}
|
||||
</FlowContainer>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
58
FRONT/DEV/src/const/buttons/icons.tsx
Normal file
58
FRONT/DEV/src/const/buttons/icons.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
17
FRONT/DEV/src/const/buttons/names.ts
Normal file
17
FRONT/DEV/src/const/buttons/names.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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'
|
||||
}
|
||||
9
FRONT/DEV/src/const/menus/othersMenus/data.ts
Normal file
9
FRONT/DEV/src/const/menus/othersMenus/data.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Menu } from "../../types/types";
|
||||
import { menusNames } from "./names";
|
||||
|
||||
export const menusData: Menu[] = [
|
||||
{
|
||||
id: menusNames[1],
|
||||
isOpen: false
|
||||
}
|
||||
]
|
||||
5
FRONT/DEV/src/const/menus/othersMenus/names.ts
Normal file
5
FRONT/DEV/src/const/menus/othersMenus/names.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { names } from "../../types/types"
|
||||
|
||||
export const menusNames: names ={
|
||||
1: 'collapse-devices',
|
||||
}
|
||||
20
FRONT/DEV/src/const/menus/sideBar/components.tsx
Normal file
20
FRONT/DEV/src/const/menus/sideBar/components.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
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);
|
||||
}
|
||||
13
FRONT/DEV/src/const/menus/sideBar/data.ts
Normal file
13
FRONT/DEV/src/const/menus/sideBar/data.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { ToolBar } from "../../types/types";
|
||||
import { toolBarNames } from "./names";
|
||||
|
||||
export const toolBarData: ToolBar = {
|
||||
current: toolBarNames[1],
|
||||
toolSpace: [
|
||||
{
|
||||
id: toolBarNames[1],
|
||||
isOpen: true,
|
||||
isActive: true
|
||||
},
|
||||
]
|
||||
};
|
||||
32
FRONT/DEV/src/const/menus/sideBar/icons.tsx
Normal file
32
FRONT/DEV/src/const/menus/sideBar/icons.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
5
FRONT/DEV/src/const/menus/sideBar/names.ts
Normal file
5
FRONT/DEV/src/const/menus/sideBar/names.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { names } from "../../types/types"
|
||||
|
||||
export const toolBarNames: names ={
|
||||
1: 'toolSpace-devices',
|
||||
}
|
||||
8
FRONT/DEV/src/const/modals/names.ts
Normal file
8
FRONT/DEV/src/const/modals/names.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { names } from "../types/types"
|
||||
|
||||
export const modalNames: names ={
|
||||
1: 'loader',
|
||||
2: 'ipConfirm',
|
||||
3: 'connectionError',
|
||||
4: 'addDevice'
|
||||
}
|
||||
55
FRONT/DEV/src/const/theme/globalStyles.ts
Normal file
55
FRONT/DEV/src/const/theme/globalStyles.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
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;
|
||||
}
|
||||
`;
|
||||
36
FRONT/DEV/src/const/theme/icons.tsx
Normal file
36
FRONT/DEV/src/const/theme/icons.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
10
FRONT/DEV/src/const/theme/names.ts
Normal file
10
FRONT/DEV/src/const/theme/names.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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
|
||||
20
FRONT/DEV/src/const/theme/theme.ts
Normal file
20
FRONT/DEV/src/const/theme/theme.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
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',
|
||||
};
|
||||
213
FRONT/DEV/src/const/types/types.ts
Normal file
213
FRONT/DEV/src/const/types/types.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
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
|
||||
7
FRONT/DEV/src/const/workspace/names.ts
Normal file
7
FRONT/DEV/src/const/workspace/names.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { names } from "../types/types"
|
||||
|
||||
export const workSpaceNames: names ={
|
||||
1: 'helloWindow',
|
||||
2: 'aboutApp',
|
||||
3: 'networkInterfaces'
|
||||
}
|
||||
5
FRONT/DEV/src/electron.d.ts
vendored
Normal file
5
FRONT/DEV/src/electron.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
interface Window {
|
||||
electron: {
|
||||
quitApp: () => void;
|
||||
};
|
||||
}
|
||||
15
FRONT/DEV/src/hooks/useMenu.ts
Normal file
15
FRONT/DEV/src/hooks/useMenu.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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 };
|
||||
}
|
||||
16
FRONT/DEV/src/hooks/useToolBar.ts
Normal file
16
FRONT/DEV/src/hooks/useToolBar.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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 };
|
||||
};
|
||||
44
FRONT/DEV/src/router.tsx
Normal file
44
FRONT/DEV/src/router.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
11
FRONT/DEV/src/store/adminStore.ts
Normal file
11
FRONT/DEV/src/store/adminStore.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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 }),
|
||||
}))
|
||||
39
FRONT/DEV/src/store/devicesStore.ts
Normal file
39
FRONT/DEV/src/store/devicesStore.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
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)
|
||||
}
|
||||
)
|
||||
)
|
||||
22
FRONT/DEV/src/store/flowStore.ts
Normal file
22
FRONT/DEV/src/store/flowStore.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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
|
||||
),
|
||||
})),
|
||||
}));
|
||||
13
FRONT/DEV/src/store/groupStore.ts
Normal file
13
FRONT/DEV/src/store/groupStore.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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 };
|
||||
9
FRONT/DEV/src/store/index.ts
Normal file
9
FRONT/DEV/src/store/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export {useModalContext, ModalProvider} from './modalContext'
|
||||
export {useTheme, ThemeProvider} from './themeContext'
|
||||
export {usePannableStore} from './pannableStore'
|
||||
export {useGroupStore} from './groupStore'
|
||||
export {useIpStore} from './ipStore'
|
||||
export {useToolBarStore} from './toolBarStore'
|
||||
export {useDevicesStore} from './devicesStore'
|
||||
export {useMenuStore} from './menusStore'
|
||||
export {useWorkSpaceStore} from './workSpaceStore'
|
||||
20
FRONT/DEV/src/store/ipStore.ts
Normal file
20
FRONT/DEV/src/store/ipStore.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {create} from 'zustand'
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
|
||||
interface IpAddressState {
|
||||
ipAddress: string;
|
||||
setIpAddress: (newIp: string) => void;
|
||||
}
|
||||
|
||||
export const useIpStore = create<IpAddressState>()(
|
||||
persist(
|
||||
(set)=>({
|
||||
ipAddress: 'localhost',
|
||||
setIpAddress: (newIp: string) => set({ ipAddress: newIp }),
|
||||
}),
|
||||
{
|
||||
name: 'currentIp-storage',
|
||||
storage: createJSONStorage(()=>localStorage)
|
||||
}
|
||||
)
|
||||
)
|
||||
28
FRONT/DEV/src/store/menusStore.ts
Normal file
28
FRONT/DEV/src/store/menusStore.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { Menu } from '../const/types/types';
|
||||
import { menusData } from '../const/menus/othersMenus/data';
|
||||
|
||||
interface MenuState {
|
||||
menus: Menu[];
|
||||
toggleMenu: (menuId: string) => void;
|
||||
}
|
||||
|
||||
export const useMenuStore = create<MenuState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
menus: menusData,
|
||||
toggleMenu: (menuId) =>
|
||||
set((state) => ({
|
||||
menus: state.menus.map((menu) =>
|
||||
menu.id === menuId ? { ...menu, isOpen: !menu.isOpen } : menu
|
||||
),
|
||||
})),
|
||||
}),
|
||||
{
|
||||
name: 'menus-storage',
|
||||
storage: createJSONStorage(() => localStorage)
|
||||
}
|
||||
)
|
||||
);
|
||||
63
FRONT/DEV/src/store/modalContext.tsx
Normal file
63
FRONT/DEV/src/store/modalContext.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { createContext, useContext, useState, ReactNode, useCallback } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface ModalContextType {
|
||||
isOpen: boolean;
|
||||
modalType: string;
|
||||
modalData: any;
|
||||
openModal: (modalType: string, modalData?: any) => void;
|
||||
closeModal: () => void;
|
||||
closeLoader: () => void;
|
||||
}
|
||||
|
||||
const ModalContext = createContext<ModalContextType>({
|
||||
isOpen: false,
|
||||
modalType: '',
|
||||
modalData: null,
|
||||
openModal: () => {},
|
||||
closeModal: () => {},
|
||||
closeLoader: () => {},
|
||||
});
|
||||
|
||||
export const useModalContext = () => useContext(ModalContext);
|
||||
|
||||
export function ModalProvider({ children }: { children: ReactNode }) {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [modalType, setType] = useState('');
|
||||
const [modalData, setData] = useState(null);
|
||||
|
||||
const openModal = (type: string, data?: any) => {
|
||||
setOpen(true);
|
||||
setType(type);
|
||||
setData(data);
|
||||
};
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
setOpen(false);
|
||||
setType('');
|
||||
setData(null);
|
||||
|
||||
window.location.reload()
|
||||
}, []);
|
||||
|
||||
const closeLoader = useCallback(() => {
|
||||
setOpen(false);
|
||||
setType('');
|
||||
setData(null);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
else {
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
return (
|
||||
<ModalContext.Provider value={{ isOpen, modalType, modalData, openModal, closeModal, closeLoader }}>
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
11
FRONT/DEV/src/store/pannableStore.ts
Normal file
11
FRONT/DEV/src/store/pannableStore.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {create} from 'zustand';
|
||||
|
||||
interface FlowState {
|
||||
isPanDisabled: boolean;
|
||||
toggleEditingMode: () => void;
|
||||
}
|
||||
|
||||
export const usePannableStore = create<FlowState>((set) => ({
|
||||
isPanDisabled: false,
|
||||
toggleEditingMode: () => set((state) => ({ isPanDisabled: !state.isPanDisabled })),
|
||||
}));
|
||||
11
FRONT/DEV/src/store/posStore.ts
Normal file
11
FRONT/DEV/src/store/posStore.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { create } from 'zustand'
|
||||
|
||||
interface PosState {
|
||||
isNullPos: boolean;
|
||||
setNullPos: (isNullPos: boolean) => void;
|
||||
}
|
||||
|
||||
export const usePosStore = create<PosState>()((set) => ({
|
||||
isNullPos: false,
|
||||
setNullPos: (isNullPos: boolean) => set({ isNullPos }),
|
||||
}))
|
||||
78
FRONT/DEV/src/store/themeContext.tsx
Normal file
78
FRONT/DEV/src/store/themeContext.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { ThemeProvider as StyledTheme } from 'styled-components';
|
||||
import { light, dark} from '../const/theme/theme';
|
||||
import { GlobalStyle } from '../const/theme/globalStyles';
|
||||
import { themeNames, ThemName } from '../const/theme/names';
|
||||
|
||||
type ThemeContextType = {
|
||||
theme: ThemName;
|
||||
toggleTheme: () => void;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeContext);
|
||||
if (!context) {
|
||||
throw new Error('useTheme must be used within a ThemeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const getSystemTheme = () =>
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNames[2] : themeNames[1];
|
||||
|
||||
const getInitialTheme = () => {
|
||||
let savedTheme = localStorage.getItem('theme') as ThemName;
|
||||
if (!savedTheme) {
|
||||
savedTheme = themeNames[3]; // Устанавливаем значение по умолчанию
|
||||
localStorage.setItem('theme', savedTheme); // Сохраняем без JSON.stringify
|
||||
}
|
||||
return savedTheme;
|
||||
};
|
||||
|
||||
const [theme, setTheme] = useState<ThemName>(getInitialTheme);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme((prev) => {
|
||||
let newTheme: ThemName;
|
||||
if (prev === themeNames[3]) {
|
||||
newTheme = themeNames[1];
|
||||
} else if (prev === themeNames[1]) {
|
||||
newTheme = themeNames[2];
|
||||
} else {
|
||||
newTheme = themeNames[3];
|
||||
}
|
||||
localStorage.setItem('theme', newTheme);
|
||||
|
||||
return newTheme;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
let currentTheme = localStorage.getItem('theme')
|
||||
const handleChange = () => {
|
||||
if (currentTheme === themeNames[3]) {
|
||||
const newTheme = getSystemTheme();
|
||||
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem('theme', themeNames[3]);
|
||||
}
|
||||
};
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
}, [theme]);
|
||||
|
||||
const currentTheme = theme === themeNames[3] ? getSystemTheme() : theme;
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||
<StyledTheme theme={{ ...currentTheme === themeNames[1] ? light : dark, toggleTheme }}>
|
||||
<GlobalStyle />
|
||||
{children}
|
||||
</StyledTheme>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
50
FRONT/DEV/src/store/toolBarStore.ts
Normal file
50
FRONT/DEV/src/store/toolBarStore.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { toolBarData } from '../const/menus/sideBar/data';
|
||||
import { ToolBar } from '../const/types/types';
|
||||
|
||||
interface MenuState {
|
||||
toolBar: ToolBar;
|
||||
toggleMenu: (menuId: string) => void;
|
||||
initialize: () => void;
|
||||
}
|
||||
|
||||
export const useToolBarStore = create<MenuState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
toolBar: toolBarData,
|
||||
toggleMenu: (menuId) => set((state) => {
|
||||
const updatedToolBar = state.toolBar.toolSpace.map((menu) => {
|
||||
if (menu.id === menuId) {
|
||||
return {
|
||||
...menu,
|
||||
isOpen: !menu.isOpen,
|
||||
isActive: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...menu,
|
||||
isActive: false,
|
||||
isOpen: false
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
toolBar: {
|
||||
...state.toolBar,
|
||||
current: menuId,
|
||||
toolSpace: updatedToolBar,
|
||||
},
|
||||
};
|
||||
}),
|
||||
initialize: () => {
|
||||
set({ toolBar: toolBarData });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'toolBar-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
}
|
||||
)
|
||||
);
|
||||
26
FRONT/DEV/src/store/workSpaceStore.ts
Normal file
26
FRONT/DEV/src/store/workSpaceStore.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { create } from 'zustand'
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
import { workSpaceNames } from '../const/workspace/names';
|
||||
|
||||
interface WorkSpaceState {
|
||||
workModule: string;
|
||||
setWorkModule: (newModule: string) => void;
|
||||
initialize: () => void;
|
||||
}
|
||||
|
||||
export const useWorkSpaceStore = create<WorkSpaceState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
workModule: workSpaceNames[1],
|
||||
setWorkModule: (newModule: string) => set({ workModule: newModule }),
|
||||
initialize: () => {
|
||||
set({ workModule: workSpaceNames[1]});
|
||||
},
|
||||
}),
|
||||
|
||||
{
|
||||
name: 'workSpace-storage',
|
||||
storage: createJSONStorage(() => localStorage)
|
||||
}
|
||||
)
|
||||
)
|
||||
20
FRONT/DEV/src/utils/checkAdmin.ts
Normal file
20
FRONT/DEV/src/utils/checkAdmin.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { useIpStore } from "../store";
|
||||
import { useAdminStore } from "../store/adminStore";
|
||||
import { useEffect } from "react";
|
||||
import { NetworkService } from "../api/request";
|
||||
|
||||
export const useCheckAdmin = () => {
|
||||
const { ipAddress } = useIpStore()
|
||||
const { setAdmin } = useAdminStore()
|
||||
|
||||
return async () => {
|
||||
const response = await NetworkService.whoami(ipAddress);
|
||||
console.log(response.data);
|
||||
if(response.status == 401){
|
||||
setAdmin(false)
|
||||
}
|
||||
else{
|
||||
setAdmin(response.data.user.is_admin);
|
||||
}
|
||||
}
|
||||
}
|
||||
104
FRONT/DEV/src/utils/nodeController/autosavePosition.ts
Normal file
104
FRONT/DEV/src/utils/nodeController/autosavePosition.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
export const processNodeChanges = (changes: any, mainGroups: any) => {
|
||||
let newData;
|
||||
|
||||
changes.forEach((change: any) => {
|
||||
if (change.type === 'position' && change.position) {
|
||||
const nodeId = change.id;
|
||||
|
||||
if (nodeId === 'g0') {
|
||||
newData = {
|
||||
container_id: mainGroups[0].id,
|
||||
container_x: change.position.x,
|
||||
container_y: change.position.y,
|
||||
isOpen: true
|
||||
};
|
||||
} else if (nodeId === 'g1') {
|
||||
newData = {
|
||||
container_id: mainGroups[1].id,
|
||||
container_x: change.position.x,
|
||||
container_y: change.position.y,
|
||||
isOpen: true
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
const getLastChanges = (changes: any[]) => {
|
||||
const latestChanges: { [key: number]: any } = {};
|
||||
|
||||
changes.forEach((change) => {
|
||||
latestChanges[change.container_id] = change;
|
||||
});
|
||||
|
||||
return Object.values(latestChanges);
|
||||
};
|
||||
|
||||
export const autosavePosition = (changeArray: any[], socket: any,) => {
|
||||
if (changeArray.length > 0) {
|
||||
const lastChanges = getLastChanges(changeArray);
|
||||
|
||||
lastChanges.forEach((change) => {
|
||||
if (socket && socket.readyState === WebSocket.OPEN && change) {
|
||||
socket.send(JSON.stringify(change))
|
||||
console.log('JSON отправлен:', JSON.stringify(change));
|
||||
console.log('Отправленные данные:', change);
|
||||
} else {
|
||||
console.log('WebSocket не открыт или нет данных для отправки');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const resetPosition = (socket: any, setNullPos: (value: boolean) => void, nodes: any[], setNodes: (nodes: any[]) => void) => {
|
||||
const nullPositions = [
|
||||
{
|
||||
container_id: 1,
|
||||
container_x: 0,
|
||||
container_y: 0,
|
||||
isOpen: true
|
||||
},
|
||||
{
|
||||
container_id: 2,
|
||||
container_x: 500,
|
||||
container_y: 0,
|
||||
isOpen: true
|
||||
},
|
||||
]
|
||||
|
||||
nullPositions.forEach(pos => {
|
||||
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify(pos))
|
||||
console.log('JSON отправлен:', JSON.stringify(pos))
|
||||
console.log('Отправленные данные:', pos)
|
||||
} else {
|
||||
console.log('WebSocket не открыт или нет данных для отправки')
|
||||
}
|
||||
})
|
||||
|
||||
const updatedNodes = nodes.map(node => {
|
||||
|
||||
if(node.id=="g0"){
|
||||
return {
|
||||
...node,
|
||||
position: { x: 0, y: 0 },
|
||||
positionAbsolute: { x: 0, y: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
if(node.id=="g1"){
|
||||
return {
|
||||
...node,
|
||||
position: { x: 500, y: 0 },
|
||||
positionAbsolute: { x: 500, y: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
setNodes(updatedNodes);
|
||||
setNullPos(false);
|
||||
}
|
||||
65
FRONT/DEV/src/utils/nodeController/collapseGroup.ts
Normal file
65
FRONT/DEV/src/utils/nodeController/collapseGroup.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
export const collapseGroup = (sortedGroup: any, nodes: any[]) => {
|
||||
const nodesMap = new Map<string, any>(nodes.map(node => [node.id, structuredClone(node)]));
|
||||
|
||||
Object.entries(sortedGroup).forEach(([groupId, groupData]: any) => {
|
||||
const groupNode = nodesMap.get(groupId);
|
||||
if (!groupNode) return;
|
||||
|
||||
const clicked = groupData.clicked;
|
||||
const padding = 20;
|
||||
|
||||
const higherOrderNodes = groupData.higherOrder.map((childId: string) => {
|
||||
const child = nodesMap.get(childId);
|
||||
if (!child) return null;
|
||||
|
||||
const newHeight = clicked ? 60 : child.data.height;
|
||||
const updatedChild = {
|
||||
...child,
|
||||
data: {
|
||||
...child.data,
|
||||
visible: !clicked,
|
||||
style: { ...child.data.style, height: newHeight }
|
||||
},
|
||||
measured: {
|
||||
...child.measured,
|
||||
height: newHeight + 4,
|
||||
width: child.data.style.width + 4
|
||||
}
|
||||
};
|
||||
nodesMap.set(childId, updatedChild);
|
||||
return updatedChild;
|
||||
}).filter(Boolean);
|
||||
|
||||
groupData.lowerOrder.forEach((childId: string) => {
|
||||
const child = nodesMap.get(childId);
|
||||
if (child) child.hidden = clicked;
|
||||
});
|
||||
|
||||
let currentY = padding;
|
||||
higherOrderNodes.forEach((child: any) => {
|
||||
nodesMap.set(child.id, {
|
||||
...child,
|
||||
position: { ...child.position, y: currentY }
|
||||
});
|
||||
currentY += child.measured.height + padding;
|
||||
});
|
||||
|
||||
const newGroupHeight = clicked
|
||||
? currentY
|
||||
: groupNode.data.height;
|
||||
|
||||
nodesMap.set(groupId, {
|
||||
...groupNode,
|
||||
data: {
|
||||
...groupNode.data,
|
||||
style: { ...groupNode.data.style, height: newGroupHeight }
|
||||
},
|
||||
measured: {
|
||||
...groupNode.measured,
|
||||
height: newGroupHeight + 4
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(nodesMap.values());
|
||||
};
|
||||
37
FRONT/DEV/src/utils/nodeController/groupSort.ts
Normal file
37
FRONT/DEV/src/utils/nodeController/groupSort.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
export const groupSort = (groups: any, nodes: any)=>{
|
||||
|
||||
const result: {
|
||||
[key: string]: {
|
||||
higherOrder: string[];
|
||||
lowerOrder: string[];
|
||||
clicked: boolean
|
||||
};
|
||||
} = {};
|
||||
|
||||
groups.forEach((group: any) => {
|
||||
const groupId = group.id;
|
||||
result[groupId] = {
|
||||
higherOrder: [],
|
||||
lowerOrder: [],
|
||||
clicked: group.click
|
||||
};
|
||||
|
||||
const children = nodes.filter((node: any) => node.id.startsWith(`${groupId}_`));
|
||||
|
||||
children.forEach((child: any) => {
|
||||
const childId = child.id;
|
||||
|
||||
const parts = childId.split('_');
|
||||
if (parts.length === 2) {
|
||||
const higherOrderValue = childId
|
||||
result[groupId].higherOrder.push(higherOrderValue)
|
||||
} else {
|
||||
const lowerOrderValue = childId
|
||||
result[groupId].lowerOrder.push(lowerOrderValue)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
6
FRONT/DEV/src/utils/nodeController/index.ts
Normal file
6
FRONT/DEV/src/utils/nodeController/index.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export { autosavePosition, processNodeChanges , resetPosition} from './autosavePosition';
|
||||
export { initNetworkIntData } from './initNetIntData';
|
||||
export { collapseGroup } from './collapseGroup';
|
||||
export {groupSort} from './groupSort'
|
||||
export { updateWifiData } from './updateWifiData';
|
||||
export {useInitCanvas} from './nodeCreator/inits/initor'
|
||||
60
FRONT/DEV/src/utils/nodeController/initNetIntData.ts
Normal file
60
FRONT/DEV/src/utils/nodeController/initNetIntData.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { ResponseData, Interfaces, MainData, IEdge, NetworkData } from '../../const/types/types'
|
||||
import { ipsInitor } from './nodeCreator/inits/initIpArray';
|
||||
|
||||
export const initNetworkIntData = (mainGroups: ResponseData[]): { interfaces: Interfaces[], edgeData: IEdge[] } => {
|
||||
const edgeData: IEdge[] = [];
|
||||
const interfaces: Interfaces[] = [];
|
||||
|
||||
if (mainGroups) {
|
||||
mainGroups.map((el, indx) => {
|
||||
let nodeData: MainData[] = [];
|
||||
el.interfaces.map((net, index) => {
|
||||
let nodeID = `g${indx}_nw${index}`;
|
||||
let portID = `p-nw${index}`;
|
||||
let targetId = `${portID}_target-right`;
|
||||
let sourceId = `${nodeID}-source-left`;
|
||||
|
||||
if (indx === 0) {
|
||||
const edge: IEdge = {
|
||||
id: `${nodeID}-${portID}`,
|
||||
source: nodeID,
|
||||
target: portID,
|
||||
sourceHandle: sourceId,
|
||||
targetHandle: targetId,
|
||||
type: "default",
|
||||
};
|
||||
edgeData.push(edge);
|
||||
}
|
||||
|
||||
const network: NetworkData = {
|
||||
device: net.name,
|
||||
type: net.type,
|
||||
mac: net.mac_address,
|
||||
connection: net.active_connection,
|
||||
mtu: net.device_mtu,
|
||||
state: net.device_state,
|
||||
strength: net.strength
|
||||
};
|
||||
|
||||
const ipArray = ipsInitor(net);
|
||||
|
||||
let interfaceData: MainData = {
|
||||
networkData: network,
|
||||
ipArrays: ipArray
|
||||
};
|
||||
|
||||
nodeData.push(interfaceData);
|
||||
});
|
||||
|
||||
let j: Interfaces = {
|
||||
container_x: el.container_x,
|
||||
container_y: el.container_y,
|
||||
mainData: nodeData
|
||||
};
|
||||
|
||||
interfaces.push(j);
|
||||
});
|
||||
}
|
||||
|
||||
return { interfaces, edgeData };
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
type StateType = Record<string, string>;
|
||||
|
||||
export const stateMap: StateType = {
|
||||
'0': "Неизвестно",
|
||||
'10': "Не обслуживается",
|
||||
'20': "Недоступно",
|
||||
'30': "Отключено",
|
||||
'40': "Подготовка",
|
||||
'50': "Конфигурация",
|
||||
'60': "Требуется аутентификация",
|
||||
'70': "Настройка IP",
|
||||
'80': "Проверка IP",
|
||||
'90': "Дополнительные настройки",
|
||||
'100': "Подключено",
|
||||
'110': "Отключается",
|
||||
'120': "Ошибка",
|
||||
}
|
||||
|
||||
export const stateColor: StateType = {
|
||||
'0': "#7e7e7e",
|
||||
'10': "#d95454",
|
||||
'20': "#d95454",
|
||||
'30': "#d95454",
|
||||
'40': "",
|
||||
'50': "",
|
||||
'60': "",
|
||||
'70': "",
|
||||
'80': "",
|
||||
'90': "",
|
||||
'100': "#66BB6A",
|
||||
'110': "",
|
||||
'120': "",
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { useState } from "react";
|
||||
import { IEdge } from "../../../../const/types/types";
|
||||
|
||||
export const useInitEdges = () => {
|
||||
const [initedEdges, setEdges] = useState<IEdge[]>([])
|
||||
|
||||
const setInitEdges = (something: IEdge[]) => {
|
||||
setEdges((prevEdges) => {
|
||||
const edges: IEdge[] = []
|
||||
|
||||
something.map(edge=>{
|
||||
edges.push(edge)
|
||||
})
|
||||
|
||||
return [...prevEdges, ...edges]
|
||||
})
|
||||
}
|
||||
return { initedEdges, setInitEdges }
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { ResponseIpConf, ResponseNetwork, IpData } from "../../../../const/types/types"
|
||||
|
||||
interface IpArray {
|
||||
route: boolean,
|
||||
ipData: IpData[]
|
||||
}
|
||||
|
||||
export const ipsInitor = (network: ResponseNetwork) => {
|
||||
const array: IpArray[] = [
|
||||
{ route: true, ipData: [] },
|
||||
{ route: false, ipData: [] },
|
||||
];
|
||||
|
||||
const ipArrayInit = (ip: ResponseIpConf, version: string) => {
|
||||
const isRouted = ip.routes.length > 0
|
||||
|
||||
const newData: IpData = {
|
||||
ipType: ip.method,
|
||||
ipAdress: ip.address,
|
||||
ipGate: ip.gateway,
|
||||
ipDns: ip.dns,
|
||||
ipRoutese: ip.routes,
|
||||
ipVersion: version
|
||||
};
|
||||
|
||||
if (isRouted) {
|
||||
array[0].ipData.push(newData);
|
||||
} else {
|
||||
array[1].ipData.push(newData);
|
||||
}
|
||||
}
|
||||
|
||||
network.ipv4.configurations.forEach((ip: ResponseIpConf) => {
|
||||
ipArrayInit(ip, '4')
|
||||
});
|
||||
|
||||
network.ipv6.configurations.forEach((ip: ResponseIpConf) => {
|
||||
ipArrayInit(ip, '6')
|
||||
});
|
||||
|
||||
const resultArray = array.filter((item) => item.ipData.length > 0);
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
import { useState } from "react";
|
||||
import { NodeColors } from "../../../../components/customeNodes/types/colors";
|
||||
import { stateColor, stateMap } from "../deviceState";
|
||||
import { Interfaces, NetworkNode, IpNode, PortNode, NodeGroup, NetworkInterface } from "../../../../const/types/types"
|
||||
|
||||
export const useInitNodes = () => {
|
||||
const [initedNodes, setNodes] = useState<(NetworkInterface)[]>([]);
|
||||
const yGap = 20
|
||||
|
||||
interface MainHeight {
|
||||
x?: number,
|
||||
y?: number,
|
||||
height?: number;
|
||||
}
|
||||
|
||||
const findMaxHeight = (data: any[])=>{
|
||||
const filtred = data.filter((el)=> el.x ===0 || el.x ===1)
|
||||
if(filtred.length ===0){
|
||||
return 140
|
||||
}
|
||||
const max =filtred.reduce((max, el)=> Math.max(max, el.height), 0)
|
||||
return max
|
||||
|
||||
}
|
||||
|
||||
const setInitNodes = (interfaces: Interfaces[]) => {
|
||||
setNodes(() => {
|
||||
const networkNodes: NetworkNode[] = []
|
||||
const portNodes: PortNode[] = []
|
||||
const ipGroupes: NodeGroup[][] = []
|
||||
const ipNodes: IpNode[][] = []
|
||||
const networkGroups: NodeGroup[] = []
|
||||
|
||||
let width: number, columnCount: MainHeight[] = []
|
||||
let widthArr: number[] = []
|
||||
let heightArr: number[] = []
|
||||
|
||||
interfaces.map((b, indexB) => {
|
||||
const centerX = 20
|
||||
const centerY = 20
|
||||
let currentY = centerY;
|
||||
b.mainData.map((data, networkIndex) => {
|
||||
data.ipArrays.map((el) => {
|
||||
if (el.route === true) {
|
||||
const column = { x: 1 }
|
||||
columnCount.push(column)
|
||||
}
|
||||
else if (el.route === false) {
|
||||
const column = { x: 0 }
|
||||
columnCount.push(column)
|
||||
}
|
||||
})
|
||||
|
||||
if (columnCount.length == 2) {
|
||||
width = (260 * 2) + 45
|
||||
}
|
||||
else {
|
||||
width = 260 + 35
|
||||
}
|
||||
|
||||
let mainHeight: MainHeight[] = []
|
||||
|
||||
data.ipArrays.forEach((ipInfo, groupIndx) => {
|
||||
if(!ipInfo || !ipInfo.ipData || ipInfo.ipData.length ===0){
|
||||
return
|
||||
}
|
||||
let groupes: NodeGroup[] = []
|
||||
let nodeCount = ipInfo.ipData.length
|
||||
let x = 20, y = 150, newX, newY, gap = 0
|
||||
|
||||
let height = (140 + 15) * (nodeCount)
|
||||
|
||||
|
||||
if (ipInfo.route) {
|
||||
newX = width - 260 - 15
|
||||
|
||||
const groupHeight = {
|
||||
x: 1,
|
||||
height: height
|
||||
}
|
||||
mainHeight.push(groupHeight)
|
||||
}
|
||||
else {
|
||||
newX = x
|
||||
const groupHeight = {
|
||||
x: 0,
|
||||
height: height
|
||||
}
|
||||
mainHeight.push(groupHeight)
|
||||
}
|
||||
|
||||
|
||||
const newIpGroupe: NodeGroup = {
|
||||
id: `g${indexB}_nw${networkIndex}_rx${groupIndx}`,
|
||||
position: {
|
||||
x: newX,
|
||||
y: y
|
||||
},
|
||||
data: {
|
||||
style: {
|
||||
width: 260,
|
||||
height: height
|
||||
}
|
||||
},
|
||||
hidden: false,
|
||||
type: "ipgroup",
|
||||
parentId: `g${indexB}_nw${networkIndex}`,
|
||||
extent: 'parent',
|
||||
draggable: false,
|
||||
}
|
||||
|
||||
let startY = 0
|
||||
const ipGap = 15
|
||||
|
||||
|
||||
ipInfo.ipData.map((ip, ipIndex) => {
|
||||
let nodes: IpNode[] = []
|
||||
const y = startY;
|
||||
|
||||
startY += 140 + ipGap;
|
||||
|
||||
|
||||
const newIpNode: IpNode = {
|
||||
id: `g${indexB}_nw${networkIndex}_rx${groupIndx}_ip${ipIndex}`,
|
||||
position: {
|
||||
x: 0,
|
||||
y: y
|
||||
},
|
||||
type: 'ip',
|
||||
data: {
|
||||
ipData: {
|
||||
ipType: ip.ipType,
|
||||
ipAdress: ip.ipAdress,
|
||||
ipGate: ip.ipGate,
|
||||
ipDns: ip.ipDns,
|
||||
ipRoutese: [],
|
||||
ipVersion: ip.ipVersion
|
||||
},
|
||||
interfaceData:{
|
||||
device: data.networkData.device,
|
||||
connection: data.networkData.connection,
|
||||
},
|
||||
route: ipInfo.route,
|
||||
style: {
|
||||
width: 260,
|
||||
height: 140,
|
||||
borderType: "solid",
|
||||
borderColor: NodeColors["1"],
|
||||
},
|
||||
},
|
||||
hidden: false,
|
||||
parentId: `g${indexB}_nw${networkIndex}_rx${groupIndx}`,
|
||||
extent: 'parent',
|
||||
draggable: false,
|
||||
}
|
||||
nodes.push(newIpNode)
|
||||
ipNodes.push(nodes)
|
||||
})
|
||||
groupes.push(newIpGroupe)
|
||||
ipGroupes.push(groupes)
|
||||
})
|
||||
|
||||
const maxHeight = findMaxHeight(mainHeight);
|
||||
// const networkNodeHeight = (maxHeight || 0) + 150;
|
||||
const networkNodeHeight = maxHeight +150
|
||||
|
||||
let groupId: string
|
||||
if (data.networkData.connection === 'unknown') {
|
||||
groupId = 'cn1'
|
||||
}
|
||||
else {
|
||||
groupId = 'cn0'
|
||||
}
|
||||
|
||||
const y = currentY;
|
||||
|
||||
currentY += networkNodeHeight + yGap;
|
||||
|
||||
|
||||
const newNode: NetworkNode = {
|
||||
id: `g${indexB}_nw${networkIndex}`,
|
||||
position: {
|
||||
x: centerX,
|
||||
y: y
|
||||
},
|
||||
type: "network",
|
||||
data: {
|
||||
networkData: {
|
||||
device: data.networkData.device,
|
||||
type: data.networkData.type,
|
||||
mac: data.networkData.mac,
|
||||
mtu: data.networkData.mtu,
|
||||
state: stateMap[data.networkData.state],
|
||||
connection: data.networkData.connection,
|
||||
id: `nw${networkIndex}`,
|
||||
strength: data.networkData.strength
|
||||
},
|
||||
style: {
|
||||
width: width,
|
||||
height: networkNodeHeight as number,
|
||||
borderType: "solid",
|
||||
borderColor: NodeColors["0"],
|
||||
backgroundColor: stateColor[data.networkData.state]
|
||||
},
|
||||
height: networkNodeHeight,
|
||||
visible: true
|
||||
},
|
||||
parentId: `g${indexB}`,
|
||||
extent: 'parent',
|
||||
};
|
||||
|
||||
widthArr.push(width)
|
||||
heightArr.push(networkNodeHeight)
|
||||
networkNodes.push(newNode)
|
||||
columnCount = []
|
||||
|
||||
let portNodeHeight = 60;
|
||||
let absoluteY = (currentY - portNodeHeight + 10) - (networkNodeHeight / 2);
|
||||
if (data.networkData.connection !== 'unknown') {
|
||||
const portNode: PortNode = {
|
||||
id: `p-nw${networkIndex}`,
|
||||
position: {
|
||||
x: -350,
|
||||
y: absoluteY,
|
||||
},
|
||||
type: 'portNode',
|
||||
data: {
|
||||
label: `${data.networkData.connection}`,
|
||||
id: `p-nw${networkIndex}`,
|
||||
style: {
|
||||
width: 260,
|
||||
height: portNodeHeight,
|
||||
borderType: "solid",
|
||||
borderColor: NodeColors["0"],
|
||||
},
|
||||
}
|
||||
}
|
||||
portNodes.push(portNode)
|
||||
}
|
||||
})
|
||||
|
||||
const sumHeight = heightArr.reduce((a, c) => a + c, 0);
|
||||
const maxWidth = Math.max(...widthArr)
|
||||
|
||||
let count = b.mainData.length
|
||||
let groupHeight = sumHeight + (count * 30)
|
||||
let groupWidth = maxWidth + 40
|
||||
|
||||
let label: string;
|
||||
|
||||
if (indexB == 0) {
|
||||
label = 'Активные интерфейсы'
|
||||
}
|
||||
else {
|
||||
label = 'Неактивные интерфейсы'
|
||||
}
|
||||
|
||||
let x = b.container_x
|
||||
if (indexB > 0 && x === 0) {
|
||||
x = x + 500
|
||||
}
|
||||
|
||||
if (b.mainData.length > 0) {
|
||||
const networkGroup: NodeGroup = {
|
||||
id: `g${indexB}`,
|
||||
position: {
|
||||
x: x,
|
||||
y: b.container_y
|
||||
},
|
||||
type: "nodegroup",
|
||||
data: {
|
||||
label: label,
|
||||
id: `g${indexB}`,
|
||||
height: groupHeight,
|
||||
style: {
|
||||
width: groupWidth,
|
||||
height: groupHeight,
|
||||
borderType: "solid",
|
||||
borderColor: NodeColors["0"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
networkGroups.push(networkGroup)
|
||||
}
|
||||
|
||||
|
||||
heightArr = []
|
||||
})
|
||||
|
||||
return [...networkGroups, ...networkNodes, ...ipGroupes.flat(), ...portNodes, ...ipNodes.flat()];
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return { initedNodes, setInitNodes };
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { useInitNodes } from "./initNodes";
|
||||
import { useInitEdges } from "./initEdges";
|
||||
import { IEdge, Interfaces } from "../../../../const/types/types";
|
||||
|
||||
export const useInitCanvas = () => {
|
||||
const { initedNodes, setInitNodes } = useInitNodes();
|
||||
const { initedEdges, setInitEdges } = useInitEdges();
|
||||
|
||||
const handleInit = (edgeData: IEdge[], interfaces: Interfaces[])=>{
|
||||
setInitNodes(interfaces)
|
||||
setInitEdges(edgeData)
|
||||
}
|
||||
|
||||
return { initedNodes, initedEdges, handleInit };
|
||||
};
|
||||
21
FRONT/DEV/src/utils/nodeController/updateWifiData.ts
Normal file
21
FRONT/DEV/src/utils/nodeController/updateWifiData.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export const updateWifiData = (wifiData: any, nodes: any)=>{
|
||||
const wifiName = wifiData.name;
|
||||
|
||||
const updatedNodes = nodes.map((node: any) => {
|
||||
if (node.data.networkData && node.data.networkData.device === wifiName) {
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
networkData: {
|
||||
...node.data.networkData,
|
||||
strength: wifiData.strength,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
|
||||
return updatedNodes
|
||||
}
|
||||
35
FRONT/DEV/src/utils/nodeController/validator.ts
Normal file
35
FRONT/DEV/src/utils/nodeController/validator.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
export const validateIPAddress = (ip: string) => {
|
||||
const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
return ipv4Pattern.test(ip) || ipv6Pattern.test(ip);
|
||||
};
|
||||
|
||||
export const validateSameSubnet = (ip1: string, ip2: string, ip3: string | string[]): boolean => {
|
||||
const extractSubnet = (ip: string): string => {
|
||||
if (typeof ip !== 'string') {
|
||||
throw new TypeError(`Некорректный формат IP-адреса: ${ip}`);
|
||||
}
|
||||
return ip.split('.').slice(0, 3).join('.');
|
||||
};
|
||||
|
||||
const areInSameSubnet = (ip: string, subnet: string): boolean => {
|
||||
return extractSubnet(ip) === subnet;
|
||||
};
|
||||
|
||||
try {
|
||||
const subnet1 = extractSubnet(ip1);
|
||||
const subnet2 = extractSubnet(ip2);
|
||||
|
||||
// Проверяем, что IP и шлюз в одной подсети
|
||||
if (subnet1 !== subnet2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если DNS — массив, проверяем, что хотя бы один DNS в той же подсети
|
||||
const dnsArray = Array.isArray(ip3) ? ip3 : [ip3];
|
||||
return dnsArray.some(dns => areInSameSubnet(dns, subnet1));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue