Добавление настроек и их функциональности
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
This commit is contained in:
parent
4257864b3d
commit
ee350c1852
23 changed files with 368 additions and 91 deletions
|
|
@ -76,7 +76,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
||||||
setHasInited(false)
|
setHasInited(false)
|
||||||
initializationAttempt.current = 0;
|
initializationAttempt.current = 0;
|
||||||
|
|
||||||
const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
|
const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
|
||||||
|
|
||||||
let connectionTimeout;
|
let connectionTimeout;
|
||||||
setSocket(newSocket);
|
setSocket(newSocket);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export const DevicesBar = () => {
|
||||||
|
|
||||||
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
|
const testSocket = new WebSocket(`ws://${ip}:8001/api/v1`);
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
testSocket.close();
|
testSocket.close();
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
||||||
const axiosWithoutCredentials = axios.create({
|
const axiosWithoutCredentials = axios.create({
|
||||||
baseURL: `https://localhost:8001`,
|
baseURL: `http://localhost:8001`,
|
||||||
})
|
})
|
||||||
|
|
||||||
export class NetworkService {
|
export class NetworkService {
|
||||||
static createAxiosInstance(ipAddress: string) {
|
static createAxiosInstance(ipAddress: string) {
|
||||||
return axios.create({
|
return axios.create({
|
||||||
baseURL: `https://${ipAddress}:8001`,
|
baseURL: `http://${ipAddress}:8001`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +86,9 @@ export class NetworkService {
|
||||||
static async scanUser(ipAddress: string): Promise<AxiosResponse> {
|
static async scanUser(ipAddress: string): Promise<AxiosResponse> {
|
||||||
try {
|
try {
|
||||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
return await axiosInstance.get('/subnet/devices');
|
return await axiosInstance.post('/subnet/devices',{
|
||||||
|
subnet: ''
|
||||||
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return error.response;
|
return error.response;
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +159,8 @@ export class NetworkService {
|
||||||
return error.response;
|
return error.response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
static async clearSubnet(ipAddress: string): Promise<AxiosResponse> {
|
static async clearSubnet(ipAddress: string): Promise<AxiosResponse> {
|
||||||
try {
|
try {
|
||||||
const axiosInstance = this.createAxiosInstance(ipAddress);
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
|
@ -173,4 +177,12 @@ export class NetworkService {
|
||||||
return error.response;
|
return error.response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static async exportDevices(ipAddress: string): Promise<AxiosResponse> {
|
||||||
|
try {
|
||||||
|
const axiosInstance = this.createAxiosInstance(ipAddress);
|
||||||
|
return await axiosInstance.post(`/settings/export`);
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {ButtonContainer, ModalContainer, ModalContent, ModalTitle} from "../style";
|
||||||
|
import {MainButton} from "../../UI/buttons/mainButton";
|
||||||
|
import {ModalMap} from "../windowsType";
|
||||||
|
import {useModalContext} from "../../../store";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { useSettingsStore } from '../../../store/settings/settingsStore';
|
||||||
|
|
||||||
|
const StyledTableList = styled.ul`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 40px;
|
||||||
|
list-style-type: none;
|
||||||
|
`
|
||||||
|
const StyledTableItem = styled.li`
|
||||||
|
width: 160px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
background-color: ${props => props.theme.accent};
|
||||||
|
color: ${props => props.theme.primary};
|
||||||
|
border: 1px solid ${props => props.theme.primary};
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 16px 0 0 16px;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 16px 16px 0;
|
||||||
|
}
|
||||||
|
&.off{
|
||||||
|
background-color: ${props => props.theme.offlineColor};
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const CustomizeNodes = () => {
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
const pendingChanges = useSettingsStore(state => state.pendingChanges);
|
||||||
|
const updateSetting = useSettingsStore(state => state.updateSetting);
|
||||||
|
const getSetting = useSettingsStore(state => state.getSetting);
|
||||||
|
const initializeSettings = useSettingsStore(state => state.initializeSettings);
|
||||||
|
|
||||||
|
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Получаем текущие значения из хранилища
|
||||||
|
const initialColumns = getSetting('table-columns-order').currentValue as string[];
|
||||||
|
const disabledColumns = getSetting('table-columns-disabled').currentValue as number[];
|
||||||
|
const items = pendingChanges['table-columns-order'] ?? initialColumns;
|
||||||
|
const disabledItems = pendingChanges['table-columns-disabled'] ?? disabledColumns;
|
||||||
|
|
||||||
|
// Drag & Drop
|
||||||
|
const handleDragStart = (index: number) => {
|
||||||
|
setDragIndex(index);
|
||||||
|
};
|
||||||
|
const handleDragOver = (index: number) => {
|
||||||
|
if (dragIndex === null || index === dragIndex) return;
|
||||||
|
|
||||||
|
const newItems = [...items];
|
||||||
|
const [movedItem] = newItems.splice(dragIndex, 1);
|
||||||
|
newItems.splice(index, 0, movedItem);
|
||||||
|
|
||||||
|
updateSetting('table-columns-order', newItems);
|
||||||
|
setDragIndex(index);
|
||||||
|
};
|
||||||
|
const handleDrop = () => {
|
||||||
|
setDragIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Переключение состояния
|
||||||
|
const handleSwitch = (index: number) => {
|
||||||
|
const newDisabledItems = disabledItems.includes(index)
|
||||||
|
? disabledItems.filter((i: number) => i !== index)
|
||||||
|
: [...disabledItems, index];
|
||||||
|
|
||||||
|
updateSetting('table-columns-disabled', newDisabledItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Закрытие с сохранением
|
||||||
|
const handleSave = () => {
|
||||||
|
openModal(ModalMap['10']);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initializeSettings();
|
||||||
|
}, [initializeSettings]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer style={{ height: '35vh', width: '80vw'}}>
|
||||||
|
<ModalTitle>
|
||||||
|
<h3>Кастомизация таблицы узлов подсети</h3>
|
||||||
|
|
||||||
|
</ModalTitle>
|
||||||
|
|
||||||
|
<ModalContent style={{ display: 'flex', height: "100%", gap: '24px', padding: 0 }}>
|
||||||
|
<StyledTableList>
|
||||||
|
{items.map((item:string, index:number) => (
|
||||||
|
<StyledTableItem
|
||||||
|
key={index}
|
||||||
|
className={disabledItems.includes(index) ? 'off' : ''}
|
||||||
|
draggable
|
||||||
|
onDragStart={() => handleDragStart(index)}
|
||||||
|
onDragOver={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleDragOver(index);
|
||||||
|
}}
|
||||||
|
onDragEnd={handleDrop}
|
||||||
|
onClick={() => handleSwitch(index)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</StyledTableItem>
|
||||||
|
))}
|
||||||
|
</StyledTableList>
|
||||||
|
</ModalContent>
|
||||||
|
|
||||||
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title='Назад'
|
||||||
|
onClick={handleSave}
|
||||||
|
/>
|
||||||
|
</ButtonContainer>
|
||||||
|
</ModalContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomizeNodes;
|
||||||
|
|
@ -8,6 +8,7 @@ import { NoteWifi } from "./modalContainers/NoteWifi";
|
||||||
import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
|
import {ConnectNetwork} from "./modalContainers/ConnectNetwork";
|
||||||
import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
|
import {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
|
||||||
import {Settings} from "../settings/Settings";
|
import {Settings} from "../settings/Settings";
|
||||||
|
import CustomizeNodes from "./modalContainers/customizeNodes";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
|
|
@ -80,6 +81,9 @@ export default function WindowComponent({ isOpen, type, data }: Props) {
|
||||||
onClose={closeLoader}
|
onClose={closeLoader}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{type === ModalMap['11'] && (
|
||||||
|
<CustomizeNodes />
|
||||||
|
)}
|
||||||
</ModalWindow >
|
</ModalWindow >
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export const ModalTitle = styled.div`
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-bottom: 1px solid #9D9D9D;
|
border-bottom: 1px solid #9D9D9D;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
h3{
|
h3{
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,6 @@ export const ModalMap: ModalType = {
|
||||||
'7': 'noteWifi',
|
'7': 'noteWifi',
|
||||||
'8': 'connectNetwork',
|
'8': 'connectNetwork',
|
||||||
'9': 'disconnectConfirm',
|
'9': 'disconnectConfirm',
|
||||||
|
"10": 'settings',
|
||||||
|
'11': 'customNodes',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import {buttonNames} from "../../const/buttons/names";
|
||||||
import SettingSelect from "./SettingSelect";
|
import SettingSelect from "./SettingSelect";
|
||||||
import {MainButton} from "../UI/buttons/mainButton";
|
import {MainButton} from "../UI/buttons/mainButton";
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {useModalContext} from "../../store";
|
||||||
|
import {ModalMap} from "../modalwindows/windowsType";
|
||||||
|
|
||||||
const SettingBlock = styled.div`
|
const SettingBlock = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -19,6 +21,7 @@ const SettingBlock = styled.div`
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: ${props => props.theme.primary};
|
background-color: ${props => props.theme.primary};
|
||||||
|
color: ${props => props.theme.textColor};
|
||||||
border: 1px solid ${props => props.theme.borderColor};
|
border: 1px solid ${props => props.theme.borderColor};
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -73,7 +76,7 @@ const InputWrapper = styled.div`
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
`
|
`
|
||||||
const ErrorText = styled.div`
|
const ErrorText = styled.div`
|
||||||
color: red;
|
color: ${props => props.theme.errorColor};
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -87,7 +90,17 @@ interface SettingItemProps {
|
||||||
const SettingItem = React.memo(({ setting, value, onChange, onValidationError }: SettingItemProps) => {
|
const SettingItem = React.memo(({ setting, value, onChange, onValidationError }: SettingItemProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const { openModal } = useModalContext();
|
||||||
|
|
||||||
|
const handleButtonAction = () => {
|
||||||
|
if (setting.id === 'customize-nodes') {
|
||||||
|
openModal(ModalMap['11']);
|
||||||
|
} else if (setting.action) {
|
||||||
|
setting.action();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Изменение числа
|
||||||
const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
let newError: string | null = null;
|
let newError: string | null = null;
|
||||||
|
|
@ -172,9 +185,10 @@ const SettingItem = React.memo(({ setting, value, onChange, onValidationError }:
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
title={setting.defaultValue || ''}
|
title={setting.defaultValue || ''}
|
||||||
onClick={setting.action}
|
onClick={handleButtonAction}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{setting.type === 'hidden' && null}
|
||||||
</SettingBlock>
|
</SettingBlock>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ import {Setting, useSettingsStore} from "../../store/settings/settingsStore";
|
||||||
import {SettingItem} from "./SettingItem";
|
import {SettingItem} from "./SettingItem";
|
||||||
import {IconComponent} from "../../const/buttons/icons";
|
import {IconComponent} from "../../const/buttons/icons";
|
||||||
import {buttonNames} from "../../const/buttons/names";
|
import {buttonNames} from "../../const/buttons/names";
|
||||||
import {ModalMap} from "../modalwindows/windowsType";
|
|
||||||
import {workSpaceNames} from "../../const/workspace/names";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|
@ -129,7 +127,7 @@ export const Settings = ({ onClose }: Props) => {
|
||||||
}, [discardChanges, onClose]);
|
}, [discardChanges, onClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContainer style={{ height: '60vh', width: '50vw'}}>
|
<ModalContainer style={{ height: '60vh', width: '50vw', minWidth: "800px" }}>
|
||||||
<ModalTitle>
|
<ModalTitle>
|
||||||
<h3>Настройки</h3>
|
<h3>Настройки</h3>
|
||||||
<CloseBtn>
|
<CloseBtn>
|
||||||
|
|
@ -168,6 +166,13 @@ export const Settings = ({ onClose }: Props) => {
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
|
<MainButton
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
title='Отменить'
|
||||||
|
onClick={handleCancel}
|
||||||
|
|
||||||
|
/>
|
||||||
<MainButton
|
<MainButton
|
||||||
type="button"
|
type="button"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useMemo, useState} from 'react';
|
||||||
import { DeviceUser } from '../../../../../../store/useDeviceStore';
|
import { DeviceUser } from '../../../../../../store/useDeviceStore';
|
||||||
|
|
||||||
interface TableScannerProps {
|
|
||||||
devices: DeviceUser[];
|
|
||||||
}
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {IconComponent} from "../../../../../../const/buttons/icons";
|
import {IconComponent} from "../../../../../../const/buttons/icons";
|
||||||
import {buttonNames} from "../../../../../../const/buttons/names";
|
import {buttonNames} from "../../../../../../const/buttons/names";
|
||||||
import {ModalMap} from "../../../../../modalwindows/windowsType";
|
import {ModalMap} from "../../../../../modalwindows/windowsType";
|
||||||
import {useModalContext} from "../../../../../../store";
|
import {useModalContext} from "../../../../../../store";
|
||||||
import Pagination from "../../../../../UI/Pagination";
|
import Pagination from "../../../../../UI/Pagination";
|
||||||
import {useSettingsStore} from "../../../../../../store/settings/settingsStore";
|
import {
|
||||||
import {useNameSetting, usePaginateSetting} from "../../../../../../hooks/useSettings";
|
useDisabledColumns,
|
||||||
|
useNameSetting,
|
||||||
|
useOrdersColumns,
|
||||||
|
usePaginateSetting
|
||||||
|
} from "../../../../../../hooks/useSettings";
|
||||||
|
|
||||||
|
interface TableScannerProps {
|
||||||
|
devices: DeviceUser[];
|
||||||
|
}
|
||||||
|
|
||||||
const TableContainer = styled.div`
|
const TableContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -67,6 +71,7 @@ const StatusBadge = styled.span<{ status:string }>`
|
||||||
|
|
||||||
const TableScanner = ({devices}:TableScannerProps) => {
|
const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
const { openModal } = useModalContext();
|
const { openModal } = useModalContext();
|
||||||
|
const nameSetting = useNameSetting();
|
||||||
|
|
||||||
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -75,7 +80,27 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
currentNote: device.note || ''
|
currentNote: device.note || ''
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const nameSetting = useNameSetting();
|
|
||||||
|
// Применение настроек отображения
|
||||||
|
const ordersColumns = useOrdersColumns();
|
||||||
|
const disabledColumns = useDisabledColumns();
|
||||||
|
const columnVisibility = useMemo(() => {
|
||||||
|
return {
|
||||||
|
status: !disabledColumns.includes(ordersColumns.indexOf('Онлайн')),
|
||||||
|
name: !disabledColumns.includes(ordersColumns.indexOf('Имя')),
|
||||||
|
vendor: !disabledColumns.includes(ordersColumns.indexOf('Производитель')),
|
||||||
|
ip: !disabledColumns.includes(ordersColumns.indexOf('IP адрес')),
|
||||||
|
ports: !disabledColumns.includes(ordersColumns.indexOf('Открытые порты')),
|
||||||
|
activity: !disabledColumns.includes(ordersColumns.indexOf('Последняя активность')),
|
||||||
|
note: !disabledColumns.includes(ordersColumns.indexOf('Заметка')),
|
||||||
|
action: !disabledColumns.includes(ordersColumns.indexOf('Действие'))
|
||||||
|
};
|
||||||
|
}, [ordersColumns, disabledColumns]);
|
||||||
|
const sortedColumns = useMemo(() => {
|
||||||
|
return ordersColumns.filter(column =>
|
||||||
|
!disabledColumns.includes(ordersColumns.indexOf(column))
|
||||||
|
);
|
||||||
|
}, [ordersColumns, disabledColumns]);
|
||||||
|
|
||||||
// Пагинация
|
// Пагинация
|
||||||
const paginateSetting = usePaginateSetting();
|
const paginateSetting = usePaginateSetting();
|
||||||
|
|
@ -106,31 +131,41 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<TableHeaderCell></TableHeaderCell>
|
{columnVisibility.status && <TableHeaderCell></TableHeaderCell>}
|
||||||
<TableHeaderCell>Имя</TableHeaderCell>
|
{columnVisibility.name && <TableHeaderCell>Имя</TableHeaderCell>}
|
||||||
<TableHeaderCell>Производитель</TableHeaderCell>
|
{columnVisibility.vendor && <TableHeaderCell>Производитель</TableHeaderCell>}
|
||||||
<TableHeaderCell>IP адрес</TableHeaderCell>
|
{columnVisibility.ip && <TableHeaderCell>IP адрес</TableHeaderCell>}
|
||||||
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
{columnVisibility.ports && <TableHeaderCell>Открытые порты</TableHeaderCell>}
|
||||||
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
{columnVisibility.activity && <TableHeaderCell>Последняя активность</TableHeaderCell>}
|
||||||
<TableHeaderCell>Заметка</TableHeaderCell>
|
{columnVisibility.note && <TableHeaderCell>Заметка</TableHeaderCell>}
|
||||||
<TableHeaderCell></TableHeaderCell>
|
{columnVisibility.action && <TableHeaderCell></TableHeaderCell>}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{currentDevices.map((item, i) => (
|
{currentDevices.map((item, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
{columnVisibility.status && (
|
||||||
<TableCell>{item.hostname || (nameSetting === 'ip' ? item.ip : 'Неизвестно')}</TableCell>
|
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||||
<TableCell>{item.vendor}</TableCell>
|
)}
|
||||||
<TableCell>{item.ip}</TableCell>
|
{columnVisibility.name && (
|
||||||
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
|
||||||
<TableCell>{item.last_seen}</TableCell>
|
|
||||||
<TableCell>{item.note || '-'}</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<button onClick={(e)=>openModalNote(e, item)}>
|
{item.hostname || (nameSetting === 'ip' ? item.ip : 'Неизвестно')}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{columnVisibility.vendor && <TableCell>{item.vendor}</TableCell>}
|
||||||
|
{columnVisibility.ip && <TableCell>{item.ip}</TableCell>}
|
||||||
|
{columnVisibility.ports && (
|
||||||
|
<TableCell>{item.open_ports.join(', ') || '-'}</TableCell>
|
||||||
|
)}
|
||||||
|
{columnVisibility.activity && <TableCell>{item.last_seen}</TableCell>}
|
||||||
|
{columnVisibility.note && <TableCell>{item.note || '-'}</TableCell>}
|
||||||
|
{columnVisibility.action && (
|
||||||
|
<TableCell>
|
||||||
|
<button onClick={(e) => openModalNote(e, item)}>
|
||||||
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
||||||
</button>
|
</button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ export default function UserScanner() {
|
||||||
}
|
}
|
||||||
setScanInitiated(true);
|
setScanInitiated(true);
|
||||||
const res = await NetworkService.scanUser(ipAddress);
|
const res = await NetworkService.scanUser(ipAddress);
|
||||||
console.log(res);
|
console.log(res)
|
||||||
setDevices(res.data.devices || []);
|
setDevices(res.data.devices || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Ошибка сканирования сети:", err);
|
console.error("Ошибка сканирования сети:", err);
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
||||||
|
|
||||||
export function WifiScanner() {
|
export function WifiScanner() {
|
||||||
//const { ipAddress } = useIpStore();
|
//const { ipAddress } = useIpStore();
|
||||||
const ipAddress = '192.168.1.147'
|
const ipAddress = '192.168.1.148'
|
||||||
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();
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
||||||
setHasInited(false)
|
setHasInited(false)
|
||||||
initializationAttempt.current = 0;
|
initializationAttempt.current = 0;
|
||||||
|
|
||||||
const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
|
const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
|
||||||
|
|
||||||
let connectionTimeout;
|
let connectionTimeout;
|
||||||
setSocket(newSocket);
|
setSocket(newSocket);
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export const ToolBar = () => {
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<ThemeButton />
|
{/*<ThemeButton />*/}
|
||||||
|
|
||||||
<Button>
|
<Button>
|
||||||
<IconContainer>
|
<IconContainer>
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export const DevicesBar = () => {
|
||||||
|
|
||||||
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`);
|
const testSocket = new WebSocket(`ws://${ip}:8001/api/v1`);
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
testSocket.close();
|
testSocket.close();
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
|
|
||||||
4
FRONT/DEV/src/electron.d.ts
vendored
4
FRONT/DEV/src/electron.d.ts
vendored
|
|
@ -2,4 +2,8 @@ interface Window {
|
||||||
electron: {
|
electron: {
|
||||||
quitApp: () => void;
|
quitApp: () => void;
|
||||||
};
|
};
|
||||||
|
electronAPI: {
|
||||||
|
showSaveDialog: (options: Electron.SaveDialogOptions) => Promise<Electron.SaveDialogReturnValue>;
|
||||||
|
writeFile: (path: string, data: string) => Promise<{ success: boolean }>;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,11 @@ export const usePaginateSetting = () => {
|
||||||
return useSettingsStore(state => state.getNumberSetting('paginate-nodes', 20));
|
return useSettingsStore(state => state.getNumberSetting('paginate-nodes', 20));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useThemeSettings = () => {
|
||||||
|
return useSettingsStore(state => state.getSettingValue('theme'));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Интервальное сканирование под узлов
|
||||||
export const useIntervalBoolean = () => {
|
export const useIntervalBoolean = () => {
|
||||||
return useSettingsStore(state => state.getSettingValue('rescan-nodes')) as boolean;
|
return useSettingsStore(state => state.getSettingValue('rescan-nodes')) as boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -15,6 +20,15 @@ export const useIntervalTime = () => {
|
||||||
return useSettingsStore(state => state.getNumberSetting('interval-nodes', 60));
|
return useSettingsStore(state => state.getNumberSetting('interval-nodes', 60));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Кастомизация таблиц
|
||||||
|
export const useOrdersColumns = () => {
|
||||||
|
return useSettingsStore(state => state.getSettingValue('table-columns-order')) as string[];
|
||||||
|
};
|
||||||
|
export const useDisabledColumns = () => {
|
||||||
|
return useSettingsStore(state => state.getSettingValue('table-columns-disabled')) as number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useThemeSetting = () => {
|
export const useThemeSetting = () => {
|
||||||
return useSettingsStore(state => state.getSettingValue('theme')) as 'light' | 'dark' | 'system';
|
return useSettingsStore(state => state.getSettingValue('theme')) as 'light' | 'dark' | 'system';
|
||||||
};
|
};
|
||||||
|
|
@ -9,7 +9,7 @@ interface IpAddressState {
|
||||||
export const useIpStore = create<IpAddressState>()(
|
export const useIpStore = create<IpAddressState>()(
|
||||||
persist(
|
persist(
|
||||||
(set)=>({
|
(set)=>({
|
||||||
ipAddress: '192:168.1.147',
|
ipAddress: '192:168.1.0',
|
||||||
setIpAddress: (newIp: string) => set({ ipAddress: newIp }),
|
setIpAddress: (newIp: string) => set({ ipAddress: newIp }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,25 @@
|
||||||
export const networkSettings = [
|
import {useIpStore} from "../../ipStore";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
|
||||||
|
const createNetworkSettings = () => {
|
||||||
|
const { ipAddress } = useIpStore.getState();
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
id: 'clean-network',
|
id: 'clean-network',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
label: 'Очистить данные сессии',
|
label: 'Очистить данные сессии',
|
||||||
sectionId: 'Точки доступа',
|
sectionId: 'Точки доступа',
|
||||||
defaultValue: 'Очистить',
|
defaultValue: 'Очистить',
|
||||||
|
action: async ()=>{
|
||||||
|
try {
|
||||||
|
const res = await NetworkService.clearNetwork(ipAddress);
|
||||||
|
console.log(res);
|
||||||
|
}catch (e){
|
||||||
|
console.error(e);
|
||||||
|
alert('Данные не были удалены: '+ e)
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'export-network',
|
id: 'export-network',
|
||||||
|
|
@ -13,4 +28,7 @@ export const networkSettings = [
|
||||||
sectionId: 'Точки доступа',
|
sectionId: 'Точки доступа',
|
||||||
defaultValue: 'Экспортировать',
|
defaultValue: 'Экспортировать',
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const networkSettings = createNetworkSettings();
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
export const nodesSettings = [
|
import {useIpStore} from "../../ipStore";
|
||||||
|
import {NetworkService} from "../../../api/request";
|
||||||
|
|
||||||
|
|
||||||
|
const createNodesSettings = () => {
|
||||||
|
const { ipAddress } = useIpStore.getState();
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
id: 'rescan-nodes',
|
id: 'rescan-nodes',
|
||||||
sectionId: 'Узлы подсети',
|
sectionId: 'Узлы подсети',
|
||||||
|
|
@ -47,13 +54,67 @@ export const nodesSettings = [
|
||||||
label: 'Очистить данные сессии',
|
label: 'Очистить данные сессии',
|
||||||
sectionId: 'Узлы подсети',
|
sectionId: 'Узлы подсети',
|
||||||
defaultValue: 'Очистить',
|
defaultValue: 'Очистить',
|
||||||
|
action: async ()=>{
|
||||||
|
try {
|
||||||
|
const res = await NetworkService.clearSubnet(ipAddress);
|
||||||
|
console.log(res)
|
||||||
|
}catch (e){
|
||||||
|
console.error(e);
|
||||||
|
alert('Данные не были удалены: '+ e)
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'export-nodes',
|
id: "export-nodes",
|
||||||
type: 'button',
|
type: "button",
|
||||||
label: 'Экспорт данных узлов',
|
label: "Экспорт данных узлов",
|
||||||
sectionId: 'Узлы подсети',
|
sectionId: "Узлы подсети",
|
||||||
defaultValue: 'Экспортировать',
|
defaultValue: "Экспортировать",
|
||||||
},
|
action: async () => {
|
||||||
] as const;
|
try {
|
||||||
|
const response = await NetworkService.exportDevices(ipAddress);
|
||||||
|
const { filePath } = await window.electronAPI.showSaveDialog({
|
||||||
|
title: "Сохранить данные узлов",
|
||||||
|
defaultPath: "nodes_export.json",
|
||||||
|
filters: [{ name: "JSON", extensions: ["json"] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
await window.electronAPI.writeFile(
|
||||||
|
filePath,
|
||||||
|
JSON.stringify(response.data, null, 2)
|
||||||
|
);
|
||||||
|
alert("Данные успешно экспортированы!");
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
alert("Ошибка экспорта: " + e.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'table-columns-order',
|
||||||
|
type: 'hidden',
|
||||||
|
label: '',
|
||||||
|
sectionId: 'Узлы подсети',
|
||||||
|
defaultValue: [
|
||||||
|
'Онлайн',
|
||||||
|
'Имя',
|
||||||
|
'Производитель',
|
||||||
|
'IP адрес',
|
||||||
|
'Открытые порты',
|
||||||
|
'Последняя активность',
|
||||||
|
'Заметка',
|
||||||
|
'Действие'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'table-columns-disabled',
|
||||||
|
label: '',
|
||||||
|
sectionId: 'Узлы подсети',
|
||||||
|
type: 'hidden',
|
||||||
|
defaultValue: []
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nodesSettings = createNodesSettings();
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,9 @@ import {appearanceSettings} from "./config/appearanceSettings";
|
||||||
type SettingValue = any;
|
type SettingValue = any;
|
||||||
type SettingsDictionary = Record<string, SettingValue>;
|
type SettingsDictionary = Record<string, SettingValue>;
|
||||||
|
|
||||||
interface SettingOption {
|
|
||||||
readonly value: any;
|
|
||||||
readonly label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Setting = {
|
export type Setting = {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly type: 'boolean' | 'select' | 'text' | 'number' | 'button';
|
readonly type: 'boolean' | 'select' | 'text' | 'number' | 'button' | 'hidden';
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
readonly defaultValue: any;
|
readonly defaultValue: any;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { ThemeProvider as StyledTheme } from 'styled-components';
|
||||||
import { light, dark} from '../const/theme/theme';
|
import { light, dark} from '../const/theme/theme';
|
||||||
import { GlobalStyle } from '../const/theme/globalStyles';
|
import { GlobalStyle } from '../const/theme/globalStyles';
|
||||||
import { themeNames, ThemName } from '../const/theme/names';
|
import { themeNames, ThemName } from '../const/theme/names';
|
||||||
|
import {useThemeSettings} from "../hooks/useSettings";
|
||||||
|
|
||||||
type ThemeContextType = {
|
type ThemeContextType = {
|
||||||
theme: ThemName;
|
theme: ThemName;
|
||||||
toggleTheme: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
|
|
@ -23,32 +23,13 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre
|
||||||
const getSystemTheme = () =>
|
const getSystemTheme = () =>
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNames[2] : themeNames[1];
|
window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNames[2] : themeNames[1];
|
||||||
|
|
||||||
const getInitialTheme = () => {
|
const getInitialTheme = useThemeSettings();
|
||||||
let savedTheme = localStorage.getItem('theme') as ThemName;
|
|
||||||
if (!savedTheme) {
|
|
||||||
savedTheme = themeNames[3]; // Устанавливаем значение по умолчанию
|
|
||||||
localStorage.setItem('theme', savedTheme); // Сохраняем без JSON.stringify
|
|
||||||
}
|
|
||||||
return savedTheme;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [theme, setTheme] = useState<ThemName>(getInitialTheme);
|
const [theme, setTheme] = useState<ThemName>(getInitialTheme);
|
||||||
|
|
||||||
const toggleTheme = () => {
|
useEffect(() => {
|
||||||
setTheme((prev) => {
|
setTheme(getInitialTheme)
|
||||||
let newTheme: ThemName;
|
}, [getInitialTheme]);
|
||||||
if (prev === themeNames[3]) {
|
|
||||||
newTheme = themeNames[1];
|
|
||||||
} else if (prev === themeNames[1]) {
|
|
||||||
newTheme = themeNames[2];
|
|
||||||
} else {
|
|
||||||
newTheme = themeNames[3];
|
|
||||||
}
|
|
||||||
localStorage.setItem('theme', newTheme);
|
|
||||||
|
|
||||||
return newTheme;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
|
@ -68,8 +49,8 @@ export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ childre
|
||||||
const currentTheme = theme === themeNames[3] ? getSystemTheme() : theme;
|
const currentTheme = theme === themeNames[3] ? getSystemTheme() : theme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
<ThemeContext.Provider value={{ theme }}>
|
||||||
<StyledTheme theme={{ ...currentTheme === themeNames[1] ? light : dark, toggleTheme }}>
|
<StyledTheme theme={{ ...currentTheme === themeNames[1] ? light : dark }}>
|
||||||
<GlobalStyle />
|
<GlobalStyle />
|
||||||
{children}
|
{children}
|
||||||
</StyledTheme>
|
</StyledTheme>
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@ app.include_router(router)
|
||||||
network_service = NetworkService()
|
network_service = NetworkService()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run("main:app", host='0.0.0.0', port=8001, reload=True, ssl_keyfile="/etc/gonets/certs/gonets.key", ssl_certfile="/etc/gonets/certs/gonets.crt")
|
#uvicorn.run("main:app", host='0.0.0.0', port=8001, reload=True, ssl_keyfile="/etc/gonets/certs/gonets.key", ssl_certfile="/etc/gonets/certs/gonets.crt")
|
||||||
#uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True)
|
uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue