Версия 0.0.3
This commit is contained in:
parent
413e9c57be
commit
8c7d0609df
189 changed files with 52063 additions and 2769 deletions
|
|
@ -1,10 +1,8 @@
|
|||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { NetworkService } from './src/api/request.ts';
|
||||
import { useIpStore } from './src/store';
|
||||
import { useEffect } from 'react';
|
||||
import { useAdminStore } from './src/store/adminStore.ts';
|
||||
import {useCheckAdmin} from './src/utils/checkAdmin.ts'
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function createWindow() {
|
|||
win.show();
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
win.loadURL('http://localhost:3000');
|
||||
win.loadURL('https://localhost:8001');
|
||||
win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile(path.join(__dirname, 'dist/index.html'));
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@iconify/icons-ic": "^1.2.13",
|
||||
"@iconify/icons-ph": "^1.2.5",
|
||||
"@iconify/react": "^5.2.0",
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"@xyflow/react": "^12.3.5",
|
||||
|
|
|
|||
|
|
@ -82,4 +82,79 @@ export class NetworkService {
|
|||
return error.response;
|
||||
}
|
||||
}
|
||||
|
||||
static async scanUser(ipAddress: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.get('/subnet/devices');
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
|
||||
static async scanWifi(ipAddress: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.get('/subnet/wifi');
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async addNote(ipAddress: string, address:string, note: string,type:string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.post(`/subnet/${type}/${address}/note`, {
|
||||
note: note
|
||||
});
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async editNote(ipAddress: string, address:string, note: string,type:string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.put(`/subnet/${type}/${address}/note`, {
|
||||
note: note
|
||||
});
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async deleteNote(ipAddress: string, address:string, type:string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.delete(`/subnet/${type}/${address}/note`);
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async statusNetwork(ipAddress: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.get(`/subnet/wifi/status`);
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async connectNetwork(ipAddress: string, password:string, address: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.post(`/subnet/wifi/connect`, {
|
||||
ssid: address,
|
||||
password: password,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
static async disconnectNetwork(ipAddress: string, ssid: string): Promise<AxiosResponse> {
|
||||
try {
|
||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||
return await axiosInstance.post(`/subnet/wifi/disconnect`, {
|
||||
ssid: ssid,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return error.response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
import React, {useState} from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { DeviceUser } from "../../../store/useDeviceStore";
|
||||
import {buttonNames} from "../../../const/buttons/names";
|
||||
import {ModalMap} from "../../modalwindows/windowsType";
|
||||
import {IconComponent} from "../../../const/buttons/icons";
|
||||
import {useModalContext} from "../../../store";
|
||||
|
||||
interface DeviceCardProps {
|
||||
device: DeviceUser;
|
||||
status: 'online' | 'offline';
|
||||
}
|
||||
|
||||
const CardWrapper = styled.div<{ status: 'online' | 'offline' }>`
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
`;
|
||||
|
||||
const CardHeader = styled.div<{ status: 'online' | 'offline' }>`
|
||||
background: ${({ status, theme }) =>
|
||||
status === 'online' ? theme.successColor : theme.offlineColor};
|
||||
color: white;
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
|
||||
const CardContent = styled.div<{ isExpanded: boolean }>`
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
padding: 0 16px;
|
||||
transition: all 0.3s ease;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
|
||||
${({ isExpanded }) => isExpanded && css`
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
padding: 16px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const InfoRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Label = styled.span`
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
`;
|
||||
|
||||
const Value = styled.span`
|
||||
color: #333;
|
||||
`;
|
||||
|
||||
|
||||
const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const toggleExpanded = () => setIsExpanded(!isExpanded);
|
||||
const { openModal } = useModalContext();
|
||||
|
||||
const openModalNote = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
openModal(ModalMap['6'], {
|
||||
address: device.mac,
|
||||
currentNote: device.note || ''
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<CardWrapper status={status} onClick={toggleExpanded}>
|
||||
<CardHeader status={status}>
|
||||
<p>
|
||||
MAC: {device.mac}
|
||||
</p>
|
||||
<span onClick={openModalNote}>
|
||||
<IconComponent name={buttonNames[16]} width="28" height="28" setColor={'white'} />
|
||||
</span>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent isExpanded={isExpanded}>
|
||||
<InfoRow>
|
||||
<Label>Имя:</Label>
|
||||
<Value>{device.hostname || device.ip}</Value>
|
||||
</InfoRow>
|
||||
|
||||
<InfoRow>
|
||||
<Label>Производитель:</Label>
|
||||
<Value>{device.vendor || 'Неизвестно'}</Value>
|
||||
</InfoRow>
|
||||
|
||||
<InfoRow>
|
||||
<Label>IP адрес:</Label>
|
||||
<Value>{device.ip}</Value>
|
||||
</InfoRow>
|
||||
|
||||
{device.open_ports?.length > 0 && (
|
||||
<InfoRow>
|
||||
<Label>Открытые порты:</Label>
|
||||
<Value>{device.open_ports.join(', ')}</Value>
|
||||
</InfoRow>
|
||||
)}
|
||||
|
||||
<InfoRow>
|
||||
<Label>Последняя активность:</Label>
|
||||
<Value>{device.last_seen}</Value>
|
||||
</InfoRow>
|
||||
|
||||
<InfoRow>
|
||||
<Label>Заметка:</Label>
|
||||
<Value>{device.note || '-'}</Value>
|
||||
</InfoRow>
|
||||
</CardContent>
|
||||
</CardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceCard;
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import styled from "styled-components";
|
||||
import {buttonNames} from "../../../const/buttons/names";
|
||||
import {IconComponent} from "../../../const/buttons/icons";
|
||||
|
||||
type Option = {
|
||||
value: number,
|
||||
label: string,
|
||||
}
|
||||
|
||||
interface DropdownTypeProps {
|
||||
options: Option[];
|
||||
label?: string;
|
||||
onChange?: (selectedItem: Option) => void;
|
||||
value: Option | null;
|
||||
}
|
||||
|
||||
const DropdownWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right:20px;
|
||||
display: inline-block;
|
||||
background-color: ${({theme}) => theme.background};
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
`
|
||||
const Label = styled.p`
|
||||
color: ${({theme}) => theme.textColor};
|
||||
font-size: 16px;
|
||||
`
|
||||
|
||||
const Dropdown = styled.div`
|
||||
background-color: ${({theme}) => theme.primary};
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
transition: all .3s ease;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
margin-top: 6px;
|
||||
`
|
||||
|
||||
const DropdownHeader = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-width: 200px;
|
||||
`
|
||||
|
||||
const DropdownContent = styled.div<{$isOpen: boolean}>`
|
||||
max-height: ${({$isOpen}) => $isOpen ? '500px' : '0'};
|
||||
opacity: ${({$isOpen}) => $isOpen ? 1 : 0};
|
||||
transition: all .3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const DropdownRow = styled.button`
|
||||
padding: 12px 24px;
|
||||
background-color: transparent;
|
||||
text-align: start;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
width: 100%;
|
||||
color: ${({theme}) => theme.textColor};
|
||||
transition: all .3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({theme}) => theme.background};
|
||||
}
|
||||
`
|
||||
|
||||
const DropdownArrow = styled.div<{ $isOpen: boolean }>`
|
||||
transition: transform 0.3s ease;
|
||||
transform: rotate(${({ $isOpen }) => ($isOpen ? "90deg" : "0deg")});
|
||||
`;
|
||||
|
||||
const DropdownType = ({options, value, onChange, label}: DropdownTypeProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function toggleOpen() {
|
||||
setIsOpen((prev) => !prev);
|
||||
}
|
||||
|
||||
function handleSelect(option: Option) {
|
||||
onChange?.(option);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DropdownWrapper ref={dropdownRef}>
|
||||
<Label>{label}</Label>
|
||||
<Dropdown>
|
||||
<DropdownHeader onClick={toggleOpen}>
|
||||
<p>{value?.label || 'Select an option'}</p>
|
||||
<DropdownArrow $isOpen={isOpen}>
|
||||
<IconComponent name={buttonNames[6]} />
|
||||
</DropdownArrow>
|
||||
</DropdownHeader>
|
||||
<DropdownContent $isOpen={isOpen}>
|
||||
{options.map((option, i) => (
|
||||
<DropdownRow key={i} onClick={() => handleSelect(option)}>
|
||||
{option.label}
|
||||
</DropdownRow>
|
||||
))}
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
</DropdownWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownType;
|
||||
|
|
@ -9,15 +9,15 @@ const Button = styled.button.withConfig({
|
|||
border: none;
|
||||
padding: ${(props) =>
|
||||
props.size === "small"
|
||||
? "4px 8px"
|
||||
? "10px 28px"
|
||||
: props.size === "big"
|
||||
? "10px 20px"
|
||||
: "6px 10px"}; // По умолчанию
|
||||
: "12px 20px"}; // По умолчанию
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: ${(props) =>
|
||||
props.size === "small"
|
||||
? "12px"
|
||||
? "14px"
|
||||
: props.size === "big"
|
||||
? "16px"
|
||||
: "14px"}; // По умолчанию
|
||||
|
|
@ -30,7 +30,7 @@ const Button = styled.button.withConfig({
|
|||
? 'row-reverse'
|
||||
: 'row'};
|
||||
gap: 10px;
|
||||
|
||||
transition: all .3s ease;
|
||||
&:hover{
|
||||
background-color: ${(props) => props.$hoverсolor};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ type Props = {
|
|||
placeholderText: string;
|
||||
id: string;
|
||||
value?: string;
|
||||
onChange?: (identifier: string, value: string) => void;
|
||||
onChange?: (value: string) => void;
|
||||
onFileChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
type?: string;
|
||||
multiple?: boolean;
|
||||
|
|
@ -33,7 +33,7 @@ const StyledInput = styled.input`
|
|||
border: 1px solid #adadad;
|
||||
}
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
color: ${props=>props.theme.textColor}
|
||||
`;
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ const Input = React.forwardRef<HTMLInputElement, Props>(({ placeholderText, id,
|
|||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = e.target;
|
||||
if (onChange) {
|
||||
onChange(id, value);
|
||||
onChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@ import React, { useEffect, useRef } from 'react';
|
|||
|
||||
interface SignalSVGProps {
|
||||
signal: number; // Принимаем signal как пропс
|
||||
color: string;
|
||||
size: string;
|
||||
}
|
||||
|
||||
const SignalSVG: React.FC<SignalSVGProps> = ({ signal }) => {
|
||||
const SignalSVG: React.FC<SignalSVGProps> = ({ signal, color, size }) => {
|
||||
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',
|
||||
'--st1-color': signal >= 1 ? color : 'black',
|
||||
'--st2-color': signal >= 2 ? color : 'black',
|
||||
'--st3-color': signal >= 3 ? color : 'black',
|
||||
'--st4-color': signal >= 4 ? color : 'black',
|
||||
'--st5-color': signal === 5 ? color : 'black',
|
||||
};
|
||||
|
||||
// Применяем стили к SVG
|
||||
|
|
@ -35,8 +37,8 @@ const SignalSVG: React.FC<SignalSVGProps> = ({ signal }) => {
|
|||
y="0px"
|
||||
viewBox="0 0 296 235"
|
||||
xmlSpace="preserve"
|
||||
width="20px" // Фиксированная ширина
|
||||
height="20px"
|
||||
width={size} // Фиксированная ширина
|
||||
height={size}
|
||||
>
|
||||
<style type="text/css">
|
||||
{`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import React from 'react';
|
||||
import { DeviceUser } from '../../../store/useDeviceStore';
|
||||
|
||||
interface TableScannerProps {
|
||||
devices: DeviceUser[];
|
||||
}
|
||||
import styled from 'styled-components';
|
||||
import {IconComponent} from "../../../const/buttons/icons";
|
||||
import {buttonNames} from "../../../const/buttons/names";
|
||||
import {ModalMap} from "../../modalwindows/windowsType";
|
||||
import {useModalContext} from "../../../store";
|
||||
|
||||
const TableContainer = styled.div`
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
background: ${({ theme }) => theme.primary};
|
||||
margin: 24px 0;
|
||||
`;
|
||||
|
||||
const StyledTable = styled.table`
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
`;
|
||||
|
||||
const TableHeaderCell = styled.th<{ sortable?: boolean }>`
|
||||
padding: 16px 24px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
background: ${({ theme }) => theme.primary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||
transition: all 0.2s ease;
|
||||
`;
|
||||
|
||||
const TableRow = styled.tr<{ clickable?: boolean }>`
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.background};
|
||||
}
|
||||
`;
|
||||
|
||||
const TableCell = styled.td`
|
||||
padding: 16px 24px;
|
||||
font-size: 14px;
|
||||
color: ${({ theme }) => theme.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.borderColor};
|
||||
transition: all 0.2s ease;
|
||||
`;
|
||||
|
||||
const StatusBadge = styled.span<{ status:string }>`
|
||||
width: 10px;
|
||||
height: 30px;
|
||||
background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.offlineColor)};
|
||||
display: inline-block;
|
||||
border-radius:4px;
|
||||
`
|
||||
|
||||
const TableScanner = ({devices}:TableScannerProps) => {
|
||||
const { openModal } = useModalContext();
|
||||
|
||||
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
||||
e.stopPropagation();
|
||||
openModal(ModalMap['6'], {
|
||||
address: device.mac,
|
||||
currentNote: device.note || ''
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
<TableHeaderCell>Имя</TableHeaderCell>
|
||||
<TableHeaderCell>Производитель</TableHeaderCell>
|
||||
<TableHeaderCell>IP адрес</TableHeaderCell>
|
||||
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
||||
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
||||
<TableHeaderCell>Заметка</TableHeaderCell>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((item, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
||||
<TableCell>{item.hostname || item.ip}</TableCell>
|
||||
<TableCell>{item.vendor}</TableCell>
|
||||
<TableCell>{item.ip}</TableCell>
|
||||
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
||||
<TableCell>{item.last_seen}</TableCell>
|
||||
<TableCell>{item.note || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<span onClick={(e)=>openModalNote(e, item)}>
|
||||
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableScanner;
|
||||
|
|
@ -184,7 +184,7 @@ export const IpNode = ({ data }: CustomNodeProps) => {
|
|||
placeholderText={data.ipData.ipAdress}
|
||||
id="newIp"
|
||||
value={ipData.newIp}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={(value) => handleChange('newIp', value)}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||
|
|
@ -203,7 +203,7 @@ export const IpNode = ({ data }: CustomNodeProps) => {
|
|||
placeholderText={data.ipData.ipGate}
|
||||
id="newGate"
|
||||
value={ipData.newGate}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={(value) => handleChange('newGate', value)}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||
|
|
@ -221,7 +221,7 @@ export const IpNode = ({ data }: CustomNodeProps) => {
|
|||
placeholderText={data.ipData.ipDns.join(', ')}
|
||||
id="newDns"
|
||||
value={ipData.newDns.join(', ')}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={( value) => handleChange('newDns', value)}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
style={{fontSize: '12px', padding: '4px 10px'}}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export const NetWorkNode = ({ data }: CustomNodeProps) => {
|
|||
|
||||
{data.networkData.type == 'WIFI' && data.networkData.state === "Подключено" ? (
|
||||
<Status>
|
||||
<SignalSVG signal={signal} />
|
||||
<SignalSVG signal={signal} color={'white'} size={'20px'} />
|
||||
</Status>
|
||||
) : (
|
||||
<div></div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const Network = styled.div<Props>`
|
|||
flex-direction: column;
|
||||
gap: 10px;
|
||||
visibility: visible;
|
||||
|
||||
overflow: hidden;
|
||||
p{
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -29,8 +29,6 @@ export const TitleContainer = styled.div<Props>`
|
|||
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'};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
// components/modalContainers/NoteDevice.tsx
|
||||
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||
import { MainButton } from "../../UI/buttons/mainButton";
|
||||
import Input from "../../UI/inputs/input";
|
||||
import { useState } from "react";
|
||||
import {NetworkService} from "../../../api/request";
|
||||
import useNetworkStore from "../../../store/useDeviceStore";
|
||||
import RouterLoader from "../../UI/routerLoader";
|
||||
import {useTheme} from "styled-components";
|
||||
import {useIpStore} from "../../../store";
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const ConnectNetwork = ({address, onClose}: Props) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const theme = useTheme();
|
||||
const { ipAddress } = useIpStore();
|
||||
const [error, setError] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError('')
|
||||
const res = await NetworkService.connectNetwork(ipAddress, password, address);
|
||||
if(res.status === 200) {
|
||||
onClose()
|
||||
}else{
|
||||
setError(res.data.detail)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error connection:', error);
|
||||
setError('Ошибка подключения к сети')
|
||||
}finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<ModalContainer style={{ height: '230px' }}>
|
||||
<ModalTitle>
|
||||
<h3>Подключение к сети</h3>
|
||||
</ModalTitle>
|
||||
|
||||
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<Input
|
||||
type="password"
|
||||
placeholderText='Введите пароль...'
|
||||
value={password}
|
||||
id='deviceNote'
|
||||
onChange={(value) => setPassword(value)}
|
||||
/>
|
||||
<p style={{ color: 'red', marginLeft: '10px', fontSize: '14px', textAlign: 'right' }}>
|
||||
{error}
|
||||
</p>
|
||||
</ModalContent>
|
||||
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title="Отмена"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
color={theme.successColor}
|
||||
hoverColor={theme.successColor}
|
||||
title='Подключиться'
|
||||
onClick={handleSave}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
|
||||
{isLoading && <RouterLoader loaderText='Попытка подключения к сети'/>}
|
||||
</ModalContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// components/modalContainers/NoteDevice.tsx
|
||||
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||
import { MainButton } from "../../UI/buttons/mainButton";
|
||||
import Input from "../../UI/inputs/input";
|
||||
import { useState } from "react";
|
||||
import {useIpStore} from "../../../store";
|
||||
import {NetworkService} from "../../../api/request";
|
||||
import useNetworkStore from "../../../store/useDeviceStore";
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
onClose: () => void;
|
||||
currentNote?: string;
|
||||
type: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const NoteDevice = ({ address, onClose, currentNote, type}: Props) => {
|
||||
const [note, setNote] = useState(currentNote || '');
|
||||
const { ipAddress } = useIpStore();
|
||||
const { devices, setDevices } = useNetworkStore();
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
if (!currentNote && note.trim() !== '') {
|
||||
await NetworkService.addNote(ipAddress, address, note, type);
|
||||
}else if (currentNote && note.trim() !== '' && note !== currentNote) {
|
||||
await NetworkService.editNote(ipAddress, address, note, type);
|
||||
}else if (currentNote && note.trim() === '') {
|
||||
await NetworkService.deleteNote(ipAddress, address, type);
|
||||
}else {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedDevices = devices.map(device =>
|
||||
device.mac === address
|
||||
? { ...device, note: note.trim() }
|
||||
: device
|
||||
);
|
||||
setDevices(updatedDevices);
|
||||
onClose();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving note:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalContainer style={{ height: '250px' }}>
|
||||
<ModalTitle>
|
||||
<h3>{currentNote ? 'Редактирование заметки' : 'Добавление заметки'}</h3>
|
||||
</ModalTitle>
|
||||
|
||||
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholderText='Введите заметку...'
|
||||
value={note}
|
||||
id='deviceNote'
|
||||
onChange={(value) => setNote(value)}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title="Отмена"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title='Сохранить'
|
||||
onClick={handleSave}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
</ModalContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// components/modalContainers/NoteDevice.tsx
|
||||
import { ModalContainer, ModalTitle, ModalContent, ButtonContainer } from "../style";
|
||||
import { MainButton } from "../../UI/buttons/mainButton";
|
||||
import Input from "../../UI/inputs/input";
|
||||
import { useState } from "react";
|
||||
import {NetworkService} from "../../../api/request";
|
||||
import useNetworkStore from "../../../store/useDeviceStore";
|
||||
import {useIpStore} from "../../../store";
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
onClose: () => void;
|
||||
currentNote?: string;
|
||||
type: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const NoteWifi = ({ address, onClose, currentNote, type}: Props) => {
|
||||
const [note, setNote] = useState(currentNote || '');
|
||||
const { ipAddress } = useIpStore();
|
||||
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
if (!currentNote && note.trim() !== '') {
|
||||
await NetworkService.addNote(ipAddress, address, note, type);
|
||||
}else if (currentNote && note.trim() !== '' && note !== currentNote) {
|
||||
await NetworkService.editNote(ipAddress, address, note, type);
|
||||
}else if (currentNote && note.trim() === '') {
|
||||
await NetworkService.deleteNote(ipAddress, address, type);
|
||||
}else {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
const updatedNetworks = wifiNetworks.map(network =>
|
||||
network.ssid === address
|
||||
? { ...network, note: note.trim() }
|
||||
: { ...network }
|
||||
);
|
||||
setWifiNetworks(updatedNetworks);
|
||||
onClose();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving note:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<ModalContainer style={{ height: '220px' }}>
|
||||
<ModalTitle>
|
||||
<h3>{currentNote ? 'Редактирование заметки' : 'Добавление заметки'}</h3>
|
||||
</ModalTitle>
|
||||
|
||||
<ModalContent style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholderText='Введите заметку...'
|
||||
value={note}
|
||||
id='deviceNote'
|
||||
onChange={(value) => setNote(value)}
|
||||
/>
|
||||
</ModalContent>
|
||||
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title="Отмена"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title='Сохранить'
|
||||
onClick={handleSave}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
</ModalContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -39,7 +39,7 @@ export const AddDevice = ({ onSave, onClose }: Props) => {
|
|||
placeholderText='Введите адрес узла...'
|
||||
id="newIp"
|
||||
value={ipAddress}
|
||||
onChange={(id, value) => setIpAddress(value)}
|
||||
onChange={(value) => setIpAddress(value)}
|
||||
/>
|
||||
{error && (
|
||||
<p style={{ color: 'red', marginTop: '5px' }}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style"
|
||||
import { useModalContext } from "../../../store"
|
||||
import { useIpStore } from "../../../store"
|
||||
import { MainButton } from "../../UI/buttons/mainButton"
|
||||
import {useState} from "react";
|
||||
import {NetworkService} from "../../../api/request";
|
||||
import RouterLoader from "../../UI/routerLoader";
|
||||
import {useTheme} from "styled-components";
|
||||
|
||||
type Props={
|
||||
address: string,
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const DisconnectConfirm = ({address, onClose}: Props)=>{
|
||||
const { ipAddress } = useIpStore();
|
||||
const theme = useTheme()
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await NetworkService.disconnectNetwork(ipAddress, address);
|
||||
if(res.status === 200) {
|
||||
onClose()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error connection:', error);
|
||||
}finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return(
|
||||
<ModalContainer>
|
||||
<ModalTitle>
|
||||
<h3>Отключение от Wi-Fi сети</h3>
|
||||
</ModalTitle>
|
||||
|
||||
<ModalContent>
|
||||
<ModalText>
|
||||
<h3>Подтверждение отключения</h3>
|
||||
<p>Вы собираетесь отключиться от сети "{address}". Все активные подключения будут прерваны.</p>
|
||||
</ModalText>
|
||||
</ModalContent>
|
||||
|
||||
<ButtonContainer>
|
||||
<MainButton title="Отменить" onClick={onClose} size="small"/>
|
||||
<MainButton title="Отключиться" color={theme.errorColor} hoverColor={theme.errorColor} onClick={handleDisconnect} size="small"/>
|
||||
</ButtonContainer>
|
||||
{isLoading && <RouterLoader loaderText='Отключение от сети'/>}
|
||||
</ModalContainer>
|
||||
)
|
||||
}
|
||||
|
|
@ -110,15 +110,17 @@ export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => {
|
|||
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 });
|
||||
if(ipAddress!=='localhost'){
|
||||
setIpAddress(ipData.newIp)
|
||||
|
||||
const updatedDevices = devices.map(device =>
|
||||
device.address === ipData.currentIp
|
||||
? { ...device, address: ipData.newIp }
|
||||
: device
|
||||
);
|
||||
useDevicesStore.setState({ devices: updatedDevices });
|
||||
}
|
||||
|
||||
onClose()
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export const RightsConfirm = ({ onClose }: Props) => {
|
|||
placeholderText='Введите пароль администратора...'
|
||||
id="password"
|
||||
value={user.password}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={(value) => handleChange('password', value)}
|
||||
/>
|
||||
|
||||
{error && (
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalCon
|
|||
import { ModalMap } from "./windowsType";
|
||||
import Loader from "../UI/loader";
|
||||
import { ModalWindow } from "./style";
|
||||
import {NoteDevice} from "./modalContainers/NoteDevice";
|
||||
import { NoteWifi } from "./modalContainers/NoteWifi";
|
||||
import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
|
||||
import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean,
|
||||
|
|
@ -35,7 +39,34 @@ export default function WindowComponent({ isOpen, type, data }: Props) {
|
|||
message={data.message}
|
||||
/>
|
||||
}
|
||||
|
||||
{type === ModalMap['6'] && (
|
||||
<NoteDevice
|
||||
type={'devices'}
|
||||
address={data.address}
|
||||
currentNote={data.currentNote}
|
||||
onClose={closeLoader}
|
||||
/>
|
||||
)}
|
||||
{type === ModalMap['7'] && (
|
||||
<NoteWifi
|
||||
type={'wifi'}
|
||||
address={data.address}
|
||||
currentNote={data.currentNote}
|
||||
onClose={closeLoader}
|
||||
/>
|
||||
)}
|
||||
{type === ModalMap['8'] && (
|
||||
<ConnectNetwork
|
||||
address={data.address}
|
||||
onClose={closeLoader}
|
||||
/>
|
||||
)}
|
||||
{type === ModalMap['9'] && (
|
||||
<DisconnectConfirm
|
||||
address={data.address}
|
||||
onClose={closeLoader}
|
||||
/>
|
||||
)}
|
||||
{type === ModalMap['4'] && (
|
||||
<AddDevice onSave={data?.onSave} onClose={closeLoader} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export const ModalContainer = styled.div<{height?: string}>`
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
height: ${(props) => props.height ? props.height : '200px'};
|
||||
gap: 15px;
|
||||
height: ${(props) => props.height ? props.height : '230px'};
|
||||
width: 520px;
|
||||
`
|
||||
|
||||
|
|
@ -66,5 +66,5 @@ export const ButtonContainer = styled.div`
|
|||
justify-content: flex-end;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 20px;
|
||||
padding: 10px 20px 20px;
|
||||
`
|
||||
|
|
@ -5,5 +5,9 @@ export const ModalMap: ModalType = {
|
|||
'2': 'loader',
|
||||
'3': 'connectionError',
|
||||
'4': 'addDevice',
|
||||
'5': 'sudo'
|
||||
'5': 'sudo',
|
||||
'6': 'noteDevice',
|
||||
'7': 'noteWifi',
|
||||
'8': 'connectNetwork',
|
||||
'9': 'disconnectConfirm',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ export const CreateUser = () => {
|
|||
if (result.status === 200) { window.location.reload() }
|
||||
} catch (err) {
|
||||
console.error('Error:', err);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +145,7 @@ export const CreateUser = () => {
|
|||
placeholderText='Введите пароль...'
|
||||
id="password"
|
||||
value={user.password}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={(value) => handleChange('password', value)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
|
|
@ -154,7 +153,7 @@ export const CreateUser = () => {
|
|||
placeholderText='Повторите пароль...'
|
||||
id="repeatPassword"
|
||||
value={repeatPassword}
|
||||
onChange={(id, value) => handleChange(id, value)}
|
||||
onChange={(value) => handleChange('repeatPassword', value)}
|
||||
style={{ marginTop: '10px' }}
|
||||
/>
|
||||
{!passwordsMatch && (
|
||||
|
|
|
|||
|
|
@ -1,82 +1,82 @@
|
|||
import styled from "styled-components"
|
||||
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
import styled from "styled-components"
|
||||
import { MainButton } from "../../../UI/buttons/mainButton"
|
||||
import { useWorkSpaceStore } from "../../../../store"
|
||||
import { workSpaceNames } from "../../../../const/workspace/names"
|
||||
import { buttonNames } from "../../../../const/buttons/names"
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${(props => props.theme.background)};
|
||||
background-image: url('./assets/images/back1.svg');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${(props => props.theme.background)};
|
||||
background-image: url('./assets/images/back1.svg');
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 52px;
|
||||
margin-bottom: 100px;
|
||||
`
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 52px;
|
||||
margin-bottom: 100px;
|
||||
`
|
||||
|
||||
const ButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 75px;
|
||||
flex-direction: column;
|
||||
`
|
||||
const ButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
gap: 75px;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
`
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
|
||||
h1{
|
||||
font-size: 52px;
|
||||
}
|
||||
`
|
||||
|
||||
h1{
|
||||
font-size: 52px;
|
||||
}
|
||||
`
|
||||
export const HelloWindow = () => {
|
||||
|
||||
export const HelloWindow = () => {
|
||||
const { setWorkModule } = useWorkSpaceStore()
|
||||
|
||||
const { setWorkModule } = useWorkSpaceStore()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Container className="unselectable">
|
||||
<Title>
|
||||
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
|
||||
</Title>
|
||||
return (
|
||||
<Wrapper>
|
||||
<Container className="unselectable">
|
||||
<Title>
|
||||
<img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/>
|
||||
</Title>
|
||||
|
||||
|
||||
<ButtonWrapper>
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
title="Настройки сети"
|
||||
icon={buttonNames[3]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
onClick={() => setWorkModule(workSpaceNames[3])}
|
||||
/>
|
||||
<MainButton
|
||||
title="О программе"
|
||||
icon={buttonNames[4]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
onClick={()=>setWorkModule(workSpaceNames[4])}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
</ButtonWrapper>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
<ButtonWrapper>
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
title="Настройки сети"
|
||||
icon={buttonNames[3]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
onClick={() => setWorkModule(workSpaceNames[3])}
|
||||
/>
|
||||
<MainButton
|
||||
title="О программе"
|
||||
icon={buttonNames[4]}
|
||||
iconSize={{ width: '32', height: '32' }}
|
||||
onClick={()=>setWorkModule(workSpaceNames[4])}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
</ButtonWrapper>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
||||
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||
|
||||
const ScanBtn = styled.button`
|
||||
position: relative;
|
||||
padding: 14px 22px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
transition: .4s ease;
|
||||
&:hover{
|
||||
transform: scale(1.03);
|
||||
}
|
||||
`
|
||||
|
||||
interface ListUsersProps {
|
||||
loading: boolean;
|
||||
handleScan: ()=>void;
|
||||
devices: DeviceUser[];
|
||||
scanInitiated: boolean;
|
||||
}
|
||||
|
||||
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
||||
const activeDevices = devices.filter(device => device.status === 'online');
|
||||
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы подсети'}
|
||||
</ScanBtn>
|
||||
|
||||
{devices.length > 0 ? (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
width: '70%',
|
||||
gap: '20px',
|
||||
}}>
|
||||
{/* Колонка активных устройств */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<h3 style={{ color: '#4CAF50', marginBottom:'12px' }}>Активные устройства ({activeDevices.length})</h3>
|
||||
{activeDevices.map((device, index) => (
|
||||
<DeviceCard
|
||||
key={index}
|
||||
status="online"
|
||||
device={device}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Колонка неактивных устройств */}
|
||||
<div style={{ flex: 1 }}>
|
||||
<h3 style={{ color: '#ababab', marginBottom:'12px' }}>Неактивные устройства ({inactiveDevices.length})</h3>
|
||||
{inactiveDevices.map((device, index) => (
|
||||
<DeviceCard
|
||||
key={index}
|
||||
status="offline"
|
||||
device={device}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : scanInitiated ? (
|
||||
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListUsers;
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import DeviceCard from "../../../../../UI/Dropdown/DeviceCard";
|
||||
import {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||
import TableScanner from "../../../../../UI/tables/TableScanner";
|
||||
|
||||
const ScanBtn = styled.button`
|
||||
position: relative;
|
||||
padding: 14px 22px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
transition: .4s ease;
|
||||
&:hover{
|
||||
transform: scale(1.03);
|
||||
}
|
||||
`
|
||||
|
||||
interface ListUsersProps {
|
||||
loading: boolean;
|
||||
handleScan: ()=>void;
|
||||
devices: DeviceUser[];
|
||||
scanInitiated: boolean;
|
||||
}
|
||||
|
||||
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScanBtn onClick={handleScan} disabled={loading}>
|
||||
{devices.length > 0 ? 'Обновить список' : 'Сканировать узлы подсети'}
|
||||
</ScanBtn>
|
||||
|
||||
{devices.length > 0 ? (
|
||||
<TableScanner devices={devices} />
|
||||
) : scanInitiated ? (
|
||||
<p style={{ marginTop: '20px' }}>Устройства не найдены</p>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListUsers;
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import { useIpStore } from "../../../../../store";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import { useState } from "react";
|
||||
import { NetworkService } from "../../../../../api/request";
|
||||
import ListUsers from "./modules/ListUsers";
|
||||
import TableUsers from "./modules/TableUsers";
|
||||
import DropdownType from "../../../../UI/Dropdown/DropdownType";
|
||||
import useNetworkStore from "../../../../../store/useDeviceStore";
|
||||
|
||||
type Option ={
|
||||
value: number,
|
||||
label: string,
|
||||
}
|
||||
|
||||
// Анимации
|
||||
const ring1 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||
`;
|
||||
|
||||
const ring2 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||
`;
|
||||
|
||||
const ring3 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||
`;
|
||||
const dotAnimation = keyframes`
|
||||
0% { content: '.'; }
|
||||
33% { content: '..'; }
|
||||
66% { content: '...'; }
|
||||
100% { content: '.'; }
|
||||
`;
|
||||
const pulse = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); }
|
||||
100% { transform: translate(-50%, -50%) scale(1); }
|
||||
`;
|
||||
|
||||
// Стили
|
||||
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content:${({scanInitiated, loading})=>(scanInitiated && !loading ? 'start' : 'center')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${(props) => props.theme.background};
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
gap: 24px;
|
||||
transition: justify-content .4s ease;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
`;
|
||||
const TitleWrapper = styled.h2`
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32px;
|
||||
width: max-content;
|
||||
`
|
||||
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
transition: .4s ease;
|
||||
transform: ${({scanInitiated,loading})=>(scanInitiated && !loading ? 'scale(0)' : 'scale(1)')};
|
||||
span {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
background: ${(props) => props.theme.accent};
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
&.center {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||
}
|
||||
&.ring1 {
|
||||
opacity: 0.12;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
|
||||
}
|
||||
|
||||
&.ring2 {
|
||||
opacity: 0.08;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||
}
|
||||
|
||||
&.ring3 {
|
||||
opacity: 0.04;
|
||||
width: 550px;
|
||||
height: 550px;
|
||||
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingText = styled.div`
|
||||
position: relative;
|
||||
padding: 14px 22px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
const ScanningText = styled.span`
|
||||
&::after {
|
||||
content: '.';
|
||||
animation: ${dotAnimation} 1.5s infinite;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export default function UserScanner() {
|
||||
const { ipAddress } = useIpStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { devices, setDevices } = useNetworkStore();
|
||||
const [scanInitiated, setScanInitiated] = useState(devices.length > 0);
|
||||
|
||||
// Сканирование
|
||||
const handleScan = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setScanInitiated(true);
|
||||
const res = await NetworkService.scanUser(ipAddress);
|
||||
console.log(res);
|
||||
setDevices(res.data.devices || []);
|
||||
} catch (err) {
|
||||
console.error("Ошибка сканирования сети:", err);
|
||||
setDevices([])
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Способ отображения
|
||||
const viewOptions: Option[] = [
|
||||
{ value: 1, label: 'Таблица' },
|
||||
{ value: 2, label: 'Список' },
|
||||
];
|
||||
const [selectedView, setSelectedView] = useState<Option>(viewOptions[0]);
|
||||
const handleViewChange = (option: Option) => {
|
||||
setSelectedView(option);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||
{(!scanInitiated || loading) && (<TitleWrapper>Сканирование узлов в подсети</TitleWrapper>)}
|
||||
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||
<span className="center"></span>
|
||||
<span className="ring1"></span>
|
||||
<span className="ring2"></span>
|
||||
<span className="ring3"></span>
|
||||
</UserLocate>
|
||||
|
||||
{scanInitiated && !loading && (
|
||||
<DropdownType options={viewOptions} value={selectedView} onChange={handleViewChange} label="Способ отображения" />
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<LoadingText>
|
||||
Сканирование узлов в подсети<ScanningText />
|
||||
</LoadingText>
|
||||
) : selectedView.value == 1 ? (
|
||||
<TableUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
||||
):(
|
||||
<ListUsers loading={loading} handleScan={handleScan} devices={devices} scanInitiated={scanInitiated} />
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
import styled, { keyframes } from "styled-components";
|
||||
import React, { useState } from "react";
|
||||
import { NetworkService } from "../../../../../api/request";
|
||||
import WifiDot, {WifiNetworkScanner} from "./modules/WifiDot";
|
||||
import ModalWifiScaner from "./modules/ModalWifiScaner";
|
||||
import useNetworkStore from "../../../../../store/useDeviceStore";
|
||||
import {useIpStore} from "../../../../../store";
|
||||
|
||||
// Анимации
|
||||
const ring1 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||
`;
|
||||
const ring2 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||
`;
|
||||
const ring3 = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||
`;
|
||||
const dotAnimation = keyframes`
|
||||
0% { content: '.'; }
|
||||
33% { content: '..'; }
|
||||
66% { content: '...'; }
|
||||
100% { content: '.'; }
|
||||
`;
|
||||
const pulse = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); }
|
||||
50% { transform: translate(-50%, -50%) scale(1.1); }
|
||||
100% { transform: translate(-50%, -50%) scale(1); }
|
||||
`;
|
||||
|
||||
// Стили
|
||||
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content:center ;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: ${(props) => props.theme.background};
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
gap: 24px;
|
||||
transition: justify-content .4s ease;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
`;
|
||||
const TitleWrapper = styled.h2`
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32px;
|
||||
`
|
||||
|
||||
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
transition: .4s ease;
|
||||
transform: scale(1);
|
||||
.circel {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
background: ${(props) => props.theme.scannerColor};
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
&.center {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||
}
|
||||
&.ring1 {
|
||||
opacity: 0.12;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite;
|
||||
}
|
||||
|
||||
&.ring2 {
|
||||
opacity: 0.08;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||
}
|
||||
|
||||
&.ring3 {
|
||||
opacity: 0.04;
|
||||
width: 550px;
|
||||
height: 550px;
|
||||
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
const LoadingText = styled.div`
|
||||
position: relative;
|
||||
padding: 14px 22px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
`;
|
||||
const ScanningText = styled.span`
|
||||
&::after {
|
||||
content: '.';
|
||||
animation: ${dotAnimation} 1.5s infinite;
|
||||
}
|
||||
`;
|
||||
const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
||||
position: relative;
|
||||
padding: 14px 22px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
transition: transform .4s ease;
|
||||
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")};
|
||||
&:hover{
|
||||
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")} scale(1.03);
|
||||
}
|
||||
`
|
||||
|
||||
export function WifiScanner() {
|
||||
const { ipAddress } = useIpStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [scanInitiated, setScanInitiated] = useState(false);
|
||||
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||
const [currentNetwork, setCurrentNetwork] = useState<WifiNetworkScanner | null>(null);
|
||||
|
||||
const handleScan = async () => {
|
||||
setWifiNetworks([]);
|
||||
setCurrentNetwork(null)
|
||||
setLoading(true);
|
||||
setScanInitiated(true);
|
||||
try {
|
||||
const res = await NetworkService.scanWifi(ipAddress);
|
||||
const networks = res.data.access_points || [];
|
||||
setWifiNetworks(networks);
|
||||
} catch (err) {
|
||||
console.error("Ошибка сканирования сети:", err);
|
||||
setWifiNetworks([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||
<TitleWrapper>Сканирование точек доступа Wi-Fi</TitleWrapper>
|
||||
|
||||
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||
<span className="center circel"></span>
|
||||
<span className="ring1 circel"></span>
|
||||
<span className="ring2 circel"></span>
|
||||
<span className="ring3 circel"></span>
|
||||
{wifiNetworks.map((network) => (
|
||||
<WifiDot
|
||||
key={network.bssid}
|
||||
network={network}
|
||||
onSelect={setCurrentNetwork}
|
||||
/>
|
||||
))}
|
||||
|
||||
{currentNetwork && (
|
||||
<ModalWifiScaner
|
||||
onClose={() => setCurrentNetwork(null)}
|
||||
network={currentNetwork}
|
||||
/>
|
||||
)}
|
||||
</UserLocate>
|
||||
|
||||
{loading ? (
|
||||
<LoadingText>
|
||||
Сканирование точек доступа <ScanningText/>
|
||||
</LoadingText>
|
||||
) : (
|
||||
<ScanBtn onClick={handleScan} disabled={loading} scanInitiated={wifiNetworks.length > 0 }>
|
||||
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать точки доступа'}
|
||||
</ScanBtn>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {WifiNetworkScanner} from "./WifiDot";
|
||||
import {IconComponent} from "../../../../../../const/buttons/icons";
|
||||
import {buttonNames} from "../../../../../../const/buttons/names";
|
||||
import {ModalMap} from "../../../../../modalwindows/windowsType";
|
||||
import {useIpStore, useModalContext} from "../../../../../../store";
|
||||
import {Status} from "../../../../../customeNodes/nodes/networkInterface/networkNode/style";
|
||||
import SignalSVG from "../../../../../UI/signalSvg";
|
||||
import { useTheme } from 'styled-components';
|
||||
import useNetworkStore, {DeviceUser} from "../../../../../../store/useDeviceStore";
|
||||
import {NetworkService} from "../../../../../../api/request";
|
||||
|
||||
const Modal = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: ${(props) => props.theme.secondary};
|
||||
border-radius: 24px;
|
||||
padding: 32px 28px;
|
||||
bottom: -40%;
|
||||
left: 220px;
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
min-width: 300px;
|
||||
&:hover .close-btn{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
`
|
||||
const CloseBtn = styled.div`
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: -20px;
|
||||
cursor: pointer;
|
||||
background-color: ${(props) => props.theme.primary};
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transition: all .4s ease;
|
||||
opacity: 0;
|
||||
visibility: visible;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.hoverBtn};
|
||||
}
|
||||
`
|
||||
const ModalTitle = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
margin-bottom: 32px;
|
||||
h4{
|
||||
font-weight: normal;
|
||||
}
|
||||
.functionalIcons{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
}
|
||||
`
|
||||
const ModalContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
`
|
||||
const ModalRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
h6{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
p{
|
||||
max-width:180px;
|
||||
text-align: right;
|
||||
}
|
||||
`
|
||||
const ModalConnect = styled.button`
|
||||
color: ${(props) => props.theme.primary};
|
||||
padding: 10px 12px;
|
||||
font-size: 18px;
|
||||
background-color: ${(props) => props.theme.accent};
|
||||
border-radius: 12px;
|
||||
margin-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
transition: .4s ease;
|
||||
&:hover{
|
||||
background-color: ${(props) => props.theme.accentHover};
|
||||
}
|
||||
`
|
||||
|
||||
const ModalWifiScaner = ({ network,onClose }: { network: WifiNetworkScanner, onClose: ()=>void }) => {
|
||||
const { openModal } = useModalContext();
|
||||
const { ipAddress } = useIpStore();
|
||||
const theme = useTheme()
|
||||
const [signal, setSignal] = useState(1);
|
||||
const { wifiNetworks } = useNetworkStore();
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getStatus = async () => {
|
||||
try {
|
||||
const response = await NetworkService.statusNetwork(ipAddress);
|
||||
const { status, ssid } = response.data;
|
||||
setIsConnected(
|
||||
!!network?.ssid &&
|
||||
ssid === network.ssid &&
|
||||
status === 'connected'
|
||||
);
|
||||
} catch (error) {
|
||||
setIsConnected(false);
|
||||
}
|
||||
};
|
||||
|
||||
getStatus();
|
||||
}, [network?.ssid, ipAddress]);
|
||||
|
||||
const currentNetwork = wifiNetworks.find(n => n.bssid === network.bssid) || network;
|
||||
const openModalNote = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
openModal(ModalMap['7'], {
|
||||
address: currentNetwork.ssid,
|
||||
currentNote: currentNetwork.note || ''
|
||||
});
|
||||
};
|
||||
|
||||
const openModalNetwork= (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if(isConnected){
|
||||
openModal(ModalMap['9'], {
|
||||
address: currentNetwork.ssid,
|
||||
});
|
||||
}else{
|
||||
openModal(ModalMap['8'], {
|
||||
address: currentNetwork.ssid,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const perc = Number(network.signal);
|
||||
|
||||
switch (true) {
|
||||
case perc >= -50: setSignal(5); break;
|
||||
case perc >= -60: setSignal(4); break;
|
||||
case perc >= -70: setSignal(3); break;
|
||||
case perc >= -80: setSignal(2); break;
|
||||
default: setSignal(1); break;
|
||||
}
|
||||
}, [network.signal])
|
||||
return (
|
||||
<Modal>
|
||||
<CloseBtn className={'close-btn'} onClick={onClose}>
|
||||
<IconComponent name={buttonNames[12]} width="30" height="30" />
|
||||
</CloseBtn>
|
||||
<ModalTitle>
|
||||
<h4>{network.ssid}</h4>
|
||||
<div className={'functionalIcons'}>
|
||||
|
||||
<span onClick={openModalNote} style={{cursor: "pointer"}}>
|
||||
<IconComponent name={buttonNames[16]} width="30" height="30" />
|
||||
</span>
|
||||
<Status>
|
||||
<SignalSVG signal={signal} size={'36px'} color={theme.accent} />
|
||||
</Status>
|
||||
</div>
|
||||
</ModalTitle>
|
||||
<ModalContent>
|
||||
<ModalRow>
|
||||
<h6>BSSID:</h6>
|
||||
<p>{network.bssid}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Производитель:</h6>
|
||||
<p>{network.vendor}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Сигнал:</h6>
|
||||
<p>{network.signal} dBm</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Канал:</h6>
|
||||
<p>{network.channel}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Шифрование:</h6>
|
||||
<p>{network.encryption}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Частота:</h6>
|
||||
<p>{network.frequency}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Скрытая сеть:</h6>
|
||||
<p>{network.hidden ? 'Да' : 'Нет'}</p>
|
||||
</ModalRow>
|
||||
<ModalRow>
|
||||
<h6>Заметка:</h6>
|
||||
<p>{currentNetwork.note ? currentNetwork.note : '-'}</p>
|
||||
</ModalRow>
|
||||
<ModalConnect onClick={openModalNetwork}>{isConnected ? 'Отключиться' : 'Подключиться'}</ModalConnect>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalWifiScaner;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import styled from "styled-components";
|
||||
|
||||
export interface WifiNetworkScanner {
|
||||
ssid: string;
|
||||
bssid: string;
|
||||
channel: number;
|
||||
signal: number;
|
||||
encryption: string;
|
||||
frequency: string;
|
||||
vendor: string;
|
||||
hidden: boolean;
|
||||
note: string;
|
||||
}
|
||||
|
||||
//Точка вайфай
|
||||
const Dot = styled.div<{ top: string; left: string }>`
|
||||
position: absolute;
|
||||
top: ${props => props.top};
|
||||
left: ${props => props.left};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
transform: translate(-50%, -50%);
|
||||
user-select: none;
|
||||
transition: .4s;
|
||||
text-wrap: nowrap;
|
||||
&:hover {
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
}
|
||||
`;
|
||||
|
||||
const WifiDot = ({ network, onSelect }: { network: WifiNetworkScanner, onSelect: (n: WifiNetworkScanner) => void }) => {
|
||||
const getPositionInRing = (bssid: string, ringSize: number) => {
|
||||
const hash = bssid.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
const randomAngle = (hash % 360) * (Math.PI / 180);
|
||||
const innerRadius = ringSize * 0.4;
|
||||
const outerRadius = ringSize * 0.5;
|
||||
const randomValue = (hash % 1000) / 1000;
|
||||
const randomRadius = Math.sqrt(innerRadius * innerRadius + randomValue * (outerRadius * outerRadius - innerRadius * innerRadius));
|
||||
|
||||
return {
|
||||
x: Math.cos(randomAngle) * randomRadius,
|
||||
y: Math.sin(randomAngle) * randomRadius
|
||||
};
|
||||
};
|
||||
|
||||
const getPosition = (signal: number, bssid: string) => {
|
||||
let strength: 1 | 2 | 3;
|
||||
if (signal >= -50) {
|
||||
strength = 3;
|
||||
} else if (signal >= -70) {
|
||||
strength = 2;
|
||||
} else if (signal >= -90) {
|
||||
strength = 1;
|
||||
} else {
|
||||
strength = 1;
|
||||
}
|
||||
const ringSizes = { 1: 550, 2: 400, 3: 250 };
|
||||
const ringSize = ringSizes[strength];
|
||||
const offset = getPositionInRing(bssid, ringSize);
|
||||
|
||||
return {
|
||||
top: `calc(50% - ${offset.y}px)`,
|
||||
left: `calc(50% - ${offset.x}px)`
|
||||
};
|
||||
};
|
||||
|
||||
const position = getPosition(network.signal, network.bssid);
|
||||
|
||||
return (
|
||||
<Dot top={position.top} left={position.left} onClick={() => onSelect(network)}>
|
||||
{network.ssid}
|
||||
</Dot>
|
||||
);
|
||||
};
|
||||
|
||||
export default WifiDot;
|
||||
|
|
@ -93,7 +93,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
|||
|
||||
newSocket.onmessage = (event) => {
|
||||
const newData = JSON.parse(event.data);
|
||||
|
||||
|
||||
setData(() => [
|
||||
newData.activation,
|
||||
newData.deactivation,
|
||||
|
|
@ -126,7 +126,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
|||
const activationData = data[0].interfaces;
|
||||
|
||||
const wifi = activationData.find((el: any) => el.type === 'WIFI');
|
||||
|
||||
|
||||
setInterfaces(newInterfaces);
|
||||
setEdgeData(newEdgeData);
|
||||
if (wifi) { setWifi(wifi); }
|
||||
|
|
@ -192,13 +192,13 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
|||
}, [nodes, loader])
|
||||
|
||||
const onEdgesChange = useCallback(
|
||||
(changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges],
|
||||
(changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: any) => setEdges((eds) => addEdge(connection, eds)),
|
||||
[setEdges],
|
||||
(connection: any) => setEdges((eds) => addEdge(connection, eds)),
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const handleNodesChange = (changes: any) => {
|
||||
|
|
@ -254,29 +254,29 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
|||
}, [isNullPos]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
style={{ background: '#F7F9FB' }}
|
||||
minZoom={0}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.1 }}
|
||||
attributionPosition="bottom-left"
|
||||
snapToGrid
|
||||
snapGrid={[20, 20]}
|
||||
panOnDrag={!isPanDisabled}
|
||||
nodeTypes={nodeTypes}
|
||||
proOptions={proOptions}
|
||||
>
|
||||
<Background gap={20} size={1} bgColor={theme.background} />
|
||||
<Controls fitViewOptions={{ padding: 0.1 }} />
|
||||
</ReactFlow>
|
||||
</>
|
||||
<>
|
||||
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={handleNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
style={{ background: '#F7F9FB' }}
|
||||
minZoom={0}
|
||||
maxZoom={4}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.1 }}
|
||||
attributionPosition="bottom-left"
|
||||
snapToGrid
|
||||
snapGrid={[20, 20]}
|
||||
panOnDrag={!isPanDisabled}
|
||||
nodeTypes={nodeTypes}
|
||||
proOptions={proOptions}
|
||||
>
|
||||
<Background gap={20} size={1} bgColor={theme.background} />
|
||||
<Controls fitViewOptions={{ padding: 0.1 }} />
|
||||
</ReactFlow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,10 +50,19 @@ export const ToolBar = () => {
|
|||
{toolBarMenu && toolBarMenu.map((menu) => (
|
||||
<ToolBarButton menuId={menu.id} key={menu.id} />
|
||||
))}
|
||||
<Button>
|
||||
<IconContainer>
|
||||
<IconComponent name={buttonNames[14]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[5])} />
|
||||
</IconContainer>
|
||||
</Button>
|
||||
<Button>
|
||||
<IconContainer>
|
||||
<IconComponent name={buttonNames[15]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[6])} />
|
||||
</IconContainer>
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
|
||||
<ButtonContainer>
|
||||
|
||||
<Button>
|
||||
<IconContainer>
|
||||
<IconComponent name={isAdmin ? buttonNames[10] : buttonNames[13]} width="30" height="30" onClick={() => openModal(ModalMap['5'])} />
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const Container = styled.div`
|
|||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
|
||||
width: 210px;
|
||||
p{
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -117,10 +117,10 @@ export const DevicesBar = () => {
|
|||
return new Promise((resolve) => {
|
||||
const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
|
||||
const timeout = setTimeout(() => {
|
||||
testSocket.close();
|
||||
resolve(false);
|
||||
}, 500
|
||||
);
|
||||
testSocket.close();
|
||||
resolve(false);
|
||||
}, 500
|
||||
);
|
||||
|
||||
testSocket.onopen = () => {
|
||||
clearTimeout(timeout);
|
||||
|
|
@ -196,12 +196,12 @@ export const DevicesBar = () => {
|
|||
|
||||
<DeviceBlock>
|
||||
<DeviceControl className="unselectable" onClick={() => switchWs(currentDevice.address)}>
|
||||
<p>Текущее устройство</p>
|
||||
<p>Корневой адрес</p>
|
||||
</DeviceControl>
|
||||
{currentDevice.hostname == '' ? (
|
||||
<Text className="paddingP">Адрес: Загрузка<AnimDot /></Text>
|
||||
<Text className="paddingP">Название: Загрузка<AnimDot /></Text>
|
||||
) : (
|
||||
<p className="paddingP">Адрес: {currentDevice.hostname}</p>
|
||||
<p className="paddingP">Название: {currentDevice.hostname}</p>
|
||||
)}
|
||||
</DeviceBlock>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@ import { useToolBar } from "../../../../hooks/useToolBar"
|
|||
import { Space } from "../../../../const/menus/sideBar/components"
|
||||
import styled from "styled-components"
|
||||
|
||||
const Container = styled.div`
|
||||
const Container = styled.div<{isOpen: boolean}>`
|
||||
background-color: ${(props) => props.theme.secondary};
|
||||
width: 250px;
|
||||
font-size: 14px;
|
||||
flex: 3;
|
||||
|
||||
overflow: hidden;
|
||||
max-width: ${({isOpen})=>(isOpen ? '250px' : '0')};
|
||||
transition: max-width .4s ease;
|
||||
`
|
||||
|
||||
export const ToolSpace = () => {
|
||||
|
|
@ -15,12 +19,12 @@ export const ToolSpace = () => {
|
|||
const name = toolBar?.current || ''
|
||||
const { isActive, isOpen } = useToolBar(name)
|
||||
|
||||
if (!isActive || !isOpen) {
|
||||
if (!isActive) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container isOpen={isOpen}>
|
||||
<Space name={name} />
|
||||
</Container>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { workSpaceNames } from "../../const/workspace/names";
|
|||
import { Header } from "./modules/appModules/header";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
import About from "./modules/appModules/about";
|
||||
import UserScanner from "./modules/appModules/usersScanner/userScanner";
|
||||
import {WifiScanner} from "./modules/appModules/wifiScanner/WifiScanner";
|
||||
|
||||
const Section = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -75,6 +77,12 @@ export default function WorkSpace() {
|
|||
from={"helloWindow"}
|
||||
/>
|
||||
)}
|
||||
{workModule === workSpaceNames[5] && (
|
||||
<UserScanner />
|
||||
)}
|
||||
{workModule === workSpaceNames[6] && (
|
||||
<WifiScanner />
|
||||
)}
|
||||
{workModule === workSpaceNames[3] &&
|
||||
<WorkSection>
|
||||
<Header />
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@ 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 userSearch from '@iconify/icons-ic/baseline-person-search';
|
||||
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';
|
||||
import wifiFind from '@iconify/icons-ic/baseline-wifi-find';
|
||||
import noteEdit from '@iconify/icons-ph/note-pencil';
|
||||
|
||||
export const ICONS = {
|
||||
[buttonNames[1]]: exitToApp,
|
||||
|
|
@ -30,8 +33,10 @@ export const ICONS = {
|
|||
[buttonNames[10]]: admin,
|
||||
[buttonNames[11]]: refresh,
|
||||
[buttonNames[12]]: close,
|
||||
[buttonNames[13]]: noAdmin
|
||||
|
||||
[buttonNames[13]]: noAdmin,
|
||||
[buttonNames[14]]: userSearch,
|
||||
[buttonNames[15]]: wifiFind,
|
||||
[buttonNames[16]]: noteEdit,
|
||||
} as const;
|
||||
|
||||
type IconName = keyof typeof ICONS;
|
||||
|
|
|
|||
|
|
@ -13,5 +13,8 @@ export const buttonNames: names ={
|
|||
10: 'admin',
|
||||
11: 'reset',
|
||||
12: 'close',
|
||||
13: 'noAdmin'
|
||||
13: 'noAdmin',
|
||||
14: 'userSearch',
|
||||
15: 'wifiFind',
|
||||
16: 'noteEdit'
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
const baseTheme = {
|
||||
accent: '#007ACC',
|
||||
successColor: "#4CAf50",
|
||||
errorColor: "#F44336",
|
||||
accentHover: '#4A90E2',
|
||||
}
|
||||
|
||||
|
|
@ -9,6 +11,10 @@ export const light = {
|
|||
secondary: '#EDEDED',
|
||||
background: '#E8E8E8',
|
||||
textColor: '#000000',
|
||||
borderColor: '#404040',
|
||||
scannerColor: '#007ACC',
|
||||
offlineColor: '#ababab',
|
||||
hoverBtn: '#d6d6d6',
|
||||
};
|
||||
|
||||
export const dark = {
|
||||
|
|
@ -17,4 +23,8 @@ export const dark = {
|
|||
secondary: '#252525',
|
||||
background: '#3A3A3A',
|
||||
textColor: '#FFFFFF',
|
||||
borderColor: '#b6b6b6',
|
||||
scannerColor: '#0E9C9D',
|
||||
offlineColor: '#656565',
|
||||
hoverBtn: '#4a4a4a',
|
||||
};
|
||||
|
|
@ -4,5 +4,7 @@ export const workSpaceNames: names ={
|
|||
1: 'helloWindow',
|
||||
2: 'aboutFromWork',
|
||||
3: 'networkInterfaces',
|
||||
4: 'aboutFromHello'
|
||||
4: 'aboutFromHello',
|
||||
5: 'userScanner',
|
||||
6: 'wifiScanner',
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ interface IpAddressState {
|
|||
export const useIpStore = create<IpAddressState>()(
|
||||
persist(
|
||||
(set)=>({
|
||||
ipAddress: 'localhost',
|
||||
ipAddress: '192:168.1.147',
|
||||
setIpAddress: (newIp: string) => set({ ipAddress: newIp }),
|
||||
}),
|
||||
{
|
||||
|
|
|
|||
51
FRONT/BUILD/resources/app/src/store/useDeviceStore.ts
Normal file
51
FRONT/BUILD/resources/app/src/store/useDeviceStore.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
export interface DeviceUser {
|
||||
ip: string;
|
||||
mac: string;
|
||||
hostname: string;
|
||||
vendor: string;
|
||||
open_ports: string[];
|
||||
status: string;
|
||||
last_seen: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface WifiNetworkScanner {
|
||||
ssid: string;
|
||||
bssid: string;
|
||||
channel: number;
|
||||
signal: number;
|
||||
encryption: string;
|
||||
frequency: string;
|
||||
vendor: string;
|
||||
hidden: boolean;
|
||||
note: string;
|
||||
}
|
||||
|
||||
interface NetworkStore {
|
||||
devices: DeviceUser[];
|
||||
setDevices: (devices: DeviceUser[]) => void;
|
||||
addDevice: (device: DeviceUser) => void;
|
||||
wifiNetworks: WifiNetworkScanner[];
|
||||
setWifiNetworks: (networks: WifiNetworkScanner[]) => void;
|
||||
addWifiNetwork: (network: WifiNetworkScanner) => void;
|
||||
|
||||
clearAll: () => void;
|
||||
}
|
||||
|
||||
const useNetworkStore = create<NetworkStore>((set) => ({
|
||||
devices: [],
|
||||
setDevices: (devices) => set({ devices }),
|
||||
addDevice: (device) =>
|
||||
set((state) => ({ devices: [...state.devices, device] })),
|
||||
|
||||
wifiNetworks: [],
|
||||
setWifiNetworks: (networks) => set({ wifiNetworks: networks }),
|
||||
addWifiNetwork: (network) =>
|
||||
set((state) => ({ wifiNetworks: [...state.wifiNetworks, network] })),
|
||||
|
||||
clearAll: () => set({ devices: [], wifiNetworks: [] })
|
||||
}));
|
||||
|
||||
export default useNetworkStore;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { useIpStore } from "../store";
|
||||
import { useAdminStore } from "../store/adminStore";
|
||||
import { useEffect } from "react";
|
||||
import { NetworkService } from "../api/request";
|
||||
|
||||
export const useCheckAdmin = () => {
|
||||
|
|
|
|||
56
FRONT/README.md
Normal file
56
FRONT/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
## Клиент приложения ГоNets
|
||||
|
||||
### Общие сведения
|
||||
|
||||
ГоNets - приложение для работы с сетевыми ресурсами в удобном и понятном графическом виде. Данное приложение разделено на серверную и клиентскую часть. Сервер сканирует, обрабатывает и преобразует информацию по сетям в простой и понятный JSON формат, который в свою очередь приходит на клиент, где данные проходят ряд обработок и формируются в графическое отображение.
|
||||
|
||||
Клиент приложения ГоNets написан на языке Typescript с использованием фреймворка React. Для более понятного отображения информации была выбрана библиотека ReatFlow, позволяющая легко и просто выстраивать различные графы, схемы, блоки и их связи. Так же, для того, чтобы приложение запускалось как десктопное был выбран метод обёртки собранной (build) версии в облочку Electron.
|
||||
|
||||
|
||||
### Зависимости клиенсткой части
|
||||
|
||||
Далее будет представлен список необходимых npm зависимостей для разработки приложения. Уточню, что необходимы они исключительно во время разработки и сборки, собранная версия работает спокойно без них.
|
||||
|
||||
Зависимости при разработки:
|
||||
|
||||
```
|
||||
|
||||
"dependencies": {
|
||||
"@iconify/icons-ic": "^1.2.13",
|
||||
"@iconify/react": "^5.2.0",
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"@xyflow/react": "^12.3.5",
|
||||
"axios": "^1.7.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.0.2",
|
||||
"styled-components": "^6.1.13",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
|
||||
"devDependencies": {
|
||||
"@iconify-json/ic": "^1.2.2",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"electron": "^34.0.2",
|
||||
"electron-packager": "^17.1.2",
|
||||
"esbuild-wasm": "^0.17.12",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^6.3.4"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Запуск и сборка
|
||||
|
||||
Для того, чтобы работать с исходным кодом, необходимо склонировать проект с ветки frontend, перейти в директорию `FRONT/DEV/` и выполнить установку зависимостей с помощью `npm install`, возможно потребуется сделать после этого `npm audit --fix` Весь исходный код, находится непосредственно в директории `DEV`, собранная же версия в директории `BUILD`.
|
||||
|
||||
Для сборки приложения и обёртки его в электрон необходимо выполнить команду `npm run package`, далее перейти в созданную после команды директорию `dist`, от туда в `gonets-for-linux-64x/resources/app`, здесь нужно выполнить `npm run build`, в появившейся директории `dist` отредактировать файл `index.html`, таким образом, что везде где стоит `/assets` нужно прописать `./assets`. Проверить запуск приложения, можно прописав в терминале `./gonets` из директории `FRONT/DEV/dist/gonets-for-linux-64x`, где и лежит исполняемвый файл.
|
||||
|
||||
После удачного билда можно переместить всё содержимое `FRONT/DEV/dist/gonets-for-linux-64x/` в `/FRONT/BUILD`
|
||||
|
||||
Дополнительно отмечу, что входной точкой/файлом настройки самого электрона является файл `main.js`
|
||||
Loading…
Add table
Add a link
Reference in a new issue