Browse Source

Servers: servers, ips, hw, storages

master
cracklesparkle 10 months ago
parent
commit
e566e23f6d
  1. 23
      frontend_reactjs/src/components/ServerData.tsx
  2. 146
      frontend_reactjs/src/components/ServerHardware.tsx
  3. 144
      frontend_reactjs/src/components/ServerIpsView.tsx
  4. 143
      frontend_reactjs/src/components/ServerStorages.tsx
  5. 191
      frontend_reactjs/src/components/ServersView.tsx
  6. 33
      frontend_reactjs/src/components/TableEditable.tsx
  7. 36
      frontend_reactjs/src/hooks/swrHooks.ts
  8. 303
      frontend_reactjs/src/pages/Servers.tsx

23
frontend_reactjs/src/components/ServerData.tsx

@ -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

@ -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

@ -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

@ -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

@ -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)
}}
/>
}
</>
)
}

33
frontend_reactjs/src/components/TableEditable.tsx

@ -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>
);
}

36
frontend_reactjs/src/hooks/swrHooks.ts

@ -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
}
)

303
frontend_reactjs/src/pages/Servers.tsx

@ -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)
const handleCityInputChange = (value: string) => {
setCitiesSearch(value)
function CustomTabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
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>
<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>
)
}}
/>
)}
/>
<Autocomplete
hidden
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>
)
}}
/>
)}
/>
{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)
}}
/>
}
<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>
<CustomTabPanel value={currentTab} index={0}>
<ServersView />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={1}>
<ServerIpsView />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={2}>
<ServerHardware />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={3}>
<ServerStorage />
</CustomTabPanel>
{/* <BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}

Loading…
Cancel
Save