forked from VinokurovVE/tests
Servers: servers, ips, hw, storages
This commit is contained in:
@ -1,14 +1,33 @@
|
||||
import { Box } from '@mui/material'
|
||||
import { IServer } from '../interfaces/servers'
|
||||
import { useServerIps } from '../hooks/swrHooks'
|
||||
import FullFeaturedCrudGrid from './TableEditable'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
|
||||
function ServerData({ id, name, region_id }: IServer) {
|
||||
const { serverIps } = useServerIps(id, 0, 10)
|
||||
|
||||
const serverIpsColumns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: 'number' },
|
||||
{ field: 'server_id', headerName: 'Server ID', type: 'number' },
|
||||
{ field: 'name', headerName: 'Название', type: 'string' },
|
||||
{ field: 'is_actual', headerName: 'Действителен', type: 'boolean' },
|
||||
{ field: 'ip', headerName: 'IP', type: 'string' },
|
||||
{ field: 'servername', headerName: 'Название сервера', type: 'string' },
|
||||
]
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', p: '16px' }}>
|
||||
{serverIps &&
|
||||
JSON.stringify(serverIps)
|
||||
<FullFeaturedCrudGrid
|
||||
initialRows={serverIps}
|
||||
columns={serverIpsColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
//setCurrentServerData(params.row)
|
||||
//setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
)
|
||||
|
146
frontend_reactjs/src/components/ServerHardware.tsx
Normal file
146
frontend_reactjs/src/components/ServerHardware.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useHardwares, useRegions, useServerIps, useServers } from '../hooks/swrHooks'
|
||||
import FullFeaturedCrudGrid from './TableEditable'
|
||||
import ServerService from '../services/ServersService'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Close } from '@mui/icons-material'
|
||||
import ServerData from './ServerData'
|
||||
|
||||
export default function ServerHardware() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [options, setOptions] = useState<IRegion[]>([])
|
||||
const [search, setSearch] = useState<string | null>("")
|
||||
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
|
||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
||||
const { servers, isLoading } = useServers()
|
||||
|
||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearch(search)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
if (servers) {
|
||||
setOptions([...servers])
|
||||
}
|
||||
}, [servers])
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearch(value)
|
||||
}
|
||||
|
||||
const handleOptionChange = (value: IRegion | null) => {
|
||||
setSelectedOption(value)
|
||||
}
|
||||
|
||||
const { hardwares, isLoading: serversLoading } = useHardwares(selectedOption?.id, 0, 10)
|
||||
|
||||
const hardwareColumns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: 'number' },
|
||||
{ field: 'name', headerName: 'Название', type: 'string' },
|
||||
{ field: 'server_id', headerName: 'Server ID', type: 'number' },
|
||||
{ field: 'servername', headerName: 'Название сервера', type: 'string' },
|
||||
{ field: 'os_info', headerName: 'ОС', type: 'string' },
|
||||
{ field: 'ram', headerName: 'ОЗУ', type: 'string' },
|
||||
{ field: 'processor', headerName: 'Проц.', type: 'string' },
|
||||
{ field: 'storages_count', headerName: 'Кол-во хранилищ', type: 'number' },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={serverDataOpen}
|
||||
onClose={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description">
|
||||
<AppBar sx={{ position: 'sticky' }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-label="close"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{currentServerData &&
|
||||
<ServerData
|
||||
id={currentServerData?.id}
|
||||
region_id={currentServerData?.region_id}
|
||||
name={currentServerData?.name}
|
||||
/>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Autocomplete
|
||||
open={open}
|
||||
onOpen={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleInputChange(value)}
|
||||
onChange={(_, value) => handleOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||
options={options}
|
||||
loading={isLoading}
|
||||
value={selectedOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Сервер"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{serversLoading ?
|
||||
<CircularProgress />
|
||||
:
|
||||
servers &&
|
||||
<FullFeaturedCrudGrid
|
||||
onSave={(id: any) => {
|
||||
console.log(id)
|
||||
}}
|
||||
onDelete={ServerService.removeServer}
|
||||
initialRows={hardwares}
|
||||
columns={hardwareColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
setCurrentServerData(params.row)
|
||||
setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
144
frontend_reactjs/src/components/ServerIpsView.tsx
Normal file
144
frontend_reactjs/src/components/ServerIpsView.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useRegions, useServerIps, useServers } from '../hooks/swrHooks'
|
||||
import FullFeaturedCrudGrid from './TableEditable'
|
||||
import ServerService from '../services/ServersService'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Close } from '@mui/icons-material'
|
||||
import ServerData from './ServerData'
|
||||
|
||||
export default function ServerIpsView() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [options, setOptions] = useState<IRegion[]>([])
|
||||
const [search, setSearch] = useState<string | null>("")
|
||||
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
|
||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
||||
const { servers, isLoading } = useServers()
|
||||
|
||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearch(search)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
if (servers) {
|
||||
setOptions([...servers])
|
||||
}
|
||||
}, [servers])
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearch(value)
|
||||
}
|
||||
|
||||
const handleOptionChange = (value: IRegion | null) => {
|
||||
setSelectedOption(value)
|
||||
}
|
||||
|
||||
const { serverIps, isLoading: serversLoading } = useServerIps(selectedOption?.id, 0, 10)
|
||||
|
||||
const serverIpsColumns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: 'number' },
|
||||
{ field: 'server_id', headerName: 'Server ID', type: 'number' },
|
||||
{ field: 'name', headerName: 'Название', type: 'string' },
|
||||
{ field: 'is_actual', headerName: 'Действителен', type: 'boolean' },
|
||||
{ field: 'ip', headerName: 'IP', type: 'string' },
|
||||
{ field: 'servername', headerName: 'Название сервера', type: 'string' },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={serverDataOpen}
|
||||
onClose={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description">
|
||||
<AppBar sx={{ position: 'sticky' }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-label="close"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{currentServerData &&
|
||||
<ServerData
|
||||
id={currentServerData?.id}
|
||||
region_id={currentServerData?.region_id}
|
||||
name={currentServerData?.name}
|
||||
/>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Autocomplete
|
||||
open={open}
|
||||
onOpen={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleInputChange(value)}
|
||||
onChange={(_, value) => handleOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||
options={options}
|
||||
loading={isLoading}
|
||||
value={selectedOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Сервер"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{serversLoading ?
|
||||
<CircularProgress />
|
||||
:
|
||||
servers &&
|
||||
<FullFeaturedCrudGrid
|
||||
onSave={(id: any) => {
|
||||
console.log(id)
|
||||
}}
|
||||
onDelete={ServerService.removeServer}
|
||||
initialRows={serverIps}
|
||||
columns={serverIpsColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
setCurrentServerData(params.row)
|
||||
setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
143
frontend_reactjs/src/components/ServerStorages.tsx
Normal file
143
frontend_reactjs/src/components/ServerStorages.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import { AppBar, Autocomplete, CircularProgress, Dialog, IconButton, TextField, Toolbar } from '@mui/material'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useHardwares, useRegions, useServerIps, useServers, useStorages } from '../hooks/swrHooks'
|
||||
import FullFeaturedCrudGrid from './TableEditable'
|
||||
import ServerService from '../services/ServersService'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Close } from '@mui/icons-material'
|
||||
import ServerData from './ServerData'
|
||||
|
||||
export default function ServerStorage() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [options, setOptions] = useState<IRegion[]>([])
|
||||
const [search, setSearch] = useState<string | null>("")
|
||||
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
|
||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
||||
const { hardwares, isLoading } = useHardwares()
|
||||
|
||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearch(search)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
if (hardwares) {
|
||||
setOptions([...hardwares])
|
||||
}
|
||||
}, [hardwares])
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearch(value)
|
||||
}
|
||||
|
||||
const handleOptionChange = (value: IRegion | null) => {
|
||||
setSelectedOption(value)
|
||||
}
|
||||
|
||||
const { storages, isLoading: serversLoading } = useStorages(selectedOption?.id, 0, 10)
|
||||
|
||||
const storageColumns: GridColDef[] = [
|
||||
{ field: 'id', headerName: 'ID', type: 'number' },
|
||||
{ field: 'hardware_id', headerName: 'Hardware ID', type: 'number' },
|
||||
{ field: 'name', headerName: 'Название', type: 'string' },
|
||||
{ field: 'size', headerName: 'Размер', type: 'string' },
|
||||
{ field: 'storage_type', headerName: 'Тип хранилища', type: 'string' },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={serverDataOpen}
|
||||
onClose={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description">
|
||||
<AppBar sx={{ position: 'sticky' }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-label="close"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{currentServerData &&
|
||||
<ServerData
|
||||
id={currentServerData?.id}
|
||||
region_id={currentServerData?.region_id}
|
||||
name={currentServerData?.name}
|
||||
/>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Autocomplete
|
||||
open={open}
|
||||
onOpen={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleInputChange(value)}
|
||||
onChange={(_, value) => handleOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||
options={options}
|
||||
loading={isLoading}
|
||||
value={selectedOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Hardware"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{serversLoading ?
|
||||
<CircularProgress />
|
||||
:
|
||||
storages &&
|
||||
<FullFeaturedCrudGrid
|
||||
onSave={(id: any) => {
|
||||
console.log(id)
|
||||
}}
|
||||
onDelete={ServerService.removeServer}
|
||||
initialRows={storages}
|
||||
columns={storageColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
setCurrentServerData(params.row)
|
||||
setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
191
frontend_reactjs/src/components/ServersView.tsx
Normal file
191
frontend_reactjs/src/components/ServersView.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { AppBar, Autocomplete, Box, Chip, CircularProgress, Dialog, Divider, Grid, IconButton, Paper, TextField, Toolbar, Typography } from '@mui/material'
|
||||
import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { IRegion } from '../interfaces/fuel'
|
||||
import { useRegions, useServers, useServersInfo } from '../hooks/swrHooks'
|
||||
import FullFeaturedCrudGrid from './TableEditable'
|
||||
import ServerService from '../services/ServersService'
|
||||
import { GridColDef } from '@mui/x-data-grid'
|
||||
import { Close, Cloud, CloudOff } from '@mui/icons-material'
|
||||
import ServerData from './ServerData'
|
||||
import { IServersInfo } from '../interfaces/servers'
|
||||
|
||||
export default function ServersView() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [options, setOptions] = useState<IRegion[]>([])
|
||||
const [search, setSearch] = useState<string | null>("")
|
||||
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
|
||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
||||
const { regions, isLoading } = useRegions(10, 1, debouncedSearch)
|
||||
|
||||
const { serversInfo } = useServersInfo(selectedOption?.id)
|
||||
|
||||
|
||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearch(search)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
if (regions) {
|
||||
setOptions([...regions])
|
||||
}
|
||||
}, [regions])
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearch(value)
|
||||
}
|
||||
|
||||
const handleOptionChange = (value: IRegion | null) => {
|
||||
setSelectedOption(value)
|
||||
}
|
||||
|
||||
const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10)
|
||||
|
||||
const serversColumns: GridColDef[] = [
|
||||
//{ field: 'id', headerName: 'ID', type: "number" },
|
||||
{ field: 'name', headerName: 'Название', type: "string", editable: true },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={serverDataOpen}
|
||||
onClose={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description">
|
||||
<AppBar sx={{ position: 'sticky' }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-label="close"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{currentServerData &&
|
||||
<ServerData
|
||||
id={currentServerData?.id}
|
||||
region_id={currentServerData?.region_id}
|
||||
name={currentServerData?.name}
|
||||
/>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Autocomplete
|
||||
open={open}
|
||||
onOpen={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleInputChange(value)}
|
||||
onChange={(_, value) => handleOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||
options={options}
|
||||
loading={isLoading}
|
||||
value={selectedOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Район"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{servers &&
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
||||
{serversInfo &&
|
||||
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }}>
|
||||
{serversInfo.map((serverInfo: IServersInfo) => (
|
||||
<Grid key={`si-${serverInfo.id}`} item xs={1} sm={1} md={1}>
|
||||
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
||||
<Typography fontWeight={600}>
|
||||
{serverInfo.name}
|
||||
</Typography>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography>
|
||||
Количество IP:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{serverInfo.IPs_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography>
|
||||
Количество серверов:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{serverInfo.servers_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Chip
|
||||
icon={serverInfo.status === "Online" ? <Cloud /> : serverInfo.status === "Offline" ? <CloudOff /> : <CloudOff />}
|
||||
variant="outlined"
|
||||
label={serverInfo.status}
|
||||
color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
|
||||
{serversLoading ?
|
||||
<CircularProgress />
|
||||
:
|
||||
servers &&
|
||||
<FullFeaturedCrudGrid
|
||||
onSave={(id: any) => {
|
||||
console.log(id)
|
||||
}}
|
||||
onDelete={ServerService.removeServer}
|
||||
initialRows={servers}
|
||||
columns={serversColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
setCurrentServerData(params.row)
|
||||
setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
@ -23,6 +23,7 @@ import {
|
||||
GridRow,
|
||||
useGridApiContext,
|
||||
} from '@mui/x-data-grid';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
interface EditToolbarProps {
|
||||
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
|
||||
@ -71,14 +72,18 @@ interface DataGridProps {
|
||||
initialRows: GridRowsProp;
|
||||
columns: GridColDef[];
|
||||
actions: boolean;
|
||||
onRowClick: GridEventListener<"rowClick">
|
||||
onRowClick: GridEventListener<"rowClick">;
|
||||
onSave: any;
|
||||
onDelete: (data: any) => Promise<AxiosResponse<any, any>>;
|
||||
}
|
||||
|
||||
export default function FullFeaturedCrudGrid({
|
||||
initialRows,
|
||||
columns,
|
||||
actions = false,
|
||||
onRowClick
|
||||
onRowClick,
|
||||
onSave,
|
||||
onDelete
|
||||
}: DataGridProps) {
|
||||
const [rows, setRows] = React.useState(initialRows);
|
||||
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>({});
|
||||
@ -95,10 +100,13 @@ export default function FullFeaturedCrudGrid({
|
||||
|
||||
const handleSaveClick = (id: GridRowId) => () => {
|
||||
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
|
||||
onSave?.(id)
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id: GridRowId) => () => {
|
||||
setRows(rows.filter((row) => row.id !== id));
|
||||
onDelete?.(id).then(response => {
|
||||
})
|
||||
};
|
||||
|
||||
const handleCancelClick = (id: GridRowId) => () => {
|
||||
@ -196,7 +204,6 @@ export default function FullFeaturedCrudGrid({
|
||||
processRowUpdate={processRowUpdate}
|
||||
slots={{
|
||||
toolbar: EditToolbar as GridSlots['toolbar'],
|
||||
row: CustomRow as GridSlots['row']
|
||||
}}
|
||||
slotProps={{
|
||||
toolbar: { setRows, setRowModesModel, columns },
|
||||
@ -204,24 +211,4 @@ export default function FullFeaturedCrudGrid({
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomRow(props: GridRowProps) {
|
||||
const { id, row, rowId, ...other } = props;
|
||||
const apiRef = useGridApiContext();
|
||||
|
||||
// Custom styles or logic can go here
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="div"
|
||||
{...other}
|
||||
>
|
||||
{Object.entries(row).map(([field, value]) => (
|
||||
<Box key={field} sx={{ flex: 1, padding: '0 8px' }}>
|
||||
{apiRef.current.getCellParams(rowId, field).formattedValue as React.ReactNode}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -186,10 +186,11 @@ export function useBoilers(limit?: number, page?: number, search?: string) {
|
||||
|
||||
export function useServers(region_id?: number, offset?: number, limit?: number) {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
region_id ? `/api/servers?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
region_id ? `/api/servers?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/servers?offset=${offset || 0}&limit=${limit || 10}`,
|
||||
(url: string) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
@ -205,7 +206,8 @@ export function useServersInfo(region_id?: number, offset?: number, limit?: numb
|
||||
region_id ? `/api/servers_info?region_id=${region_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
@ -221,7 +223,8 @@ export function useServer(server_id?: number) {
|
||||
server_id ? `/api/server/${server_id}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
@ -232,12 +235,13 @@ export function useServer(server_id?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export function useServerIps(server_id?: number, offset?: number, limit?: number) {
|
||||
export function useServerIps(server_id?: number | null, offset?: number, limit?: number) {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
server_id ? `/api/server_ips?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
server_id ? `/api/server_ips?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/server_ips?offset=${offset || 0}&limit=${limit || 10}`,
|
||||
(url: string) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
@ -252,10 +256,11 @@ export function useServerIps(server_id?: number, offset?: number, limit?: number
|
||||
|
||||
export function useHardwares(server_id?: number, offset?: number, limit?: number) {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
server_id ? `/api/hardwares?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
server_id ? `/api/hardwares?server_id=${server_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/hardwares?offset=${offset || 0}&limit=${limit || 10}`,
|
||||
(url: string) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
@ -287,10 +292,11 @@ export function useHardware(hardware_id?: number) {
|
||||
|
||||
export function useStorages(hardware_id?: number, offset?: number, limit?: number) {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
hardware_id ? `/api/storages?hardware_id=${hardware_id}&offset=${offset || 0}&limit=${limit || 10}` : null,
|
||||
(url) => fetcher(url, BASE_URL.servers),
|
||||
hardware_id ? `/api/storages?hardware_id=${hardware_id}&offset=${offset || 0}&limit=${limit || 10}` : `/api/storages?offset=${offset || 0}&limit=${limit || 10}`,
|
||||
(url: string) => fetcher(url, BASE_URL.servers),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnMount: false
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1,267 +1,66 @@
|
||||
import { AppBar, Autocomplete, Box, Chip, CircularProgress, Dialog, Divider, Grid, IconButton, Paper, TextField, Toolbar, Typography, colors } from "@mui/material"
|
||||
import { useBoilers, useCities, useRegions, useServers, useServersInfo } from "../hooks/swrHooks"
|
||||
import { Fragment, useEffect, useState } from "react"
|
||||
import { IBoiler, ICity, IRegion } from "../interfaces/fuel"
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid"
|
||||
import ServerData from "../components/ServerData"
|
||||
import { IServer, IServersInfo } from "../interfaces/servers"
|
||||
import { Close, Cloud, CloudOff, Storage } from "@mui/icons-material"
|
||||
import { BarChart } from "@mui/x-charts"
|
||||
import FullFeaturedCrudGrid from "../components/TableEditable"
|
||||
import { Box, Tab, Tabs } from "@mui/material"
|
||||
import { useState } from "react"
|
||||
import ServersView from "../components/ServersView"
|
||||
import ServerIpsView from "../components/ServerIpsView"
|
||||
import ServerHardware from "../components/ServerHardware"
|
||||
import ServerStorage from "../components/ServerStorages"
|
||||
|
||||
export default function Servers() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [options, setOptions] = useState<IRegion[]>([])
|
||||
const [search, setSearch] = useState<string | null>(null)
|
||||
const [debouncedSearch, setDebouncedSearch] = useState<string | null>("")
|
||||
const [selectedOption, setSelectedOption] = useState<IRegion | null>(null)
|
||||
const { regions, isLoading } = useRegions(10, 1, debouncedSearch)
|
||||
const [currentTab, setCurrentTab] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedSearch(search)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
useEffect(() => {
|
||||
if (regions) {
|
||||
setOptions([...regions])
|
||||
}
|
||||
}, [regions])
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearch(value)
|
||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setCurrentTab(newValue);
|
||||
}
|
||||
|
||||
const handleOptionChange = (value: IRegion | null) => {
|
||||
setSelectedOption(value)
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
const [citiesOpen, setCitiesOpen] = useState(false)
|
||||
const [citiesPage, setCitiesPage] = useState(1)
|
||||
const [citiesSearch, setCitiesSearch] = useState('')
|
||||
const [debouncedCitySearch, setDebouncedCitySearch] = useState('')
|
||||
const { cities, isLoading: citiesLoading } = useCities(10, citiesPage, debouncedCitySearch)
|
||||
const [citiesOptions, setCitiesOptions] = useState<ICity[]>([])
|
||||
const [selectedCityOption, setSelectedCityOption] = useState<ICity | null>(null)
|
||||
function CustomTabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
const handleCityInputChange = (value: string) => {
|
||||
setCitiesSearch(value)
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleCityOptionChange = (value: ICity | null) => {
|
||||
setSelectedCityOption(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (cities) {
|
||||
setCitiesOptions([...cities])
|
||||
}
|
||||
}, [cities])
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedCitySearch(citiesSearch)
|
||||
}, 500)
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
}
|
||||
}, [citiesSearch])
|
||||
|
||||
useEffect(() => {
|
||||
setCitiesPage(1)
|
||||
setCitiesSearch("")
|
||||
}, [])
|
||||
|
||||
const { servers, isLoading: serversLoading } = useServers(selectedOption?.id, 0, 10)
|
||||
|
||||
const serversColumns: GridColDef[] = [
|
||||
//{ field: 'id', headerName: 'ID', type: "number" },
|
||||
{ field: 'name', headerName: 'Название', type: "string", editable: true },
|
||||
]
|
||||
|
||||
const [serverDataOpen, setServerDataOpen] = useState(false)
|
||||
const [currentServerData, setCurrentServerData] = useState<any | null>(null)
|
||||
|
||||
const { serversInfo } = useServersInfo(selectedOption?.id)
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={serverDataOpen}
|
||||
onClose={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description">
|
||||
<AppBar sx={{ position: 'sticky' }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setServerDataOpen(false)
|
||||
}}
|
||||
aria-label="close"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{currentServerData &&
|
||||
<ServerData
|
||||
id={currentServerData?.id}
|
||||
region_id={currentServerData?.region_id}
|
||||
name={currentServerData?.name}
|
||||
/>
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
|
||||
<Typography variant='h6' fontWeight='600'>
|
||||
Servers by region
|
||||
</Typography>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={currentTab} onChange={handleTabChange} aria-label="basic tabs example">
|
||||
<Tab label="Серверы" />
|
||||
<Tab label="IP-адреса" />
|
||||
<Tab label="Hardware" />
|
||||
<Tab label="Storages" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<Autocomplete
|
||||
open={open}
|
||||
onOpen={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleInputChange(value)}
|
||||
onChange={(_, value) => handleOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: IRegion, value: IRegion) => option.name === value.name}
|
||||
getOptionLabel={(option: IRegion) => option.name ? option.name : ""}
|
||||
options={options}
|
||||
loading={isLoading}
|
||||
value={selectedOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Район"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{isLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<CustomTabPanel value={currentTab} index={0}>
|
||||
<ServersView />
|
||||
</CustomTabPanel>
|
||||
|
||||
<Autocomplete
|
||||
hidden
|
||||
<CustomTabPanel value={currentTab} index={1}>
|
||||
<ServerIpsView />
|
||||
</CustomTabPanel>
|
||||
|
||||
open={citiesOpen}
|
||||
onOpen={() => {
|
||||
setCitiesOpen(true)
|
||||
}}
|
||||
onClose={() => {
|
||||
setCitiesOpen(false)
|
||||
}}
|
||||
onInputChange={(_, value) => handleCityInputChange(value)}
|
||||
onChange={(_, value) => handleCityOptionChange(value)}
|
||||
filterOptions={(x) => x}
|
||||
isOptionEqualToValue={(option: ICity, value: ICity) => option.name === value.name}
|
||||
getOptionLabel={(option: ICity) => option.name ? option.name : ""}
|
||||
options={citiesOptions}
|
||||
loading={citiesLoading}
|
||||
value={selectedCityOption}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Город"
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<Fragment>
|
||||
{citiesLoading ? <CircularProgress color="inherit" size={20} /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</Fragment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<CustomTabPanel value={currentTab} index={2}>
|
||||
<ServerHardware />
|
||||
</CustomTabPanel>
|
||||
|
||||
{servers &&
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
|
||||
<Typography variant='h6' fontWeight='600'>
|
||||
Информация
|
||||
</Typography>
|
||||
|
||||
{serversInfo &&
|
||||
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }}>
|
||||
{serversInfo.map((serverInfo: IServersInfo) => (
|
||||
<Grid key={`si-${serverInfo.id}`} item xs={1} sm={1} md={1}>
|
||||
<Paper sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
|
||||
<Typography fontWeight={600}>
|
||||
{serverInfo.name}
|
||||
</Typography>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography>
|
||||
Количество IP:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{serverInfo.IPs_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography>
|
||||
Количество серверов:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
{serverInfo.servers_count}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Chip
|
||||
icon={serverInfo.status === "Online" ? <Cloud /> : serverInfo.status === "Offline" ? <CloudOff /> : <CloudOff />}
|
||||
variant="outlined"
|
||||
label={serverInfo.status}
|
||||
color={serverInfo.status === "Online" ? "success" : serverInfo.status === "Offline" ? "error" : "error"}
|
||||
/>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
|
||||
{serversLoading ?
|
||||
<CircularProgress />
|
||||
:
|
||||
servers &&
|
||||
<FullFeaturedCrudGrid
|
||||
initialRows={servers}
|
||||
columns={serversColumns}
|
||||
actions
|
||||
onRowClick={(params, event, details) => {
|
||||
setCurrentServerData(params.row)
|
||||
setServerDataOpen(true)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<CustomTabPanel value={currentTab} index={3}>
|
||||
<ServerStorage />
|
||||
</CustomTabPanel>
|
||||
|
||||
{/* <BarChart
|
||||
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
||||
|
Reference in New Issue
Block a user