Merge pull request #483 from ylei-tsubame/issues/442-dashboard-nodes
Web UI: add node list to dashboardmain
commit
dc5b01f087
24 changed files with 956 additions and 156 deletions
@ -0,0 +1,312 @@ |
|||||||
|
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 } = props; |
||||||
|
|
||||||
|
const { data: rAnvil, loading: loadingAnvil } = useFetch<AnvilListItem>( |
||||||
|
`/anvil/${anvilUuid}`, |
||||||
|
); |
||||||
|
|
||||||
|
const anvil = useMemo<APIAnvilDetail | undefined>( |
||||||
|
() => rAnvil && toAnvilDetail(rAnvil), |
||||||
|
[rAnvil], |
||||||
|
); |
||||||
|
|
||||||
|
const { data: cpu, loading: loadingCpu } = useFetch<AnvilCPU>( |
||||||
|
`/anvil/${anvilUuid}/cpu`, |
||||||
|
); |
||||||
|
|
||||||
|
const cpuSubnodes = useMemo<AnvilCPU['hosts'][string][] | undefined>( |
||||||
|
() => cpu && Object.values(cpu.hosts), |
||||||
|
[cpu], |
||||||
|
); |
||||||
|
|
||||||
|
const { data: rMemory, loading: loadingMemory } = useFetch<AnvilMemory>( |
||||||
|
`/anvil/${anvilUuid}/memory`, |
||||||
|
); |
||||||
|
|
||||||
|
const memory = useMemo<AnvilMemoryCalcable | undefined>( |
||||||
|
() => rMemory && toAnvilMemoryCalcable(rMemory), |
||||||
|
[rMemory], |
||||||
|
); |
||||||
|
|
||||||
|
const { data: rStorages, loading: loadingStorages } = |
||||||
|
useFetch<AnvilSharedStorage>(`/anvil/${anvilUuid}/store`); |
||||||
|
|
||||||
|
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; |
@ -0,0 +1,84 @@ |
|||||||
|
import { gridClasses } from '@mui/material'; |
||||||
|
import { FC, ReactNode, useMemo } from 'react'; |
||||||
|
|
||||||
|
import AnvilSummary from './AnvilSummary'; |
||||||
|
import { toAnvilOverviewList } from '../../lib/api_converters'; |
||||||
|
import Grid from '../Grid'; |
||||||
|
import { |
||||||
|
InnerPanel, |
||||||
|
InnerPanelBody, |
||||||
|
InnerPanelHeader, |
||||||
|
Panel, |
||||||
|
PanelHeader, |
||||||
|
} from '../Panels'; |
||||||
|
import Spinner from '../Spinner'; |
||||||
|
import { BodyText, HeaderText } from '../Text'; |
||||||
|
import useFetch from '../../hooks/useFetch'; |
||||||
|
|
||||||
|
const AnvilSummaryList: FC = () => { |
||||||
|
const { data: rawAnvils, loading: loadingAnvils } = |
||||||
|
useFetch<APIAnvilOverviewArray>('/anvil', { refreshInterval: 5000 }); |
||||||
|
|
||||||
|
const anvils = useMemo<APIAnvilOverviewList | undefined>( |
||||||
|
() => rawAnvils && toAnvilOverviewList(rawAnvils), |
||||||
|
[rawAnvils], |
||||||
|
); |
||||||
|
|
||||||
|
const grid = useMemo<ReactNode>( |
||||||
|
() => |
||||||
|
anvils && ( |
||||||
|
<Grid |
||||||
|
columns={{ xs: 1, sm: 2, md: 3, xl: 4 }} |
||||||
|
layout={Object.values(anvils).reduce<GridLayout>( |
||||||
|
(previous, current) => { |
||||||
|
const { description, name, uuid } = current; |
||||||
|
|
||||||
|
const key = `anvil-${uuid}`; |
||||||
|
|
||||||
|
previous[key] = { |
||||||
|
children: ( |
||||||
|
<InnerPanel height="100%" mv={0}> |
||||||
|
<InnerPanelHeader> |
||||||
|
<BodyText |
||||||
|
overflow="hidden" |
||||||
|
textOverflow="ellipsis" |
||||||
|
whiteSpace="nowrap" |
||||||
|
> |
||||||
|
{name}: {description} |
||||||
|
</BodyText> |
||||||
|
</InnerPanelHeader> |
||||||
|
<InnerPanelBody> |
||||||
|
<AnvilSummary anvilUuid={uuid} /> |
||||||
|
</InnerPanelBody> |
||||||
|
</InnerPanel> |
||||||
|
), |
||||||
|
}; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, |
||||||
|
{}, |
||||||
|
)} |
||||||
|
spacing="1em" |
||||||
|
sx={{ |
||||||
|
alignContent: 'stretch', |
||||||
|
|
||||||
|
[`& > .${gridClasses.item}`]: { |
||||||
|
minWidth: '20em', |
||||||
|
}, |
||||||
|
}} |
||||||
|
/> |
||||||
|
), |
||||||
|
[anvils], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<Panel> |
||||||
|
<PanelHeader> |
||||||
|
<HeaderText>Nodes</HeaderText> |
||||||
|
</PanelHeader> |
||||||
|
{loadingAnvils ? <Spinner /> : grid} |
||||||
|
</Panel> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AnvilSummaryList; |
@ -1,14 +1,15 @@ |
|||||||
import { LinearProgress } from '@mui/material'; |
import { LinearProgress, linearProgressClasses, styled } from '@mui/material'; |
||||||
import { styled } from '@mui/material/styles'; |
|
||||||
import { |
import { BORDER_RADIUS } from '../../lib/consts/DEFAULT_THEME'; |
||||||
PANEL_BACKGROUND, |
|
||||||
BORDER_RADIUS, |
|
||||||
} from '../../lib/consts/DEFAULT_THEME'; |
|
||||||
|
|
||||||
const BorderLinearProgress = styled(LinearProgress)({ |
const BorderLinearProgress = styled(LinearProgress)({ |
||||||
|
backgroundColor: 'transparent', |
||||||
|
borderRadius: BORDER_RADIUS, |
||||||
height: '1em', |
height: '1em', |
||||||
|
|
||||||
|
[`& .${linearProgressClasses.bar}`]: { |
||||||
borderRadius: BORDER_RADIUS, |
borderRadius: BORDER_RADIUS, |
||||||
backgroundColor: PANEL_BACKGROUND, |
}, |
||||||
}); |
}); |
||||||
|
|
||||||
export default BorderLinearProgress; |
export default BorderLinearProgress; |
||||||
|
@ -0,0 +1,93 @@ |
|||||||
|
import { Box, linearProgressClasses, styled } from '@mui/material'; |
||||||
|
import { FC, ReactElement, createElement, useMemo } from 'react'; |
||||||
|
|
||||||
|
import { GREY } from '../../lib/consts/DEFAULT_THEME'; |
||||||
|
|
||||||
|
import RoundedLinearProgress from './BorderLinearProgress'; |
||||||
|
import Underline from './Underline'; |
||||||
|
|
||||||
|
const ThinRoundedLinearProgress = styled(RoundedLinearProgress)({ |
||||||
|
height: '.4em', |
||||||
|
}); |
||||||
|
|
||||||
|
const ThinUnderline = styled(Underline)({ |
||||||
|
height: '.2em', |
||||||
|
}); |
||||||
|
|
||||||
|
const StackBar: FC<StackBarProps> = (props) => { |
||||||
|
const { barProps = {}, thin, underlineProps, value } = props; |
||||||
|
|
||||||
|
const { sx: barSx, ...restBarProps } = barProps; |
||||||
|
|
||||||
|
const values = useMemo<Record<string, StackBarValue>>( |
||||||
|
() => ('value' in value ? { default: value as StackBarValue } : value), |
||||||
|
[value], |
||||||
|
); |
||||||
|
|
||||||
|
const entries = useMemo<[string, StackBarValue][]>( |
||||||
|
() => Object.entries(values).reverse(), |
||||||
|
[values], |
||||||
|
); |
||||||
|
|
||||||
|
const creatableBar = useMemo( |
||||||
|
() => (thin ? ThinRoundedLinearProgress : RoundedLinearProgress), |
||||||
|
[thin], |
||||||
|
); |
||||||
|
|
||||||
|
const creatableUnderline = useMemo( |
||||||
|
() => (thin ? ThinUnderline : Underline), |
||||||
|
[thin], |
||||||
|
); |
||||||
|
|
||||||
|
const bars = useMemo<ReactElement[]>( |
||||||
|
() => |
||||||
|
entries.map<ReactElement>( |
||||||
|
([id, { colour = GREY, value: val }], index) => { |
||||||
|
const backgroundColor = |
||||||
|
typeof colour === 'string' |
||||||
|
? colour |
||||||
|
: Object.entries(colour).findLast( |
||||||
|
([mark]) => val >= Number(mark), |
||||||
|
)?.[1] ?? GREY; |
||||||
|
|
||||||
|
let position: 'absolute' | 'relative' = 'relative'; |
||||||
|
let top: 0 | undefined; |
||||||
|
let width: string | undefined; |
||||||
|
|
||||||
|
if (index) { |
||||||
|
position = 'absolute'; |
||||||
|
top = 0; |
||||||
|
width = '100%'; |
||||||
|
} |
||||||
|
|
||||||
|
return createElement(creatableBar, { |
||||||
|
key: `stack-bar-${id}`, |
||||||
|
sx: { |
||||||
|
position, |
||||||
|
top, |
||||||
|
width, |
||||||
|
|
||||||
|
[`& .${linearProgressClasses.bar}`]: { |
||||||
|
backgroundColor, |
||||||
|
}, |
||||||
|
|
||||||
|
...barSx, |
||||||
|
}, |
||||||
|
variant: 'determinate', |
||||||
|
value: val, |
||||||
|
...restBarProps, |
||||||
|
}); |
||||||
|
}, |
||||||
|
), |
||||||
|
[barSx, entries, creatableBar, restBarProps], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<Box position="relative"> |
||||||
|
{bars} |
||||||
|
{createElement(creatableUnderline, underlineProps)} |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default StackBar; |
@ -0,0 +1,13 @@ |
|||||||
|
import { Box, styled } from '@mui/material'; |
||||||
|
|
||||||
|
import { BORDER_RADIUS, DISABLED } from '../../lib/consts/DEFAULT_THEME'; |
||||||
|
|
||||||
|
const Underline = styled(Box)({ |
||||||
|
backgroundColor: DISABLED, |
||||||
|
borderRadius: BORDER_RADIUS, |
||||||
|
display: 'block', |
||||||
|
height: '4px', |
||||||
|
position: 'relative', |
||||||
|
}); |
||||||
|
|
||||||
|
export default Underline; |
@ -1,4 +1,5 @@ |
|||||||
import AllocationBar from './AllocationBar'; |
import AllocationBar from './AllocationBar'; |
||||||
import ProgressBar from './ProgressBar'; |
import ProgressBar from './ProgressBar'; |
||||||
|
import StackBar from './StackBar'; |
||||||
|
|
||||||
export { AllocationBar, ProgressBar }; |
export { AllocationBar, ProgressBar, StackBar }; |
||||||
|
@ -0,0 +1,13 @@ |
|||||||
|
import toAnvilDetail from './toAnvilDetail'; |
||||||
|
import toAnvilMemoryCalcable from './toAnvilMemoryCalcable'; |
||||||
|
import toAnvilOverviewHostList from './toAnvilOverviewHostList'; |
||||||
|
import toAnvilOverviewList from './toAnvilOverviewList'; |
||||||
|
import toAnvilSharedStorageOverview from './toAnvilSharedStorageOverview'; |
||||||
|
|
||||||
|
export { |
||||||
|
toAnvilDetail, |
||||||
|
toAnvilMemoryCalcable, |
||||||
|
toAnvilOverviewHostList, |
||||||
|
toAnvilOverviewList, |
||||||
|
toAnvilSharedStorageOverview, |
||||||
|
}; |
@ -0,0 +1,39 @@ |
|||||||
|
const toAnvilDetail = (data: AnvilListItem): APIAnvilDetail => { |
||||||
|
const { |
||||||
|
anvil_name: anvilName, |
||||||
|
anvil_state: anvilState, |
||||||
|
anvil_uuid: anvilUuid, |
||||||
|
hosts: rHosts, |
||||||
|
} = data; |
||||||
|
|
||||||
|
const hosts = rHosts.reduce<APIAnvilDetail['hosts']>((previous, current) => { |
||||||
|
const { |
||||||
|
host_name: hostName, |
||||||
|
host_uuid: hostUuid, |
||||||
|
maintenance_mode: maintenance, |
||||||
|
server_count: serverCount, |
||||||
|
state, |
||||||
|
state_percent: stateProgress, |
||||||
|
} = current; |
||||||
|
|
||||||
|
previous[hostUuid] = { |
||||||
|
name: hostName, |
||||||
|
maintenance, |
||||||
|
serverCount, |
||||||
|
state, |
||||||
|
stateProgress, |
||||||
|
uuid: hostUuid, |
||||||
|
}; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, {}); |
||||||
|
|
||||||
|
return { |
||||||
|
hosts, |
||||||
|
name: anvilName, |
||||||
|
state: anvilState, |
||||||
|
uuid: anvilUuid, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export default toAnvilDetail; |
@ -0,0 +1,15 @@ |
|||||||
|
const toAnvilMemoryCalcable = (data: AnvilMemory): AnvilMemoryCalcable => { |
||||||
|
const { allocated: rAllocated, reserved: rReserved, total: rTotal } = data; |
||||||
|
|
||||||
|
const allocated = BigInt(rAllocated); |
||||||
|
const reserved = BigInt(rReserved); |
||||||
|
const total = BigInt(rTotal); |
||||||
|
|
||||||
|
return { |
||||||
|
allocated, |
||||||
|
reserved, |
||||||
|
total, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export default toAnvilMemoryCalcable; |
@ -0,0 +1,13 @@ |
|||||||
|
const toAnvilOverviewHostList = ( |
||||||
|
data: APIAnvilOverviewArray[number]['hosts'], |
||||||
|
): APIAnvilOverview['hosts'] => |
||||||
|
data.reduce<APIAnvilOverview['hosts']>( |
||||||
|
(previous, { hostName: name, hostType: type, hostUUID: uuid }) => { |
||||||
|
previous[uuid] = { name, type, uuid }; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, |
||||||
|
{}, |
||||||
|
); |
||||||
|
|
||||||
|
export default toAnvilOverviewHostList; |
@ -0,0 +1,28 @@ |
|||||||
|
import toAnvilOverviewHostList from './toAnvilOverviewHostList'; |
||||||
|
|
||||||
|
const toAnvilOverviewList = ( |
||||||
|
data: APIAnvilOverviewArray, |
||||||
|
): APIAnvilOverviewList => |
||||||
|
data.reduce<APIAnvilOverviewList>( |
||||||
|
( |
||||||
|
previous, |
||||||
|
{ |
||||||
|
anvilDescription: description, |
||||||
|
anvilName: name, |
||||||
|
anvilUUID: uuid, |
||||||
|
hosts, |
||||||
|
}, |
||||||
|
) => { |
||||||
|
previous[uuid] = { |
||||||
|
description, |
||||||
|
hosts: toAnvilOverviewHostList(hosts), |
||||||
|
name, |
||||||
|
uuid, |
||||||
|
}; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, |
||||||
|
{}, |
||||||
|
); |
||||||
|
|
||||||
|
export default toAnvilOverviewList; |
@ -0,0 +1,29 @@ |
|||||||
|
const toAnvilSharedStorageOverview = ( |
||||||
|
data: AnvilSharedStorage, |
||||||
|
): APIAnvilSharedStorageOverview => { |
||||||
|
const { storage_groups, total_free, total_size } = data; |
||||||
|
|
||||||
|
const totalFree = BigInt(total_free); |
||||||
|
const totalSize = BigInt(total_size); |
||||||
|
|
||||||
|
return storage_groups.reduce<APIAnvilSharedStorageOverview>( |
||||||
|
(previous, current) => { |
||||||
|
const { |
||||||
|
storage_group_free: rFree, |
||||||
|
storage_group_name: name, |
||||||
|
storage_group_total: rSize, |
||||||
|
storage_group_uuid: uuid, |
||||||
|
} = current; |
||||||
|
|
||||||
|
const free = BigInt(rFree); |
||||||
|
const size = BigInt(rSize); |
||||||
|
|
||||||
|
previous.storageGroups[uuid] = { free, name, size, uuid }; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, |
||||||
|
{ storageGroups: {}, totalFree, totalSize }, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default toAnvilSharedStorageOverview; |
@ -0,0 +1,3 @@ |
|||||||
|
type AnvilSummaryProps = { |
||||||
|
anvilUuid: string; |
||||||
|
}; |
@ -0,0 +1,14 @@ |
|||||||
|
type StackBarValue = { |
||||||
|
colour?: string | Record<number, string>; |
||||||
|
value: number; |
||||||
|
}; |
||||||
|
|
||||||
|
type StackBarOptionalProps = { |
||||||
|
barProps?: import('@mui/material').LinearProgressProps; |
||||||
|
thin?: boolean; |
||||||
|
underlineProps?: import('@mui/material').BoxProps; |
||||||
|
}; |
||||||
|
|
||||||
|
type StackBarProps = StackBarOptionalProps & { |
||||||
|
value: StackBarValue | Record<string, StackBarValue>; |
||||||
|
}; |
Loading…
Reference in new issue