Добавление настроек и их функциональности
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
|
|
@ -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 {DisconnectConfirm} from "./modalContainers/disconnectConfirm";
|
||||
import {Settings} from "../settings/Settings";
|
||||
import CustomizeNodes from "./modalContainers/customizeNodes";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean,
|
||||
|
|
@ -80,6 +81,9 @@ export default function WindowComponent({ isOpen, type, data }: Props) {
|
|||
onClose={closeLoader}
|
||||
/>
|
||||
)}
|
||||
{type === ModalMap['11'] && (
|
||||
<CustomizeNodes />
|
||||
)}
|
||||
</ModalWindow >
|
||||
}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export const ModalTitle = styled.div`
|
|||
padding: 20px;
|
||||
border-bottom: 1px solid #9D9D9D;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
h3{
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ export const ModalMap: ModalType = {
|
|||
'7': 'noteWifi',
|
||||
'8': 'connectNetwork',
|
||||
'9': 'disconnectConfirm',
|
||||
"10": 'settings',
|
||||
'11': 'customNodes',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import {buttonNames} from "../../const/buttons/names";
|
|||
import SettingSelect from "./SettingSelect";
|
||||
import {MainButton} from "../UI/buttons/mainButton";
|
||||
import React from 'react';
|
||||
import {useModalContext} from "../../store";
|
||||
import {ModalMap} from "../modalwindows/windowsType";
|
||||
|
||||
const SettingBlock = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -19,6 +21,7 @@ const SettingBlock = styled.div`
|
|||
font-size: 16px;
|
||||
outline: none;
|
||||
background-color: ${props => props.theme.primary};
|
||||
color: ${props => props.theme.textColor};
|
||||
border: 1px solid ${props => props.theme.borderColor};
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
|
|
@ -73,7 +76,7 @@ const InputWrapper = styled.div`
|
|||
gap: 6px;
|
||||
`
|
||||
const ErrorText = styled.div`
|
||||
color: red;
|
||||
color: ${props => props.theme.errorColor};
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
|
|
@ -87,7 +90,17 @@ interface SettingItemProps {
|
|||
const SettingItem = React.memo(({ setting, value, onChange, onValidationError }: SettingItemProps) => {
|
||||
const theme = useTheme();
|
||||
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 inputValue = e.target.value;
|
||||
let newError: string | null = null;
|
||||
|
|
@ -172,9 +185,10 @@ const SettingItem = React.memo(({ setting, value, onChange, onValidationError }:
|
|||
type="button"
|
||||
size="small"
|
||||
title={setting.defaultValue || ''}
|
||||
onClick={setting.action}
|
||||
onClick={handleButtonAction}
|
||||
/>
|
||||
)}
|
||||
{setting.type === 'hidden' && null}
|
||||
</SettingBlock>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import {Setting, useSettingsStore} from "../../store/settings/settingsStore";
|
|||
import {SettingItem} from "./SettingItem";
|
||||
import {IconComponent} from "../../const/buttons/icons";
|
||||
import {buttonNames} from "../../const/buttons/names";
|
||||
import {ModalMap} from "../modalwindows/windowsType";
|
||||
import {workSpaceNames} from "../../const/workspace/names";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
|
|
@ -129,7 +127,7 @@ export const Settings = ({ onClose }: Props) => {
|
|||
}, [discardChanges, onClose]);
|
||||
|
||||
return (
|
||||
<ModalContainer style={{ height: '60vh', width: '50vw'}}>
|
||||
<ModalContainer style={{ height: '60vh', width: '50vw', minWidth: "800px" }}>
|
||||
<ModalTitle>
|
||||
<h3>Настройки</h3>
|
||||
<CloseBtn>
|
||||
|
|
@ -168,6 +166,13 @@ export const Settings = ({ onClose }: Props) => {
|
|||
</ModalContent>
|
||||
|
||||
<ButtonContainer>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
title='Отменить'
|
||||
onClick={handleCancel}
|
||||
|
||||
/>
|
||||
<MainButton
|
||||
type="button"
|
||||
size="small"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useMemo, useState} 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";
|
||||
import Pagination from "../../../../../UI/Pagination";
|
||||
import {useSettingsStore} from "../../../../../../store/settings/settingsStore";
|
||||
import {useNameSetting, usePaginateSetting} from "../../../../../../hooks/useSettings";
|
||||
import {
|
||||
useDisabledColumns,
|
||||
useNameSetting,
|
||||
useOrdersColumns,
|
||||
usePaginateSetting
|
||||
} from "../../../../../../hooks/useSettings";
|
||||
|
||||
interface TableScannerProps {
|
||||
devices: DeviceUser[];
|
||||
}
|
||||
|
||||
const TableContainer = styled.div`
|
||||
width: 100%;
|
||||
|
|
@ -67,6 +71,7 @@ const StatusBadge = styled.span<{ status:string }>`
|
|||
|
||||
const TableScanner = ({devices}:TableScannerProps) => {
|
||||
const { openModal } = useModalContext();
|
||||
const nameSetting = useNameSetting();
|
||||
|
||||
const openModalNote = (e: React.MouseEvent, device:DeviceUser ) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -75,7 +80,27 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
|||
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();
|
||||
|
|
@ -105,32 +130,42 @@ const TableScanner = ({devices}:TableScannerProps) => {
|
|||
<TableContainer>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
<TableHeaderCell>Имя</TableHeaderCell>
|
||||
<TableHeaderCell>Производитель</TableHeaderCell>
|
||||
<TableHeaderCell>IP адрес</TableHeaderCell>
|
||||
<TableHeaderCell>Открытые порты</TableHeaderCell>
|
||||
<TableHeaderCell>Последняя активность</TableHeaderCell>
|
||||
<TableHeaderCell>Заметка</TableHeaderCell>
|
||||
<TableHeaderCell></TableHeaderCell>
|
||||
</tr>
|
||||
<tr>
|
||||
{columnVisibility.status && <TableHeaderCell></TableHeaderCell>}
|
||||
{columnVisibility.name && <TableHeaderCell>Имя</TableHeaderCell>}
|
||||
{columnVisibility.vendor && <TableHeaderCell>Производитель</TableHeaderCell>}
|
||||
{columnVisibility.ip && <TableHeaderCell>IP адрес</TableHeaderCell>}
|
||||
{columnVisibility.ports && <TableHeaderCell>Открытые порты</TableHeaderCell>}
|
||||
{columnVisibility.activity && <TableHeaderCell>Последняя активность</TableHeaderCell>}
|
||||
{columnVisibility.note && <TableHeaderCell>Заметка</TableHeaderCell>}
|
||||
{columnVisibility.action && <TableHeaderCell></TableHeaderCell>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentDevices.map((item, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell><StatusBadge status={item.status}></StatusBadge></TableCell>
|
||||
<TableCell>{item.hostname || (nameSetting === 'ip' ? 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>
|
||||
<button onClick={(e)=>openModalNote(e, item)}>
|
||||
<IconComponent name={buttonNames[16]} width="26" height="26" />
|
||||
</button>
|
||||
</TableCell>
|
||||
{columnVisibility.status && (
|
||||
<TableCell><StatusBadge status={item.status} /></TableCell>
|
||||
)}
|
||||
{columnVisibility.name && (
|
||||
<TableCell>
|
||||
{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" />
|
||||
</button>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export default function UserScanner() {
|
|||
}
|
||||
setScanInitiated(true);
|
||||
const res = await NetworkService.scanUser(ipAddress);
|
||||
console.log(res);
|
||||
console.log(res)
|
||||
setDevices(res.data.devices || []);
|
||||
} catch (err) {
|
||||
console.error("Ошибка сканирования сети:", err);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ const ScanBtn = styled.button<{scanInitiated: boolean}>`
|
|||
|
||||
export function WifiScanner() {
|
||||
//const { ipAddress } = useIpStore();
|
||||
const ipAddress = '192.168.1.147'
|
||||
const ipAddress = '192.168.1.148'
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [scanInitiated, setScanInitiated] = useState(false);
|
||||
const { wifiNetworks, setWifiNetworks } = useNetworkStore();
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const NetworkInterfaces = ({ startConnection, ws }: Props) => {
|
|||
setHasInited(false)
|
||||
initializationAttempt.current = 0;
|
||||
|
||||
const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`);
|
||||
const newSocket = new WebSocket(`ws://${ws}:8001/api/v1`);
|
||||
|
||||
let connectionTimeout;
|
||||
setSocket(newSocket);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const ToolBar = () => {
|
|||
</IconContainer>
|
||||
</Button>
|
||||
|
||||
<ThemeButton />
|
||||
{/*<ThemeButton />*/}
|
||||
|
||||
<Button>
|
||||
<IconContainer>
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export const DevicesBar = () => {
|
|||
|
||||
const checkWebSocketAvailability = async (ip: string): Promise<boolean> => {
|
||||
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(() => {
|
||||
testSocket.close();
|
||||
resolve(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue