You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
8.6 KiB
323 lines
8.6 KiB
import { Grid, gridClasses } from '@mui/material'; |
|
import { dSizeStr } from 'format-data-size'; |
|
import { FC, ReactNode, useMemo } from 'react'; |
|
|
|
import { BLUE, GREY, PURPLE, RED } from '../../lib/consts/DEFAULT_THEME'; |
|
|
|
import { |
|
toAnvilDetail, |
|
toAnvilMemoryCalcable, |
|
toAnvilSharedStorageOverview, |
|
} from '../../lib/api_converters'; |
|
import Divider from '../Divider'; |
|
import FlexBox from '../FlexBox'; |
|
import Spinner from '../Spinner'; |
|
import StackBar from '../Bars/StackBar'; |
|
import { BodyText, InlineMonoText, MonoText } from '../Text'; |
|
import useFetch from '../../hooks/useFetch'; |
|
|
|
const N_100 = BigInt(100); |
|
|
|
const MAP_TO_ANVIL_STATE_COLOUR = { |
|
degraded: RED, |
|
not_ready: PURPLE, |
|
optimal: BLUE, |
|
}; |
|
|
|
const MAP_TO_HOST_STATE_COLOUR: Record<string, string> = { |
|
offline: PURPLE, |
|
online: BLUE, |
|
}; |
|
|
|
const AnvilSummary: FC<AnvilSummaryProps> = (props) => { |
|
const { anvilUuid, refreshInterval = 5000 } = props; |
|
|
|
const { data: rAnvil, loading: loadingAnvil } = useFetch<AnvilListItem>( |
|
`/anvil/${anvilUuid}`, |
|
{ |
|
refreshInterval, |
|
}, |
|
); |
|
|
|
const anvil = useMemo<APIAnvilDetail | undefined>( |
|
() => rAnvil && toAnvilDetail(rAnvil), |
|
[rAnvil], |
|
); |
|
|
|
const { data: cpu, loading: loadingCpu } = useFetch<AnvilCPU>( |
|
`/anvil/${anvilUuid}/cpu`, |
|
{ |
|
refreshInterval, |
|
}, |
|
); |
|
|
|
const cpuSubnodes = useMemo<AnvilCPU['hosts'][string][] | undefined>( |
|
() => cpu && Object.values(cpu.hosts), |
|
[cpu], |
|
); |
|
|
|
const { data: rMemory, loading: loadingMemory } = useFetch<AnvilMemory>( |
|
`/anvil/${anvilUuid}/memory`, |
|
{ |
|
refreshInterval, |
|
}, |
|
); |
|
|
|
const memory = useMemo<AnvilMemoryCalcable | undefined>( |
|
() => rMemory && toAnvilMemoryCalcable(rMemory), |
|
[rMemory], |
|
); |
|
|
|
const { data: rStorages, loading: loadingStorages } = |
|
useFetch<AnvilSharedStorage>(`/anvil/${anvilUuid}/store`, { |
|
refreshInterval, |
|
}); |
|
|
|
const storages = useMemo<APIAnvilSharedStorageOverview | undefined>( |
|
() => rStorages && toAnvilSharedStorageOverview(rStorages), |
|
[rStorages], |
|
); |
|
|
|
const loading = useMemo<boolean>( |
|
() => |
|
[loadingAnvil, loadingCpu, loadingMemory, loadingStorages].some( |
|
(cond) => cond, |
|
), |
|
[loadingAnvil, loadingCpu, loadingMemory, loadingStorages], |
|
); |
|
|
|
const anvilSummary = useMemo( |
|
() => |
|
anvil && ( |
|
<MonoText inheritColour color={MAP_TO_ANVIL_STATE_COLOUR[anvil.state]}> |
|
{anvil.state} |
|
</MonoText> |
|
), |
|
[anvil], |
|
); |
|
|
|
const hostsSummary = useMemo( |
|
() => |
|
anvil && ( |
|
<Grid |
|
alignItems="center" |
|
columns={20} |
|
columnSpacing="0.5em" |
|
container |
|
sx={{ |
|
[`& > .${gridClasses.item}:nth-child(-n + 4)`]: { |
|
marginBottom: '-.6em', |
|
}, |
|
}} |
|
> |
|
{Object.values(anvil.hosts).map<ReactNode>((host) => { |
|
const { name, serverCount, state, stateProgress, uuid } = host; |
|
|
|
const stateColour: string = MAP_TO_HOST_STATE_COLOUR[state] ?? GREY; |
|
|
|
let stateValue: string = state; |
|
let servers: ReactNode; |
|
|
|
if (['offline', 'online'].includes(state)) { |
|
servers = <MonoText variant="caption">{serverCount}</MonoText>; |
|
} else { |
|
stateValue = `${stateProgress}%`; |
|
} |
|
|
|
return [ |
|
<Grid item key={`${uuid}-state-label`} xs={7}> |
|
<BodyText variant="caption" whiteSpace="nowrap"> |
|
{name} |
|
</BodyText> |
|
</Grid>, |
|
<Grid item key={`${uuid}-state`} xs={5}> |
|
<MonoText inheritColour color={stateColour}> |
|
{stateValue} |
|
</MonoText> |
|
</Grid>, |
|
<Grid item key={`${uuid}-divider`} xs> |
|
<Divider sx={{ marginBottom: '-.4em' }} /> |
|
</Grid>, |
|
<Grid item key={`${uuid}-server-label`} width="2.2em"> |
|
{servers && <BodyText variant="caption">Servers</BodyText>} |
|
</Grid>, |
|
<Grid |
|
display="flex" |
|
item |
|
justifyContent="flex-end" |
|
key={`${uuid}-server-count`} |
|
width="2em" |
|
> |
|
{servers} |
|
</Grid>, |
|
]; |
|
})} |
|
</Grid> |
|
), |
|
[anvil], |
|
); |
|
|
|
const cpuSummary = useMemo( |
|
() => |
|
cpu && |
|
cpuSubnodes && ( |
|
<FlexBox row spacing=".5em"> |
|
<FlexBox spacing={0}> |
|
<BodyText variant="caption" whiteSpace="nowrap"> |
|
Vendor{' '} |
|
<InlineMonoText sx={{ paddingRight: 0 }}> |
|
{cpuSubnodes[0].vendor} |
|
</InlineMonoText> |
|
</BodyText> |
|
</FlexBox> |
|
<Divider sx={{ flexGrow: 1 }} /> |
|
<Grid |
|
alignItems="center" |
|
columns={2} |
|
container |
|
sx={{ |
|
width: '3.7em', |
|
|
|
[`& > .${gridClasses.item}:nth-child(-n + 2)`]: { |
|
marginBottom: '-.6em', |
|
}, |
|
}} |
|
> |
|
<Grid item xs={1}> |
|
<BodyText variant="caption">Cores</BodyText> |
|
</Grid> |
|
<Grid display="flex" item justifyContent="flex-end" xs={1}> |
|
<MonoText variant="caption">{cpu.cores}</MonoText> |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText variant="caption">Threads</BodyText> |
|
</Grid> |
|
<Grid display="flex" item justifyContent="flex-end" xs={1}> |
|
<MonoText variant="caption">{cpu.threads}</MonoText> |
|
</Grid> |
|
</Grid> |
|
</FlexBox> |
|
), |
|
[cpu, cpuSubnodes], |
|
); |
|
|
|
const memorySummary = useMemo( |
|
() => |
|
memory && ( |
|
<FlexBox spacing={0}> |
|
<FlexBox row justifyContent="flex-end"> |
|
<BodyText mb="-.3em" variant="caption"> |
|
Free |
|
<InlineMonoText> |
|
{dSizeStr(memory.total - (memory.reserved + memory.allocated), { |
|
toUnit: 'ibyte', |
|
})} |
|
</InlineMonoText> |
|
/ |
|
<InlineMonoText sx={{ paddingRight: 0 }}> |
|
{dSizeStr(memory.total, { toUnit: 'ibyte' })} |
|
</InlineMonoText> |
|
</BodyText> |
|
</FlexBox> |
|
<StackBar |
|
thin |
|
value={{ |
|
reserved: { |
|
value: Number((memory.reserved * N_100) / memory.total), |
|
}, |
|
allocated: { |
|
value: Number( |
|
((memory.reserved + memory.allocated) * N_100) / memory.total, |
|
), |
|
colour: { 0: BLUE, 70: PURPLE, 90: RED }, |
|
}, |
|
}} |
|
/> |
|
</FlexBox> |
|
), |
|
[memory], |
|
); |
|
|
|
const storeSummary = useMemo( |
|
() => |
|
storages && ( |
|
<FlexBox spacing={0}> |
|
<FlexBox row justifyContent="flex-end"> |
|
<BodyText mb="-.3em" variant="caption"> |
|
Total free |
|
<InlineMonoText> |
|
{dSizeStr(storages.totalFree, { toUnit: 'ibyte' })} |
|
</InlineMonoText> |
|
/ |
|
<InlineMonoText sx={{ paddingRight: 0 }}> |
|
{dSizeStr(storages.totalSize, { toUnit: 'ibyte' })} |
|
</InlineMonoText> |
|
</BodyText> |
|
</FlexBox> |
|
<StackBar |
|
thin |
|
value={{ |
|
allocated: { |
|
value: Number( |
|
((storages.totalSize - storages.totalFree) * N_100) / |
|
storages.totalSize, |
|
), |
|
colour: { 0: BLUE, 70: PURPLE, 90: RED }, |
|
}, |
|
}} |
|
/> |
|
</FlexBox> |
|
), |
|
[storages], |
|
); |
|
|
|
return loading ? ( |
|
<Spinner mt={0} /> |
|
) : ( |
|
<Grid |
|
alignItems="center" |
|
columns={4} |
|
container |
|
sx={{ |
|
[`& > .${gridClasses.item}:nth-child(odd)`]: { |
|
alignItems: 'center', |
|
display: 'flex', |
|
height: '2.2em', |
|
}, |
|
}} |
|
> |
|
<Grid item xs={1}> |
|
<BodyText>Node</BodyText> |
|
</Grid> |
|
<Grid item xs={3}> |
|
{anvilSummary} |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Subnodes</BodyText> |
|
</Grid> |
|
<Grid item xs={3}> |
|
{hostsSummary} |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>CPU</BodyText> |
|
</Grid> |
|
<Grid item xs={3}> |
|
{cpuSummary} |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Memory</BodyText> |
|
</Grid> |
|
<Grid item xs={3}> |
|
{memorySummary} |
|
</Grid> |
|
<Grid item xs={1}> |
|
<BodyText>Storage</BodyText> |
|
</Grid> |
|
<Grid item xs={3}> |
|
{storeSummary} |
|
</Grid> |
|
</Grid> |
|
); |
|
}; |
|
|
|
export default AnvilSummary;
|
|
|