Добавление заметок
This commit is contained in:
parent
ea56d17ea7
commit
e61ffd793c
20 changed files with 274 additions and 118 deletions
|
|
@ -95,7 +95,35 @@ export class NetworkService {
|
||||||
static async scanWifi(ipAddress: string): Promise<AxiosResponse> {
|
static async scanWifi(ipAddress: string): Promise<AxiosResponse> {
|
||||||
try {
|
try {
|
||||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
return await axiosInstance.get('/subnet/user');
|
return await axiosInstance.get('/subnet/wifi');
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async addNote(ipAddress: string, mac:string, note: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post(`/subnet/devices/${mac}/note`, {
|
||||||
|
note: note
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async editNote(ipAddress: string, mac:string, note: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.put(`/subnet/devices/${mac}/note`, {
|
||||||
|
note: note
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static async deleteNote(ipAddress: string, mac:string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.delete(`/subnet/devices/${mac}/note`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return error.response;
|
return error.response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import { DeviceUser } from "../../../store/useDeviceStore";
|
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 {
|
interface DeviceCardProps {
|
||||||
device: DeviceUser;
|
device: DeviceUser;
|
||||||
|
|
@ -24,7 +28,7 @@ const CardWrapper = styled.div<{ status: 'online' | 'offline' }>`
|
||||||
|
|
||||||
const CardHeader = styled.div<{ status: 'online' | 'offline' }>`
|
const CardHeader = styled.div<{ status: 'online' | 'offline' }>`
|
||||||
background: ${({ status, theme }) =>
|
background: ${({ status, theme }) =>
|
||||||
status === 'online' ? theme.successColor : theme.errorColor};
|
status === 'online' ? theme.successColor : theme.offlineColor};
|
||||||
color: white;
|
color: white;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
@ -67,9 +71,19 @@ const Value = styled.span`
|
||||||
color: #333;
|
color: #333;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const toggleExpanded = () => setIsExpanded(!isExpanded);
|
const toggleExpanded = () => setIsExpanded(!isExpanded);
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const openModalNote = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(ModalMap['6'], {
|
||||||
|
mac: device.mac,
|
||||||
|
currentNote: device.note || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -78,6 +92,9 @@ const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||||
<p>
|
<p>
|
||||||
MAC: {device.mac}
|
MAC: {device.mac}
|
||||||
</p>
|
</p>
|
||||||
|
<span onClick={openModalNote}>
|
||||||
|
<IconComponent name={buttonNames[7]} width="24" height="24" setColor={'white'} />
|
||||||
|
</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent isExpanded={isExpanded}>
|
<CardContent isExpanded={isExpanded}>
|
||||||
|
|
@ -107,6 +124,11 @@ const DeviceCard: React.FC<DeviceCardProps> = ({ device, status }) => {
|
||||||
<Label>Последняя активность:</Label>
|
<Label>Последняя активность:</Label>
|
||||||
<Value>{device.last_seen}</Value>
|
<Value>{device.last_seen}</Value>
|
||||||
</InfoRow>
|
</InfoRow>
|
||||||
|
|
||||||
|
<InfoRow>
|
||||||
|
<Label>Заметка:</Label>
|
||||||
|
<Value>{device.note || '-'}</Value>
|
||||||
|
</InfoRow>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ const DropdownRow = styled.button`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: ${({theme}) => theme.textColor};
|
||||||
transition: all .3s ease;
|
transition: all .3s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ const Button = styled.button.withConfig({
|
||||||
border: none;
|
border: none;
|
||||||
padding: ${(props) =>
|
padding: ${(props) =>
|
||||||
props.size === "small"
|
props.size === "small"
|
||||||
? "4px 8px"
|
? "10px 28px"
|
||||||
: props.size === "big"
|
: props.size === "big"
|
||||||
? "10px 20px"
|
? "10px 20px"
|
||||||
: "6px 10px"}; // По умолчанию
|
: "12px 20px"}; // По умолчанию
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: ${(props) =>
|
font-size: ${(props) =>
|
||||||
props.size === "small"
|
props.size === "small"
|
||||||
? "12px"
|
? "14px"
|
||||||
: props.size === "big"
|
: props.size === "big"
|
||||||
? "16px"
|
? "16px"
|
||||||
: "14px"}; // По умолчанию
|
: "14px"}; // По умолчанию
|
||||||
|
|
@ -30,7 +30,7 @@ const Button = styled.button.withConfig({
|
||||||
? 'row-reverse'
|
? 'row-reverse'
|
||||||
: 'row'};
|
: 'row'};
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
transition: all .3s ease;
|
||||||
&:hover{
|
&:hover{
|
||||||
background-color: ${(props) => props.$hoverсolor};
|
background-color: ${(props) => props.$hoverсolor};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ type Props = {
|
||||||
placeholderText: string;
|
placeholderText: string;
|
||||||
id: string;
|
id: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (identifier: string, value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onFileChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
onFileChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
type?: string;
|
type?: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
|
@ -24,7 +24,7 @@ const InputContainer = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInput = styled.input`
|
const StyledInput = styled.input`
|
||||||
padding: 8px 10px;
|
padding: 12px 10px;
|
||||||
color: black;
|
color: black;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #A1A1A1;
|
border: 1px solid #A1A1A1;
|
||||||
|
|
@ -41,7 +41,7 @@ const Input = React.forwardRef<HTMLInputElement, Props>(({ placeholderText, id,
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(id, value);
|
onChange(value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ interface TableScannerProps {
|
||||||
devices: DeviceUser[];
|
devices: DeviceUser[];
|
||||||
}
|
}
|
||||||
import styled from 'styled-components';
|
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`
|
const TableContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -48,12 +52,21 @@ const TableCell = styled.td`
|
||||||
const StatusBadge = styled.span<{ status:string }>`
|
const StatusBadge = styled.span<{ status:string }>`
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.errorColor)};
|
background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.offlineColor)};
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius:4px;
|
border-radius:4px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const TableScanner = ({devices}:TableScannerProps) => {
|
const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openModal(ModalMap['6'], {
|
||||||
|
mac: device.mac,
|
||||||
|
currentNote: device.note || ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
|
|
@ -66,6 +79,8 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
<TableHeaderCell>IP адрес</TableHeaderCell>
|
<TableHeaderCell>IP адрес</TableHeaderCell>
|
||||||
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
||||||
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Заметка</TableHeaderCell>
|
||||||
|
<TableHeaderCell></TableHeaderCell>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -77,6 +92,12 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
<TableCell>{item.ip}</TableCell>
|
<TableCell>{item.ip}</TableCell>
|
||||||
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
||||||
<TableCell>{item.last_seen}</TableCell>
|
<TableCell>{item.last_seen}</TableCell>
|
||||||
|
<TableCell>{item.note || '-'}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<span onClick={(e)=>openModalNote(e, item)}>
|
||||||
|
<IconComponent name={buttonNames[7]} width="24" height="24" />
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// 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 {
|
||||||
|
mac: string;
|
||||||
|
onClose: () => void;
|
||||||
|
currentNote?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const NoteDevice = ({ mac, onClose, currentNote}: 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, mac, note);
|
||||||
|
}else if (currentNote && note.trim() !== '' && note !== currentNote) {
|
||||||
|
await NetworkService.editNote(ipAddress, mac, note);
|
||||||
|
}else if (currentNote && note.trim() === '') {
|
||||||
|
await NetworkService.deleteNote(ipAddress, mac);
|
||||||
|
}else {
|
||||||
|
onClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedDevices = devices.map(device =>
|
||||||
|
device.mac === mac
|
||||||
|
? { ...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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -3,6 +3,7 @@ import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalCon
|
||||||
import { ModalMap } from "./windowsType";
|
import { ModalMap } from "./windowsType";
|
||||||
import Loader from "../UI/loader";
|
import Loader from "../UI/loader";
|
||||||
import { ModalWindow } from "./style";
|
import { ModalWindow } from "./style";
|
||||||
|
import {NoteDevice} from "./modalContainers/NoteDevice";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
|
|
@ -35,6 +36,13 @@ export default function WindowComponent({ isOpen, type, data }: Props) {
|
||||||
message={data.message}
|
message={data.message}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{type === ModalMap['6'] && (
|
||||||
|
<NoteDevice
|
||||||
|
mac={data.mac}
|
||||||
|
currentNote={data.currentNote}
|
||||||
|
onClose={closeLoader}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{type === ModalMap['4'] && (
|
{type === ModalMap['4'] && (
|
||||||
<AddDevice onSave={data?.onSave} onClose={closeLoader} />
|
<AddDevice onSave={data?.onSave} onClose={closeLoader} />
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export const ModalContainer = styled.div<{height?: string}>`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
height: ${(props) => props.height ? props.height : '200px'};
|
height: ${(props) => props.height ? props.height : '230px'};
|
||||||
width: 520px;
|
width: 520px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@ export const ModalMap: ModalType = {
|
||||||
'2': 'loader',
|
'2': 'loader',
|
||||||
'3': 'connectionError',
|
'3': 'connectionError',
|
||||||
'4': 'addDevice',
|
'4': 'addDevice',
|
||||||
'5': 'sudo'
|
'5': 'sudo',
|
||||||
|
'6': 'noteDevice',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ interface ListUsersProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => {
|
||||||
|
|
||||||
// Фильтр по статусу
|
|
||||||
const activeDevices = devices.filter(device => device.status === 'online');
|
const activeDevices = devices.filter(device => device.status === 'online');
|
||||||
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
const inactiveDevices = devices.filter(device => device.status !== 'online');
|
||||||
|
|
||||||
|
|
@ -55,7 +53,7 @@ const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps
|
||||||
</div>
|
</div>
|
||||||
{/* Колонка неактивных устройств */}
|
{/* Колонка неактивных устройств */}
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<h3 style={{ color: '#F44336', marginBottom:'12px' }}>Неактивные устройства ({inactiveDevices.length})</h3>
|
<h3 style={{ color: '#ababab', marginBottom:'12px' }}>Неактивные устройства ({inactiveDevices.length})</h3>
|
||||||
{inactiveDevices.map((device, index) => (
|
{inactiveDevices.map((device, index) => (
|
||||||
<DeviceCard
|
<DeviceCard
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
|
|
@ -14,21 +14,21 @@ type Option ={
|
||||||
|
|
||||||
// Анимации
|
// Анимации
|
||||||
const ring1 = keyframes`
|
const ring1 = keyframes`
|
||||||
0% { width: 250px; height: 250px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
50% { width: 280px; height: 280px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||||
100% { width: 250px; height: 250px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ring2 = keyframes`
|
const ring2 = keyframes`
|
||||||
0% { width: 400px; height: 400px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
50% { width: 430px; height: 430px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||||
100% { width: 400px; height: 400px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ring3 = keyframes`
|
const ring3 = keyframes`
|
||||||
0% { width: 550px; height: 550px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
50% { width: 580px; height: 580px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||||
100% { width: 550px; height: 550px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
`;
|
`;
|
||||||
const dotAnimation = keyframes`
|
const dotAnimation = keyframes`
|
||||||
0% { content: '.'; }
|
0% { content: '.'; }
|
||||||
|
|
@ -36,6 +36,11 @@ const dotAnimation = keyframes`
|
||||||
66% { content: '...'; }
|
66% { content: '...'; }
|
||||||
100% { 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}>`
|
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||||
|
|
@ -60,8 +65,8 @@ const TitleWrapper = styled.h2`
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
|
width: max-content;
|
||||||
`
|
`
|
||||||
|
|
||||||
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
|
@ -80,7 +85,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
&.center {
|
&.center {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||||
}
|
}
|
||||||
&.ring1 {
|
&.ring1 {
|
||||||
opacity: 0.12;
|
opacity: 0.12;
|
||||||
|
|
@ -94,6 +99,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ring3 {
|
&.ring3 {
|
||||||
|
|
@ -101,9 +107,9 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
width: 550px;
|
width: 550px;
|
||||||
height: 550px;
|
height: 550px;
|
||||||
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LoadingText = styled.div`
|
const LoadingText = styled.div`
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,19 @@ type Option ={
|
||||||
|
|
||||||
// Анимации
|
// Анимации
|
||||||
const ring1 = keyframes`
|
const ring1 = keyframes`
|
||||||
0% { width: 250px; height: 250px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
50% { width: 280px; height: 280px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.2; }
|
||||||
100% { width: 250px; height: 250px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.12; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ring2 = keyframes`
|
const ring2 = keyframes`
|
||||||
0% { width: 400px; height: 400px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
50% { width: 430px; height: 430px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; }
|
||||||
100% { width: 400px; height: 400px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.08; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ring3 = keyframes`
|
const ring3 = keyframes`
|
||||||
0% { width: 550px; height: 550px; }
|
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
50% { width: 580px; height: 580px; }
|
50% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.1; }
|
||||||
100% { width: 550px; height: 550px; }
|
100% { transform: translate(-50%, -50%) scale(1); opacity: 0.04; }
|
||||||
`;
|
`;
|
||||||
const dotAnimation = keyframes`
|
const dotAnimation = keyframes`
|
||||||
0% { content: '.'; }
|
0% { content: '.'; }
|
||||||
|
|
@ -37,13 +35,18 @@ const dotAnimation = keyframes`
|
||||||
66% { content: '...'; }
|
66% { content: '...'; }
|
||||||
100% { 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}>`
|
const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content:${({scanInitiated, loading})=>(scanInitiated && !loading ? 'start' : 'center')};
|
justify-content:center ;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -69,7 +72,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: .4s ease;
|
transition: .4s ease;
|
||||||
transform: ${({scanInitiated,loading})=>(scanInitiated && !loading ? 'scale(0)' : 'scale(1)')};
|
transform: scale(1);
|
||||||
span {
|
span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -81,7 +84,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
&.center {
|
&.center {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
animation: ${({ loading }) => (loading ? pulse : "none")} 2s ease infinite;
|
||||||
}
|
}
|
||||||
&.ring1 {
|
&.ring1 {
|
||||||
opacity: 0.12;
|
opacity: 0.12;
|
||||||
|
|
@ -95,6 +98,7 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.1s" : "0")};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ring3 {
|
&.ring3 {
|
||||||
|
|
@ -102,11 +106,12 @@ const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>`
|
||||||
width: 550px;
|
width: 550px;
|
||||||
height: 550px;
|
height: 550px;
|
||||||
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite;
|
||||||
|
animation-delay: ${({ loading }) => (loading ? "0.2s" : "0")};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const LoadingText = styled.div`
|
const LoadingText = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 14px 22px;
|
padding: 14px 22px;
|
||||||
|
|
@ -122,7 +127,7 @@ const ScanningText = styled.span`
|
||||||
animation: ${dotAnimation} 1.5s infinite;
|
animation: ${dotAnimation} 1.5s infinite;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const ScanBtn = styled.button<{loading: boolean}>`
|
const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 14px 22px;
|
padding: 14px 22px;
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
|
|
@ -130,57 +135,25 @@ const ScanBtn = styled.button<{loading: boolean}>`
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
transition: .4s ease;
|
transition: transform .4s ease;
|
||||||
transform: ${({loading}) => (!loading ? "translateY(240px)" : "translateY(0)")};
|
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")};
|
||||||
&:hover{
|
&:hover{
|
||||||
transform: ${({loading}) => (!loading ? "translateY(240px)" : "translateY(0)")} scale(1.03);
|
transform: ${({scanInitiated}) => (scanInitiated ? "translateY(240px)" : "translateY(0)")} scale(1.03);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
ssid: "Huawei",
|
|
||||||
bssid: "wef",
|
|
||||||
channel: 0,
|
|
||||||
signal:2,
|
|
||||||
encryption: "wef",
|
|
||||||
frequency: "ewf",
|
|
||||||
vendor: "striwefwng",
|
|
||||||
hidden: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ssid: "Gu-2",
|
|
||||||
bssid: "grgerger",
|
|
||||||
channel: 0,
|
|
||||||
signal: 2,
|
|
||||||
encryption: "weergref",
|
|
||||||
frequency: "eergewf",
|
|
||||||
vendor: "grgregerge",
|
|
||||||
hidden: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ssid: "Huawei_wifi2",
|
|
||||||
bssid: "wqdqw",
|
|
||||||
channel: 2,
|
|
||||||
signal: 3,
|
|
||||||
encryption: "wefwe",
|
|
||||||
frequency: "wef",
|
|
||||||
vendor: "fwefw",
|
|
||||||
hidden: false
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function WifiScanner() {
|
export function WifiScanner() {
|
||||||
const {ipAddress} = useIpStore();
|
const ipAddress = '192.168.1.147';
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [scanInitiated, setScanInitiated] = useState(false);
|
const [scanInitiated, setScanInitiated] = useState(false);
|
||||||
const {wifiNetworks, setWifiNetworks} = useNetworkStore();
|
const {wifiNetworks, setWifiNetworks} = useNetworkStore();
|
||||||
const [currentNetwork, setCurrentNetwork] = useState<WifiNetworkScanner | null>(null);
|
const [currentNetwork, setCurrentNetwork] = useState<WifiNetworkScanner | null>(null);
|
||||||
|
|
||||||
const handleScan = async () => {
|
const handleScan = async () => {
|
||||||
try {
|
setWifiNetworks([]);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setScanInitiated(true);
|
setScanInitiated(true);
|
||||||
|
try {
|
||||||
const res = await NetworkService.scanWifi(ipAddress);
|
const res = await NetworkService.scanWifi(ipAddress);
|
||||||
const networks = res.data.access_points || [];
|
const networks = res.data.access_points || [];
|
||||||
setWifiNetworks(networks);
|
setWifiNetworks(networks);
|
||||||
|
|
@ -194,16 +167,14 @@ export function WifiScanner() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
<Wrapper scanInitiated={scanInitiated} loading={loading}>
|
||||||
{(!scanInitiated || loading) && (
|
|
||||||
<TitleWrapper>Сканирование Wi-Fi сети</TitleWrapper>
|
<TitleWrapper>Сканирование Wi-Fi сети</TitleWrapper>
|
||||||
)}
|
|
||||||
|
|
||||||
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
<UserLocate loading={loading} scanInitiated={scanInitiated}>
|
||||||
<span className="center"></span>
|
<span className="center"></span>
|
||||||
<span className="ring1"></span>
|
<span className="ring1"></span>
|
||||||
<span className="ring2"></span>
|
<span className="ring2"></span>
|
||||||
<span className="ring3"></span>
|
<span className="ring3"></span>
|
||||||
{data.map((network) => (
|
{wifiNetworks.map((network) => (
|
||||||
<WifiDot
|
<WifiDot
|
||||||
key={network.bssid}
|
key={network.bssid}
|
||||||
network={network}
|
network={network}
|
||||||
|
|
@ -224,7 +195,7 @@ export function WifiScanner() {
|
||||||
Сканирование сети <ScanningText/>
|
Сканирование сети <ScanningText/>
|
||||||
</LoadingText>
|
</LoadingText>
|
||||||
) : (
|
) : (
|
||||||
<ScanBtn onClick={handleScan} disabled={loading} loading={loading}>
|
<ScanBtn onClick={handleScan} disabled={loading} scanInitiated={scanInitiated}>
|
||||||
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
|
{wifiNetworks.length > 0 ? 'Обновить список' : 'Сканировать сеть'}
|
||||||
</ScanBtn>
|
</ScanBtn>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const CloseBtn = styled.div`
|
||||||
top: -20px;
|
top: -20px;
|
||||||
right: -20px;
|
right: -20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: white;
|
background-color: ${(props) => props.theme.primary};
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -37,7 +37,7 @@ const CloseBtn = styled.div`
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #d6d6d6;
|
background-color: ${(props) => props.theme.hoverBtn};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const ModalTitle = styled.div`
|
const ModalTitle = styled.div`
|
||||||
|
|
@ -100,9 +100,9 @@ const ModalWifiScaner = ({ network,onClose }: { network: WifiNetworkScanner, onC
|
||||||
<ModalTitle>
|
<ModalTitle>
|
||||||
<h4>{network.ssid}</h4>
|
<h4>{network.ssid}</h4>
|
||||||
<SignalBarsWrapper>
|
<SignalBarsWrapper>
|
||||||
<SignalBar active={network.signal >= 1} />
|
<SignalBar active={network.signal >= -120} />
|
||||||
<SignalBar active={network.signal >= 2} />
|
<SignalBar active={network.signal >= -70} />
|
||||||
<SignalBar active={network.signal >= 3} />
|
<SignalBar active={network.signal >= -50} />
|
||||||
</SignalBarsWrapper>
|
</SignalBarsWrapper>
|
||||||
</ModalTitle>
|
</ModalTitle>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
|
|
|
||||||
|
|
@ -46,15 +46,24 @@ const WifiDot = ({ network, onSelect }: { network: WifiNetworkScanner, onSelect:
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPosition = (signal: number, bssid: string) => {
|
const getPosition = (signal: number, bssid: string) => {
|
||||||
const strength = Math.min(3, Math.max(1, signal));
|
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 ringSizes = { 1: 550, 2: 400, 3: 250 };
|
||||||
const ringSize = ringSizes[strength as 1 | 2 | 3];
|
const ringSize = ringSizes[strength];
|
||||||
const offset = getPositionInRing(bssid, ringSize);
|
const offset = getPositionInRing(bssid, ringSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: `calc(50% - ${offset.y}px)`,
|
top: `calc(50% - ${offset.y}px)`,
|
||||||
left: `calc(50% - ${offset.x}px)`
|
left: `calc(50% - ${offset.x}px)`
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const position = getPosition(network.signal, network.bssid);
|
const position = getPosition(network.signal, network.bssid);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export const ToolBar = () => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button>
|
<Button>
|
||||||
<IconContainer>
|
<IconContainer>
|
||||||
<IconComponent name={buttonNames[14]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[6])} />
|
<IconComponent name={buttonNames[15]} width="30" height="30" onClick={() => setWorkModule(workSpaceNames[6])} />
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import admin from '@iconify/icons-ic/baseline-admin-panel-settings';
|
||||||
import noAdmin from '@iconify/icons-ic/baseline-remove-moderator'
|
import noAdmin from '@iconify/icons-ic/baseline-remove-moderator'
|
||||||
import refresh from '@iconify/icons-ic/baseline-refresh';
|
import refresh from '@iconify/icons-ic/baseline-refresh';
|
||||||
import close from '@iconify/icons-ic/round-close';
|
import close from '@iconify/icons-ic/round-close';
|
||||||
|
import wifiFind from '@iconify/icons-ic/baseline-wifi-find';
|
||||||
|
|
||||||
export const ICONS = {
|
export const ICONS = {
|
||||||
[buttonNames[1]]: exitToApp,
|
[buttonNames[1]]: exitToApp,
|
||||||
|
|
@ -33,6 +34,7 @@ export const ICONS = {
|
||||||
[buttonNames[12]]: close,
|
[buttonNames[12]]: close,
|
||||||
[buttonNames[13]]: noAdmin,
|
[buttonNames[13]]: noAdmin,
|
||||||
[buttonNames[14]]: userSearch,
|
[buttonNames[14]]: userSearch,
|
||||||
|
[buttonNames[15]]: wifiFind,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type IconName = keyof typeof ICONS;
|
type IconName = keyof typeof ICONS;
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,6 @@ export const buttonNames: names ={
|
||||||
11: 'reset',
|
11: 'reset',
|
||||||
12: 'close',
|
12: 'close',
|
||||||
13: 'noAdmin',
|
13: 'noAdmin',
|
||||||
14: 'userSearch'
|
14: 'userSearch',
|
||||||
|
15: 'wifiFind',
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,10 @@ export const light = {
|
||||||
secondary: '#EDEDED',
|
secondary: '#EDEDED',
|
||||||
background: '#E8E8E8',
|
background: '#E8E8E8',
|
||||||
textColor: '#000000',
|
textColor: '#000000',
|
||||||
|
borderColor: '#404040',
|
||||||
scannerColor: '#007ACC',
|
scannerColor: '#007ACC',
|
||||||
|
offlineColor: '#ababab',
|
||||||
|
hoverBtn: '#d6d6d6',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark = {
|
export const dark = {
|
||||||
|
|
@ -20,5 +23,8 @@ export const dark = {
|
||||||
secondary: '#252525',
|
secondary: '#252525',
|
||||||
background: '#3A3A3A',
|
background: '#3A3A3A',
|
||||||
textColor: '#FFFFFF',
|
textColor: '#FFFFFF',
|
||||||
|
borderColor: '#b6b6b6',
|
||||||
scannerColor: '#0E9C9D',
|
scannerColor: '#0E9C9D',
|
||||||
|
offlineColor: '#656565',
|
||||||
|
hoverBtn: '#4a4a4a',
|
||||||
};
|
};
|
||||||
|
|
@ -8,6 +8,7 @@ export interface DeviceUser {
|
||||||
open_ports: string[];
|
open_ports: string[];
|
||||||
status: string;
|
status: string;
|
||||||
last_seen: string;
|
last_seen: string;
|
||||||
|
note: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WifiNetworkScanner {
|
export interface WifiNetworkScanner {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue