From 79cd66f17bb3c337003969c7cd1ee73f4b1b399c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 29 Sep 2023 15:20:50 -0400 Subject: [PATCH 01/20] fix(striker-ui): correct host status definition --- striker-ui/types/APIAnvil.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index 216b035d..455fad56 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -65,12 +65,12 @@ type AnvilSharedStorage = { }; type AnvilStatusHost = { - state: 'offline' | 'booted' | 'crmd' | 'in_ccm' | 'online'; - host_uuid: string; host_name: string; - state_percent: number; + host_uuid: string; + maintenance_mode: boolean; + state: 'offline' | 'booted' | 'crmd' | 'in_ccm' | 'online'; state_message: string; - removable: boolean; + state_percent: number; }; type AnvilStatus = { From c5c344b978df186ff878cecc7102c99e1067aa59 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 29 Sep 2023 18:42:19 -0400 Subject: [PATCH 02/20] fix(striker-ui-api): add CPU model, guessed vendor to node/cpu response --- .../lib/request_handlers/anvil/getAnvilCpu.ts | 88 +++++++++++++++---- striker-ui-api/src/types/ApiAn.d.ts | 20 +++++ 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts index 4263e8b9..6cf35000 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts @@ -1,3 +1,4 @@ +import assert from 'assert'; import { RequestHandler } from 'express'; import { query } from '../../accessModule'; @@ -11,40 +12,55 @@ export const getAnvilCpu: RequestHandler = async ( params: { anvilUuid }, } = request; - let rCores: null | string; - let rThreads: null | string; + let rCpus: [ + hostUuid: string, + hostName: string, + cpuModel: string, + cpuCores: string, + cpuThreads: string, + cpuMinCores: string, + cpuMinThreads: string, + ][]; try { - [[rCores = '', rThreads = '']] = await query< - [[cpuCores: null | string, cpuThreads: null | string]] - >( + rCpus = await query( `SELECT + b.host_uuid, + b.host_name, + c.scan_hardware_cpu_model, + c.scan_hardware_cpu_cores, + c.scan_hardware_cpu_threads, MIN(c.scan_hardware_cpu_cores) AS cores, MIN(c.scan_hardware_cpu_threads) AS threads FROM anvils AS a JOIN hosts AS b ON b.host_uuid IN ( a.anvil_node1_host_uuid, - a.anvil_node2_host_uuid, - a.anvil_dr1_host_uuid + a.anvil_node2_host_uuid ) JOIN scan_hardware AS c ON b.host_uuid = c.scan_hardware_host_uuid - WHERE a.anvil_uuid = '${anvilUuid}';`, + WHERE a.anvil_uuid = '${anvilUuid}' + GROUP BY + b.host_uuid, + b.host_name, + c.scan_hardware_cpu_model, + c.scan_hardware_cpu_cores, + c.scan_hardware_cpu_threads + ORDER BY b.host_name;`, ); + + assert.ok(rCpus.length, 'No entry found'); } catch (error) { stderr(`Failed to get anvil ${anvilUuid} cpu info; CAUSE: ${error}`); return response.status(500).send(); } - const cores = Number.parseInt(rCores); - const threads = Number.parseInt(rThreads); - - let rAllocated: null | string; + let rAllocatedRow: [cpuAllocated: string][]; try { - [[rAllocated = '']] = await query<[[cpuAllocated: null | string]]>( + rAllocatedRow = await query( `SELECT SUM( CAST( @@ -58,17 +74,51 @@ export const getAnvilCpu: RequestHandler = async ( ON a.server_uuid = b.server_definition_server_uuid WHERE a.server_anvil_uuid = '${anvilUuid}';`, ); + + assert.ok(rAllocatedRow.length, 'No entry found'); } catch (error) { stderr(`Failed to get anvil ${anvilUuid} server cpu info; CAUSE: ${error}`); return response.status(500).send(); } - const allocated = Number.parseInt(rAllocated); + const { + 0: { 5: rMinCores, 6: rMinThreads }, + } = rCpus; + + const minCores = Number(rMinCores); + const minThreads = Number(rMinThreads); + + const [[rAllocated]] = rAllocatedRow; + + const allocated = Number(rAllocated); + + const rsBody = rCpus.reduce( + (previous, current) => { + const { 0: uuid, 1: name, 2: model, 3: rCores, 4: rThreads } = current; + + const cores = Number(rCores); + const threads = Number(rThreads); + const vendor = model.replace(/^(\w+).*$/, '$1'); + + previous.hosts[uuid] = { + cores, + model, + name, + threads, + uuid, + vendor, + }; + + return previous; + }, + { + allocated, + cores: minCores, + hosts: {}, + threads: minThreads, + }, + ); - response.status(200).send({ - allocated, - cores, - threads, - }); + response.status(200).send(rsBody); }; diff --git a/striker-ui-api/src/types/ApiAn.d.ts b/striker-ui-api/src/types/ApiAn.d.ts index 2885af63..72779ef3 100644 --- a/striker-ui-api/src/types/ApiAn.d.ts +++ b/striker-ui-api/src/types/ApiAn.d.ts @@ -1,3 +1,23 @@ +type AnvilDetailCpuHost = { + cores: number; + model: string; + name: string; + threads: number; + uuid: string; + vendor: string; +}; + +type AnvilDetailCpuHostList = { + [hostUuid: string]: AnvilDetailCpuHost; +}; + +type AnvilDetailCpuSummary = { + allocated: number; + cores: number; + hosts: AnvilDetailCpuHostList; + threads: number; +}; + type AnvilDetailFileForProvisionServer = { fileUUID: string; fileName: string; From 8eda5b1849bf08734ef61c8515903239a3bacb88 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 29 Sep 2023 21:36:48 -0400 Subject: [PATCH 03/20] fix(striker-ui): correct free memory calculation --- striker-ui/components/Memory.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/striker-ui/components/Memory.tsx b/striker-ui/components/Memory.tsx index 1a56a8de..d94befad 100644 --- a/striker-ui/components/Memory.tsx +++ b/striker-ui/components/Memory.tsx @@ -35,14 +35,18 @@ const Memory = (): JSX.Element => { - + From 66a1e6c11100fd50c6c2fbc5b925ff2dc0d373a9 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 29 Sep 2023 22:18:27 -0400 Subject: [PATCH 04/20] fix(striker-ui): simplify bar underline, add stack bar --- striker-ui/components/Bars/AllocationBar.tsx | 23 ++---- .../components/Bars/BorderLinearProgress.tsx | 17 ++--- striker-ui/components/Bars/ProgressBar.tsx | 18 ++--- striker-ui/components/Bars/StackBar.tsx | 72 +++++++++++++++++++ striker-ui/components/Bars/Underline.tsx | 13 ++++ striker-ui/components/Bars/index.tsx | 3 +- striker-ui/types/StackBar.d.ts | 8 +++ 7 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 striker-ui/components/Bars/StackBar.tsx create mode 100644 striker-ui/components/Bars/Underline.tsx create mode 100644 striker-ui/types/StackBar.d.ts diff --git a/striker-ui/components/Bars/AllocationBar.tsx b/striker-ui/components/Bars/AllocationBar.tsx index 90b3df0f..ebbe6292 100644 --- a/striker-ui/components/Bars/AllocationBar.tsx +++ b/striker-ui/components/Bars/AllocationBar.tsx @@ -1,13 +1,9 @@ -import { LinearProgress } from '@mui/material'; -import { styled } from '@mui/material/styles'; +import { styled } from '@mui/material'; + +import { PURPLE, RED, BLUE } from '../../lib/consts/DEFAULT_THEME'; -import { - PURPLE, - RED, - BLUE, - BORDER_RADIUS, -} from '../../lib/consts/DEFAULT_THEME'; import BorderLinearProgress from './BorderLinearProgress'; +import Underline from './Underline'; const PREFIX = 'AllocationBar'; @@ -15,7 +11,6 @@ const classes = { barOk: `${PREFIX}-barOk`, barWarning: `${PREFIX}-barWarning`, barAlert: `${PREFIX}-barAlert`, - underline: `${PREFIX}-underline`, }; const StyledDiv = styled('div')(() => ({ @@ -30,10 +25,6 @@ const StyledDiv = styled('div')(() => ({ [`& .${classes.barAlert}`]: { backgroundColor: RED, }, - - [`& .${classes.underline}`]: { - borderRadius: BORDER_RADIUS, - }, })); const breakpointWarning = 70; @@ -54,11 +45,7 @@ const AllocationBar = ({ allocated }: { allocated: number }): JSX.Element => ( variant="determinate" value={allocated} /> - + ); diff --git a/striker-ui/components/Bars/BorderLinearProgress.tsx b/striker-ui/components/Bars/BorderLinearProgress.tsx index 50c1de44..57509a31 100644 --- a/striker-ui/components/Bars/BorderLinearProgress.tsx +++ b/striker-ui/components/Bars/BorderLinearProgress.tsx @@ -1,14 +1,15 @@ -import { LinearProgress } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { - PANEL_BACKGROUND, - BORDER_RADIUS, -} from '../../lib/consts/DEFAULT_THEME'; +import { LinearProgress, linearProgressClasses, styled } from '@mui/material'; + +import { BORDER_RADIUS } from '../../lib/consts/DEFAULT_THEME'; const BorderLinearProgress = styled(LinearProgress)({ - height: '1em', + backgroundColor: 'transparent', borderRadius: BORDER_RADIUS, - backgroundColor: PANEL_BACKGROUND, + height: '1em', + + [`& .${linearProgressClasses.bar}`]: { + borderRadius: BORDER_RADIUS, + }, }); export default BorderLinearProgress; diff --git a/striker-ui/components/Bars/ProgressBar.tsx b/striker-ui/components/Bars/ProgressBar.tsx index 9f0280ad..d122acf2 100644 --- a/striker-ui/components/Bars/ProgressBar.tsx +++ b/striker-ui/components/Bars/ProgressBar.tsx @@ -1,15 +1,15 @@ -import { LinearProgress } from '@mui/material'; -import { styled } from '@mui/material/styles'; +import { styled } from '@mui/material'; + +import { PURPLE, BLUE } from '../../lib/consts/DEFAULT_THEME'; -import { PURPLE, BLUE, BORDER_RADIUS } from '../../lib/consts/DEFAULT_THEME'; import BorderLinearProgress from './BorderLinearProgress'; +import Underline from './Underline'; const PREFIX = 'ProgressBar'; const classes = { barOk: `${PREFIX}-barOk`, barInProgress: `${PREFIX}-barInProgress`, - underline: `${PREFIX}-underline`, }; const StyledDiv = styled('div')(() => ({ @@ -20,10 +20,6 @@ const StyledDiv = styled('div')(() => ({ [`& .${classes.barInProgress}`]: { backgroundColor: PURPLE, }, - - [`& .${classes.underline}`]: { - borderRadius: BORDER_RADIUS, - }, })); const completed = 100; @@ -44,11 +40,7 @@ const ProgressBar = ({ variant="determinate" value={progressPercentage} /> - + ); diff --git a/striker-ui/components/Bars/StackBar.tsx b/striker-ui/components/Bars/StackBar.tsx new file mode 100644 index 00000000..d0676490 --- /dev/null +++ b/striker-ui/components/Bars/StackBar.tsx @@ -0,0 +1,72 @@ +import { Box, linearProgressClasses } from '@mui/material'; +import { FC, ReactElement, useMemo } from 'react'; + +import { GREY } from '../../lib/consts/DEFAULT_THEME'; + +import BorderLinearProgress from './BorderLinearProgress'; +import Underline from './Underline'; + +const StackBar: FC = (props) => { + const { value } = props; + + const values = useMemo>( + () => ('value' in value ? { default: value as StackBarValue } : value), + [value], + ); + + const entries = useMemo<[string, StackBarValue][]>( + () => Object.entries(values).reverse(), + [values], + ); + + const bars = useMemo( + () => + entries.map( + ([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 ( + + ); + }, + ), + [entries], + ); + + return ( + + {bars} + + + ); +}; + +export default StackBar; diff --git a/striker-ui/components/Bars/Underline.tsx b/striker-ui/components/Bars/Underline.tsx new file mode 100644 index 00000000..b2553a2a --- /dev/null +++ b/striker-ui/components/Bars/Underline.tsx @@ -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; diff --git a/striker-ui/components/Bars/index.tsx b/striker-ui/components/Bars/index.tsx index 0863c898..d321b41e 100644 --- a/striker-ui/components/Bars/index.tsx +++ b/striker-ui/components/Bars/index.tsx @@ -1,4 +1,5 @@ import AllocationBar from './AllocationBar'; import ProgressBar from './ProgressBar'; +import StackBar from './StackBar'; -export { AllocationBar, ProgressBar }; +export { AllocationBar, ProgressBar, StackBar }; diff --git a/striker-ui/types/StackBar.d.ts b/striker-ui/types/StackBar.d.ts new file mode 100644 index 00000000..b0af9b11 --- /dev/null +++ b/striker-ui/types/StackBar.d.ts @@ -0,0 +1,8 @@ +type StackBarValue = { + colour?: string | Record; + value: number; +}; + +type StackBarProps = { + value: StackBarValue | Record; +}; From 78cbdeb1230c3d1b47dbbcc7a5dfc6779637553f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 00:18:39 -0400 Subject: [PATCH 05/20] fix(striker-ui): add thin option to stack bar --- striker-ui/components/Bars/StackBar.tsx | 65 ++++++++++++++++--------- striker-ui/types/StackBar.d.ts | 8 ++- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/striker-ui/components/Bars/StackBar.tsx b/striker-ui/components/Bars/StackBar.tsx index d0676490..b6e4e38e 100644 --- a/striker-ui/components/Bars/StackBar.tsx +++ b/striker-ui/components/Bars/StackBar.tsx @@ -1,13 +1,23 @@ -import { Box, linearProgressClasses } from '@mui/material'; -import { FC, ReactElement, useMemo } from 'react'; +import { Box, linearProgressClasses, styled } from '@mui/material'; +import { FC, ReactElement, createElement, useMemo } from 'react'; import { GREY } from '../../lib/consts/DEFAULT_THEME'; -import BorderLinearProgress from './BorderLinearProgress'; +import RoundedLinearProgress from './BorderLinearProgress'; import Underline from './Underline'; +const ThinRoundedLinearProgress = styled(RoundedLinearProgress)({ + height: '.4em', +}); + +const ThinUnderline = styled(Underline)({ + height: '.2em', +}); + const StackBar: FC = (props) => { - const { value } = props; + const { barProps = {}, thin, underlineProps, value } = props; + + const { sx: barSx, ...restBarProps } = barProps; const values = useMemo>( () => ('value' in value ? { default: value as StackBarValue } : value), @@ -19,6 +29,16 @@ const StackBar: FC = (props) => { [values], ); + const creatableBar = useMemo( + () => (thin ? ThinRoundedLinearProgress : RoundedLinearProgress), + [thin], + ); + + const creatableUnderline = useMemo( + () => (thin ? ThinUnderline : Underline), + [thin], + ); + const bars = useMemo( () => entries.map( @@ -40,31 +60,32 @@ const StackBar: FC = (props) => { width = '100%'; } - return ( - - ); + return createElement(creatableBar, { + key: `stack-bar-${id}`, + sx: { + position, + top, + width, + + [`& .${linearProgressClasses.bar}`]: { + backgroundColor, + }, + + ...barSx, + }, + variant: 'determinate', + value: val, + ...restBarProps, + }); }, ), - [entries], + [barSx, entries, creatableBar, restBarProps], ); return ( {bars} - + {createElement(creatableUnderline, underlineProps)} ); }; diff --git a/striker-ui/types/StackBar.d.ts b/striker-ui/types/StackBar.d.ts index b0af9b11..468ac20e 100644 --- a/striker-ui/types/StackBar.d.ts +++ b/striker-ui/types/StackBar.d.ts @@ -3,6 +3,12 @@ type StackBarValue = { value: number; }; -type StackBarProps = { +type StackBarOptionalProps = { + barProps?: import('@mui/material').LinearProgressProps; + thin?: boolean; + underlineProps?: import('@mui/material').BoxProps; +}; + +type StackBarProps = StackBarOptionalProps & { value: StackBarValue | Record; }; From ac28ea88df42eea696305c158562a1b09c55f121 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 00:20:54 -0400 Subject: [PATCH 06/20] fix(striker-ui): isolate fetched data conversion --- .../components/Files/ManageFilePanel.tsx | 36 +------------------ striker-ui/lib/api_converters/index.ts | 5 +++ .../api_converters/toAnvilMemoryCalcable.ts | 15 ++++++++ .../api_converters/toAnvilOverviewHostList.ts | 13 +++++++ .../lib/api_converters/toAnvilOverviewList.ts | 28 +++++++++++++++ striker-ui/types/APIAnvil.d.ts | 16 +++++++++ 6 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 striker-ui/lib/api_converters/index.ts create mode 100644 striker-ui/lib/api_converters/toAnvilMemoryCalcable.ts create mode 100644 striker-ui/lib/api_converters/toAnvilOverviewHostList.ts create mode 100644 striker-ui/lib/api_converters/toAnvilOverviewList.ts diff --git a/striker-ui/components/Files/ManageFilePanel.tsx b/striker-ui/components/Files/ManageFilePanel.tsx index b5c78483..663fa12f 100644 --- a/striker-ui/components/Files/ManageFilePanel.tsx +++ b/striker-ui/components/Files/ManageFilePanel.tsx @@ -6,6 +6,7 @@ import { UPLOAD_FILE_TYPES } from '../../lib/consts/UPLOAD_FILE_TYPES'; import AddFileForm from './AddFileForm'; import api from '../../lib/api'; +import { toAnvilOverviewList } from '../../lib/api_converters'; import ConfirmDialog from '../ConfirmDialog'; import { DialogWithHeader } from '../Dialog'; import Divider from '../Divider'; @@ -23,41 +24,6 @@ import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; import useFetch from '../../hooks/useFetch'; import useProtectedState from '../../hooks/useProtectedState'; -const toAnvilOverviewHostList = ( - data: APIAnvilOverviewArray[number]['hosts'], -) => - data.reduce( - (previous, { hostName: name, hostType: type, hostUUID: uuid }) => { - previous[uuid] = { name, type, uuid }; - - return previous; - }, - {}, - ); - -const toAnvilOverviewList = (data: APIAnvilOverviewArray) => - data.reduce( - ( - previous, - { - anvilDescription: description, - anvilName: name, - anvilUUID: uuid, - hosts, - }, - ) => { - previous[uuid] = { - description, - hosts: toAnvilOverviewHostList(hosts), - name, - uuid, - }; - - return previous; - }, - {}, - ); - const toFileOverviewList = (rows: string[][]) => rows.reduce((previous, row) => { const [uuid, name, size, type, checksum] = row; diff --git a/striker-ui/lib/api_converters/index.ts b/striker-ui/lib/api_converters/index.ts new file mode 100644 index 00000000..58ec1845 --- /dev/null +++ b/striker-ui/lib/api_converters/index.ts @@ -0,0 +1,5 @@ +import toAnvilMemoryCalcable from './toAnvilMemoryCalcable'; +import toAnvilOverviewHostList from './toAnvilOverviewHostList'; +import toAnvilOverviewList from './toAnvilOverviewList'; + +export { toAnvilMemoryCalcable, toAnvilOverviewHostList, toAnvilOverviewList }; diff --git a/striker-ui/lib/api_converters/toAnvilMemoryCalcable.ts b/striker-ui/lib/api_converters/toAnvilMemoryCalcable.ts new file mode 100644 index 00000000..f8fc37ef --- /dev/null +++ b/striker-ui/lib/api_converters/toAnvilMemoryCalcable.ts @@ -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; diff --git a/striker-ui/lib/api_converters/toAnvilOverviewHostList.ts b/striker-ui/lib/api_converters/toAnvilOverviewHostList.ts new file mode 100644 index 00000000..b2a79d02 --- /dev/null +++ b/striker-ui/lib/api_converters/toAnvilOverviewHostList.ts @@ -0,0 +1,13 @@ +const toAnvilOverviewHostList = ( + data: APIAnvilOverviewArray[number]['hosts'], +): APIAnvilOverview['hosts'] => + data.reduce( + (previous, { hostName: name, hostType: type, hostUUID: uuid }) => { + previous[uuid] = { name, type, uuid }; + + return previous; + }, + {}, + ); + +export default toAnvilOverviewHostList; diff --git a/striker-ui/lib/api_converters/toAnvilOverviewList.ts b/striker-ui/lib/api_converters/toAnvilOverviewList.ts new file mode 100644 index 00000000..576ce873 --- /dev/null +++ b/striker-ui/lib/api_converters/toAnvilOverviewList.ts @@ -0,0 +1,28 @@ +import toAnvilOverviewHostList from './toAnvilOverviewHostList'; + +const toAnvilOverviewList = ( + data: APIAnvilOverviewArray, +): APIAnvilOverviewList => + data.reduce( + ( + previous, + { + anvilDescription: description, + anvilName: name, + anvilUUID: uuid, + hosts, + }, + ) => { + previous[uuid] = { + description, + hosts: toAnvilOverviewHostList(hosts), + name, + uuid, + }; + + return previous; + }, + {}, + ); + +export default toAnvilOverviewList; diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index 455fad56..ee9d4403 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -1,6 +1,16 @@ type AnvilCPU = { allocated: number; cores: number; + hosts: { + [hostUuid: string]: { + cores: number; + model: string; + name: string; + threads: number; + uuid: string; + vendor: string; + }; + }; threads: number; }; @@ -10,6 +20,12 @@ type AnvilMemory = { total: string; }; +type AnvilMemoryCalcable = { + allocated: bigint; + reserved: bigint; + total: bigint; +}; + type AnvilNetworkBondLink = { link_name: string; link_uuid: string; From bf19d03ee5f9e88a5c614cd60e51be02ff23b17d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 01:12:35 -0400 Subject: [PATCH 07/20] fix(striker-ui): correct response status when no records in fetch anvil cpu --- .../src/lib/request_handlers/anvil/getAnvilCpu.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts index 6cf35000..2a1bace2 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { RequestHandler } from 'express'; import { query } from '../../accessModule'; @@ -49,14 +48,14 @@ export const getAnvilCpu: RequestHandler = async ( c.scan_hardware_cpu_threads ORDER BY b.host_name;`, ); - - assert.ok(rCpus.length, 'No entry found'); } catch (error) { stderr(`Failed to get anvil ${anvilUuid} cpu info; CAUSE: ${error}`); return response.status(500).send(); } + if (!rCpus.length) return response.status(404).send(); + let rAllocatedRow: [cpuAllocated: string][]; try { @@ -74,14 +73,14 @@ export const getAnvilCpu: RequestHandler = async ( ON a.server_uuid = b.server_definition_server_uuid WHERE a.server_anvil_uuid = '${anvilUuid}';`, ); - - assert.ok(rAllocatedRow.length, 'No entry found'); } catch (error) { stderr(`Failed to get anvil ${anvilUuid} server cpu info; CAUSE: ${error}`); return response.status(500).send(); } + if (!rAllocatedRow.length) return response.status(404).send(); + const { 0: { 5: rMinCores, 6: rMinThreads }, } = rCpus; From 58a3b56defede8651c80ac375bd401eef2c86400 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 01:14:57 -0400 Subject: [PATCH 08/20] fix(striker-ui): add total size, free in anvil shared storage summary --- .../request_handlers/anvil/getAnvilStore.ts | 26 +++++++++++++++++-- striker-ui-api/src/types/ApiAn.d.ts | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilStore.ts b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilStore.ts index a5e07532..7f2d6480 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilStore.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilStore.ts @@ -27,7 +27,14 @@ export const getAnvilStore: RequestHandler< return response.status(400).send(); } - let rows: [uuid: string, name: string, size: string, free: string][]; + let rows: [ + uuid: string, + name: string, + size: string, + free: string, + totalSize: string, + totalFree: string, + ][]; try { rows = await query( @@ -35,7 +42,9 @@ export const getAnvilStore: RequestHandler< DISTINCT ON (b.storage_group_uuid) storage_group_uuid, b.storage_group_name, d.scan_lvm_vg_size, - d.scan_lvm_vg_free + d.scan_lvm_vg_free, + SUM(d.scan_lvm_vg_size) AS total_vg_size, + SUM(d.scan_lvm_vg_free) AS total_vg_free FROM anvils AS a JOIN storage_groups AS b ON a.anvil_uuid = b.storage_group_anvil_uuid @@ -44,6 +53,11 @@ export const getAnvilStore: RequestHandler< JOIN scan_lvm_vgs AS d ON c.storage_group_member_vg_uuid = d.scan_lvm_vg_internal_uuid WHERE a.anvil_uuid = '${anUuid}' + GROUP BY + b.storage_group_uuid, + b.storage_group_name, + d.scan_lvm_vg_size, + d.scan_lvm_vg_free ORDER BY b.storage_group_uuid, d.scan_lvm_vg_free;`, ); } catch (error) { @@ -52,6 +66,12 @@ export const getAnvilStore: RequestHandler< return response.status(500).send(); } + if (!rows.length) return response.status(404).send(); + + const { + 0: { 4: totalSize, 5: totalFree }, + } = rows; + const rsbody: AnvilDetailStoreSummary = { storage_groups: rows.map( ([sgUuid, sgName, sgSize, sgFree]) => ({ @@ -61,6 +81,8 @@ export const getAnvilStore: RequestHandler< storage_group_uuid: sgUuid, }), ), + total_free: totalFree, + total_size: totalSize, }; return response.json(rsbody); diff --git a/striker-ui-api/src/types/ApiAn.d.ts b/striker-ui-api/src/types/ApiAn.d.ts index 72779ef3..1ead1181 100644 --- a/striker-ui-api/src/types/ApiAn.d.ts +++ b/striker-ui-api/src/types/ApiAn.d.ts @@ -129,6 +129,8 @@ type AnvilDetailParamsDictionary = { type AnvilDetailStoreSummary = { storage_groups: AnvilDetailStore[]; + total_free: string; + total_size: string; }; type AnvilOverview = { From 72ee08414a99737042b9e67d339848133db5cc85 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 03:39:21 -0400 Subject: [PATCH 09/20] fix(striker-ui): replace host names in cpu summary with short version --- striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts index 2a1bace2..9a0d9133 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/getAnvilCpu.ts @@ -1,6 +1,7 @@ import { RequestHandler } from 'express'; import { query } from '../../accessModule'; +import { getShortHostName } from '../../disassembleHostName'; import { stderr } from '../../shell'; export const getAnvilCpu: RequestHandler = async ( @@ -103,7 +104,7 @@ export const getAnvilCpu: RequestHandler = async ( previous.hosts[uuid] = { cores, model, - name, + name: getShortHostName(name), threads, uuid, vendor, From 05438d39f4c78f53b48cf7d5f1385d2fc4c519a9 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 04:02:02 -0400 Subject: [PATCH 10/20] fix(striker-ui): add shared storage api response converter --- striker-ui/lib/api_converters/index.ts | 8 ++++- .../toAnvilSharedStorageOverview.ts | 29 +++++++++++++++++++ striker-ui/types/APIAnvil.d.ts | 17 +++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 striker-ui/lib/api_converters/toAnvilSharedStorageOverview.ts diff --git a/striker-ui/lib/api_converters/index.ts b/striker-ui/lib/api_converters/index.ts index 58ec1845..7ce821fe 100644 --- a/striker-ui/lib/api_converters/index.ts +++ b/striker-ui/lib/api_converters/index.ts @@ -1,5 +1,11 @@ import toAnvilMemoryCalcable from './toAnvilMemoryCalcable'; import toAnvilOverviewHostList from './toAnvilOverviewHostList'; import toAnvilOverviewList from './toAnvilOverviewList'; +import toAnvilSharedStorageOverview from './toAnvilSharedStorageOverview'; -export { toAnvilMemoryCalcable, toAnvilOverviewHostList, toAnvilOverviewList }; +export { + toAnvilMemoryCalcable, + toAnvilOverviewHostList, + toAnvilOverviewList, + toAnvilSharedStorageOverview, +}; diff --git a/striker-ui/lib/api_converters/toAnvilSharedStorageOverview.ts b/striker-ui/lib/api_converters/toAnvilSharedStorageOverview.ts new file mode 100644 index 00000000..74e2e099 --- /dev/null +++ b/striker-ui/lib/api_converters/toAnvilSharedStorageOverview.ts @@ -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( + (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; diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index ee9d4403..37e9cdb4 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -78,6 +78,8 @@ type AnvilSharedStorageGroup = { type AnvilSharedStorage = { storage_groups: AnvilSharedStorageGroup[]; + total_size: string; + total_free: string; }; type AnvilStatusHost = { @@ -130,3 +132,18 @@ type APIAnvilOverview = { type APIAnvilOverviewList = { [uuid: string]: APIAnvilOverview; }; + +type APIAnvilStorageGroupCalcable = { + free: bigint; + name: string; + size: bigint; + uuid: string; +}; + +type APIAnvilSharedStorageOverview = { + storageGroups: { + [uuid: string]: APIAnvilStorageGroupCalcable; + }; + totalFree: bigint; + totalSize: bigint; +}; From 5d1ab29955146eea7be073984261eec031dd29e8 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 04:11:58 -0400 Subject: [PATCH 11/20] fix(striker-ui): add anvil summary components --- striker-ui/components/Anvils/AnvilSummary.tsx | 202 ++++++++++++++++++ .../components/Anvils/AnvilSummaryList.tsx | 71 ++++++ striker-ui/types/AnvilSummary.d.ts | 7 + 3 files changed, 280 insertions(+) create mode 100644 striker-ui/components/Anvils/AnvilSummary.tsx create mode 100644 striker-ui/components/Anvils/AnvilSummaryList.tsx create mode 100644 striker-ui/types/AnvilSummary.d.ts diff --git a/striker-ui/components/Anvils/AnvilSummary.tsx b/striker-ui/components/Anvils/AnvilSummary.tsx new file mode 100644 index 00000000..053ab415 --- /dev/null +++ b/striker-ui/components/Anvils/AnvilSummary.tsx @@ -0,0 +1,202 @@ +import { Grid, gridClasses } from '@mui/material'; +import { dSizeStr } from 'format-data-size'; +import { FC, useMemo } from 'react'; + +import { BLUE, PURPLE, RED } from '../../lib/consts/DEFAULT_THEME'; + +import { + toAnvilMemoryCalcable, + toAnvilSharedStorageOverview, +} from '../../lib/api_converters'; +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 n100 = BigInt(100); + +const AnvilSummary: FC = (props) => { + const { anvilUuid } = props; + + const { data: cpu, loading: loadingCpu } = useFetch( + `/anvil/${anvilUuid}/cpu`, + ); + + const cpuSubnodes = useMemo( + () => cpu && Object.values(cpu.hosts), + [cpu], + ); + + const { data: rMemory, loading: loadingMemory } = useFetch( + `/anvil/${anvilUuid}/memory`, + ); + + const memory = useMemo( + () => rMemory && toAnvilMemoryCalcable(rMemory), + [rMemory], + ); + + const { data: rStorages, loading: loadingStorages } = + useFetch(`/anvil/${anvilUuid}/store`); + + const storages = useMemo( + () => rStorages && toAnvilSharedStorageOverview(rStorages), + [rStorages], + ); + + const loading = useMemo( + () => loadingCpu || loadingMemory || loadingStorages, + [loadingCpu, loadingMemory, loadingStorages], + ); + + const cpuSummary = useMemo( + () => + cpu && + cpuSubnodes && ( + + + {cpuSubnodes[0].name} + {cpuSubnodes[0].vendor} + + .${gridClasses.item}:nth-child(-n + 2)`]: { + marginBottom: '-.6em', + }, + }} + width="calc(0% + 4em)" + > + + CORES + + + {cpu.cores} + + + THREADS + + + {cpu.threads} + + + + {cpuSubnodes[1].name} + {cpuSubnodes[1].vendor} + + + ), + [cpu, cpuSubnodes], + ); + + const memorySummary = useMemo( + () => + memory && ( + + + + FREE + + {dSizeStr(memory.total - (memory.reserved + memory.allocated), { + toUnit: 'ibyte', + })} + + / + + {dSizeStr(memory.total, { toUnit: 'ibyte' })} + + + + + + ), + [memory], + ); + + const storeSummary = useMemo( + () => + storages && ( + + + + FREE + + {dSizeStr(storages.totalFree, { toUnit: 'ibyte' })} + + / + + {dSizeStr(storages.totalSize, { toUnit: 'ibyte' })} + + + + + + ), + [storages], + ); + + return loading ? ( + + ) : ( + .${gridClasses.item}:nth-child(odd)`]: { + alignItems: 'center', + display: 'flex', + height: '2.5em', + }, + }} + > + + CPU + + + {cpuSummary} + + + Memory + + + {memorySummary} + + + Storage + + + {storeSummary} + + + ); +}; + +export default AnvilSummary; diff --git a/striker-ui/components/Anvils/AnvilSummaryList.tsx b/striker-ui/components/Anvils/AnvilSummaryList.tsx new file mode 100644 index 00000000..5c09eb5f --- /dev/null +++ b/striker-ui/components/Anvils/AnvilSummaryList.tsx @@ -0,0 +1,71 @@ +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('/anvil', { refreshInterval: 5000 }); + + const anvils = useMemo( + () => rawAnvils && toAnvilOverviewList(rawAnvils), + [rawAnvils], + ); + + const grid = useMemo( + () => + anvils && ( + ( + (previous, current) => { + const { description, name, uuid } = current; + + const key = `anvil-${uuid}`; + + previous[key] = { + children: ( + + + + {name}: {description} + + + + + + + ), + }; + + return previous; + }, + {}, + )} + /> + ), + [anvils], + ); + + return ( + + + Nodes + + {loadingAnvils ? : grid} + + ); +}; + +export default AnvilSummaryList; diff --git a/striker-ui/types/AnvilSummary.d.ts b/striker-ui/types/AnvilSummary.d.ts new file mode 100644 index 00000000..8cfd05ff --- /dev/null +++ b/striker-ui/types/AnvilSummary.d.ts @@ -0,0 +1,7 @@ +type AnvilSummaryOptionalProps = { + loading?: boolean; +}; + +type AnvilSummaryProps = AnvilSummaryOptionalProps & { + anvilUuid: string; +}; From f59ed30ff66b0e81d405bad1ea07f5ee8e16dedb Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 30 Sep 2023 04:16:02 -0400 Subject: [PATCH 12/20] fix(striker-ui): adjust server list header, show node list in dashboard --- striker-ui/pages/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/striker-ui/pages/index.tsx b/striker-ui/pages/index.tsx index af64bb03..cb21383d 100644 --- a/striker-ui/pages/index.tsx +++ b/striker-ui/pages/index.tsx @@ -7,6 +7,7 @@ import { Add as AddIcon } from '@mui/icons-material'; import API_BASE_URL from '../lib/consts/API_BASE_URL'; import { DIVIDER } from '../lib/consts/DEFAULT_THEME'; +import AnvilSummaryList from '../components/Anvils/AnvilSummaryList'; import { Preview } from '../components/Display'; import fetchJSON from '../lib/fetchers/fetchJSON'; import Header from '../components/Header'; @@ -17,6 +18,7 @@ import { Panel, PanelHeader } from '../components/Panels'; import periodicFetch from '../lib/fetchers/periodicFetch'; import ProvisionServerDialog from '../components/ProvisionServerDialog'; import Spinner from '../components/Spinner'; +import { HeaderText } from '../components/Text'; import { last } from '../lib/time'; type ServerListItem = ServerOverviewMetadata & { @@ -223,6 +225,10 @@ const Dashboard: FC = () => { ) : ( <> + Servers + setIsOpenProvisionServerDialog(true)}> + + { @@ -232,9 +238,6 @@ const Dashboard: FC = () => { sx={{ marginRight: '.6em' }} value={inputSearchTerm} /> - setIsOpenProvisionServerDialog(true)}> - - {createServerPreviewContainer(includeServers, router)} {includeServers.length > 0 && ( @@ -244,6 +247,7 @@ const Dashboard: FC = () => { )} + { From bd6bc7f7f68abe613387815ac44b26f084003f69 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 2 Oct 2023 20:41:09 -0400 Subject: [PATCH 13/20] fix(striker-ui): add api converter to anvil detail --- striker-ui/lib/api_converters/index.ts | 2 + .../lib/api_converters/toAnvilDetail.ts | 37 +++++++++++++++++++ striker-ui/types/APIAnvil.d.ts | 15 ++++++++ 3 files changed, 54 insertions(+) create mode 100644 striker-ui/lib/api_converters/toAnvilDetail.ts diff --git a/striker-ui/lib/api_converters/index.ts b/striker-ui/lib/api_converters/index.ts index 7ce821fe..73268108 100644 --- a/striker-ui/lib/api_converters/index.ts +++ b/striker-ui/lib/api_converters/index.ts @@ -1,9 +1,11 @@ +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, diff --git a/striker-ui/lib/api_converters/toAnvilDetail.ts b/striker-ui/lib/api_converters/toAnvilDetail.ts new file mode 100644 index 00000000..fcf23e4d --- /dev/null +++ b/striker-ui/lib/api_converters/toAnvilDetail.ts @@ -0,0 +1,37 @@ +const toAnvilDetail = (data: AnvilListItem): APIAnvilDetail => { + const { + anvil_name: anvilName, + anvil_state: anvilState, + anvil_uuid: anvilUuid, + hosts: rHosts, + } = data; + + const hosts = rHosts.reduce((previous, current) => { + const { + host_name: hostName, + host_uuid: hostUuid, + maintenance_mode: maintenance, + state, + state_percent: stateProgress, + } = current; + + previous[hostUuid] = { + name: hostName, + maintenance, + state, + stateProgress, + uuid: hostUuid, + }; + + return previous; + }, {}); + + return { + hosts, + name: anvilName, + state: anvilState, + uuid: anvilUuid, + }; +}; + +export default toAnvilDetail; diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index 37e9cdb4..4276e3c7 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -129,6 +129,21 @@ type APIAnvilOverview = { uuid: string; }; +type APIAnvilDetail = { + hosts: { + [uuid: string]: { + maintenance: boolean; + name: string; + state: AnvilStatusHost['state']; + stateProgress: number; + uuid: string; + }; + }; + name: string; + state: AnvilStatus['anvil_state']; + uuid: string; +}; + type APIAnvilOverviewList = { [uuid: string]: APIAnvilOverview; }; From 134e11489c68260b7a34d44220f43d5f708cbf9a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 2 Oct 2023 20:48:50 -0400 Subject: [PATCH 14/20] fix(striker-ui): add node, subnode summary --- striker-ui/components/Anvils/AnvilSummary.tsx | 100 ++++++++++++++++-- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/striker-ui/components/Anvils/AnvilSummary.tsx b/striker-ui/components/Anvils/AnvilSummary.tsx index 053ab415..39d641ee 100644 --- a/striker-ui/components/Anvils/AnvilSummary.tsx +++ b/striker-ui/components/Anvils/AnvilSummary.tsx @@ -1,10 +1,11 @@ -import { Grid, gridClasses } from '@mui/material'; +import { Grid, gridClasses, typographyClasses } from '@mui/material'; import { dSizeStr } from 'format-data-size'; -import { FC, useMemo } from 'react'; +import { FC, ReactNode, useMemo } from 'react'; -import { BLUE, PURPLE, RED } from '../../lib/consts/DEFAULT_THEME'; +import { BLUE, GREY, PURPLE, RED } from '../../lib/consts/DEFAULT_THEME'; import { + toAnvilDetail, toAnvilMemoryCalcable, toAnvilSharedStorageOverview, } from '../../lib/api_converters'; @@ -14,11 +15,31 @@ import StackBar from '../Bars/StackBar'; import { BodyText, InlineMonoText, MonoText } from '../Text'; import useFetch from '../../hooks/useFetch'; -const n100 = BigInt(100); +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 = { + offline: PURPLE, + online: BLUE, +}; const AnvilSummary: FC = (props) => { const { anvilUuid } = props; + const { data: rAnvil, loading: loadingAnvil } = useFetch( + `/anvil/${anvilUuid}`, + ); + + const anvil = useMemo( + () => rAnvil && toAnvilDetail(rAnvil), + [rAnvil], + ); + const { data: cpu, loading: loadingCpu } = useFetch( `/anvil/${anvilUuid}/cpu`, ); @@ -46,8 +67,57 @@ const AnvilSummary: FC = (props) => { ); const loading = useMemo( - () => loadingCpu || loadingMemory || loadingStorages, - [loadingCpu, loadingMemory, loadingStorages], + () => + [loadingAnvil, loadingCpu, loadingMemory, loadingStorages].some( + (cond) => cond, + ), + [loadingAnvil, loadingCpu, loadingMemory, loadingStorages], + ); + + const anvilSummary = useMemo( + () => + anvil && ( + + {anvil.state} + + ), + [anvil], + ); + + const hostsSummary = useMemo( + () => + anvil && ( + .${typographyClasses.root}:first-child`]: { + marginBottom: '-.6em', + }, + }} + > + {Object.values(anvil.hosts).map((host) => { + const { name, state, stateProgress, uuid } = host; + + const stateColour: string = MAP_TO_HOST_STATE_COLOUR[state] ?? GREY; + + let stateValue: string = state; + + if (!['offline', 'online'].includes(state)) { + stateValue = `${stateProgress}%`; + } + + return ( + + {name}{' '} + + {stateValue} + + + ); + })} + + ), + [anvil], ); const cpuSummary = useMemo( @@ -114,11 +184,11 @@ const AnvilSummary: FC = (props) => { thin value={{ reserved: { - value: Number((memory.reserved * n100) / memory.total), + value: Number((memory.reserved * N_100) / memory.total), }, allocated: { value: Number( - ((memory.reserved + memory.allocated) * n100) / memory.total, + ((memory.reserved + memory.allocated) * N_100) / memory.total, ), colour: { 0: BLUE, 70: PURPLE, 90: RED }, }, @@ -150,7 +220,7 @@ const AnvilSummary: FC = (props) => { value={{ allocated: { value: Number( - ((storages.totalSize - storages.totalFree) * n100) / + ((storages.totalSize - storages.totalFree) * N_100) / storages.totalSize, ), colour: { 0: BLUE, 70: PURPLE, 90: RED }, @@ -177,6 +247,18 @@ const AnvilSummary: FC = (props) => { }, }} > + + Node + + + {anvilSummary} + + + Subnodes + + + {hostsSummary} + CPU From 3e27af973bac60b735c0de236ef82a3d6736cb9e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 2 Oct 2023 22:49:30 -0400 Subject: [PATCH 15/20] fix(striker-ui): adjust texts in anvil summary --- striker-ui/components/Anvils/AnvilSummary.tsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/striker-ui/components/Anvils/AnvilSummary.tsx b/striker-ui/components/Anvils/AnvilSummary.tsx index 39d641ee..ab8dd523 100644 --- a/striker-ui/components/Anvils/AnvilSummary.tsx +++ b/striker-ui/components/Anvils/AnvilSummary.tsx @@ -107,7 +107,11 @@ const AnvilSummary: FC = (props) => { } return ( - + {name}{' '} {stateValue} @@ -124,10 +128,11 @@ const AnvilSummary: FC = (props) => { () => cpu && cpuSubnodes && ( - + - {cpuSubnodes[0].name} - {cpuSubnodes[0].vendor} + + Vendor {cpuSubnodes[0].vendor} + = (props) => { width="calc(0% + 4em)" > - CORES + Cores {cpu.cores} - THREADS + Threads {cpu.threads} - - {cpuSubnodes[1].name} - {cpuSubnodes[1].vendor} - ), [cpu, cpuSubnodes], @@ -168,7 +169,7 @@ const AnvilSummary: FC = (props) => { - FREE + Free {dSizeStr(memory.total - (memory.reserved + memory.allocated), { toUnit: 'ibyte', @@ -205,7 +206,7 @@ const AnvilSummary: FC = (props) => { - FREE + Total free {dSizeStr(storages.totalFree, { toUnit: 'ibyte' })} From a21d0a9881a371453ea637566c921c31891c50ab Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 2 Oct 2023 22:54:42 -0400 Subject: [PATCH 16/20] fix(striker-ui): hide node description if too long --- striker-ui/components/Anvils/AnvilSummaryList.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/Anvils/AnvilSummaryList.tsx b/striker-ui/components/Anvils/AnvilSummaryList.tsx index 5c09eb5f..135b4b56 100644 --- a/striker-ui/components/Anvils/AnvilSummaryList.tsx +++ b/striker-ui/components/Anvils/AnvilSummaryList.tsx @@ -38,7 +38,11 @@ const AnvilSummaryList: FC = () => { children: ( - + {name}: {description} From 352f63a8495296fee84cd02374b45c31e1af17ab Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 3 Oct 2023 01:20:27 -0400 Subject: [PATCH 17/20] fix(striker-ui-api): add server count to subnode summary --- .../anvil/buildAnvilSummary.ts | 39 +++++++++++++++++++ striker-ui-api/src/types/ApiAn.d.ts | 1 + 2 files changed, 40 insertions(+) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/buildAnvilSummary.ts b/striker-ui-api/src/lib/request_handlers/anvil/buildAnvilSummary.ts index a0dfc1d9..99be97a0 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/buildAnvilSummary.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/buildAnvilSummary.ts @@ -34,6 +34,34 @@ export const buildAnvilSummary = async ({ hosts: [], }; + let scounts: [scount: number, hostUuid: string][]; + + try { + scounts = await query( + `SELECT + COUNT(a.server_name), + b.host_uuid + FROM servers AS a + JOIN hosts AS b + ON a.server_host_uuid = b.host_uuid + JOIN anvils AS c + ON b.host_uuid IN ( + c.anvil_node1_host_uuid, + c.anvil_node2_host_uuid + ) + WHERE c.anvil_uuid = '${anvilUuid}' + AND a.server_state = 'running' + GROUP BY b.host_uuid, b.host_name + ORDER BY b.host_name;`, + ); + } catch (error) { + stderr(`Failed to get subnodes' server count; CAUSE: ${error}`); + + throw error; + } + + if (!scounts.length) throw new Error(`No host server records found`); + for (const huuid of [n1uuid, n2uuid]) { const { host_uuid: { @@ -43,10 +71,21 @@ export const buildAnvilSummary = async ({ const { hosts: rhosts } = result; + const found = scounts.find((row) => { + if (row.length !== 2) return false; + + const { 1: uuid } = row; + + return uuid === huuid; + }); + + const scount = found ? found[0] : 0; + const hsummary: AnvilDetailHostSummary = { host_name: hname, host_uuid: huuid, maintenance_mode: false, + server_count: scount, state: 'offline', state_message: buildHostStateMessage(), state_percent: 0, diff --git a/striker-ui-api/src/types/ApiAn.d.ts b/striker-ui-api/src/types/ApiAn.d.ts index 1ead1181..41948e9e 100644 --- a/striker-ui-api/src/types/ApiAn.d.ts +++ b/striker-ui-api/src/types/ApiAn.d.ts @@ -86,6 +86,7 @@ type AnvilDetailHostSummary = { host_name: string; host_uuid: string; maintenance_mode: boolean; + server_count: number; state: string; state_message: string; state_percent: number; From f4a1ab5a0be92b54f6dbb34934bd0e346e1918b2 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 3 Oct 2023 02:56:36 -0400 Subject: [PATCH 18/20] fix(striker-ui): add servers to subnodes, revise node summary layout --- striker-ui/components/Anvils/AnvilSummary.tsx | 88 +++++++++++++------ .../components/Anvils/AnvilSummaryList.tsx | 2 +- .../lib/api_converters/toAnvilDetail.ts | 2 + striker-ui/types/APIAnvil.d.ts | 2 + 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/striker-ui/components/Anvils/AnvilSummary.tsx b/striker-ui/components/Anvils/AnvilSummary.tsx index ab8dd523..a8c9cd19 100644 --- a/striker-ui/components/Anvils/AnvilSummary.tsx +++ b/striker-ui/components/Anvils/AnvilSummary.tsx @@ -1,4 +1,4 @@ -import { Grid, gridClasses, typographyClasses } from '@mui/material'; +import { Grid, gridClasses } from '@mui/material'; import { dSizeStr } from 'format-data-size'; import { FC, ReactNode, useMemo } from 'react'; @@ -9,6 +9,7 @@ import { toAnvilMemoryCalcable, toAnvilSharedStorageOverview, } from '../../lib/api_converters'; +import Divider from '../Divider'; import FlexBox from '../FlexBox'; import Spinner from '../Spinner'; import StackBar from '../Bars/StackBar'; @@ -87,39 +88,63 @@ const AnvilSummary: FC = (props) => { const hostsSummary = useMemo( () => anvil && ( - .${typographyClasses.root}:first-child`]: { + [`& > .${gridClasses.item}:nth-child(-n + 4)`]: { marginBottom: '-.6em', }, }} > {Object.values(anvil.hosts).map((host) => { - const { name, state, stateProgress, uuid } = 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)) { + if (['offline', 'online'].includes(state)) { + servers = ( + + Servers{' '} + + {serverCount} + + + ); + } else { stateValue = `${stateProgress}%`; } - return ( - - {name}{' '} - + return [ + + + {name} + + , + + {stateValue} - - - ); + + , + + + , + + {servers} + , + ]; })} - + ), [anvil], ); @@ -128,22 +153,27 @@ const AnvilSummary: FC = (props) => { () => cpu && cpuSubnodes && ( - + - Vendor {cpuSubnodes[0].vendor} + Vendor{' '} + + {cpuSubnodes[0].vendor} + + .${gridClasses.item}:nth-child(-n + 2)`]: { marginBottom: '-.6em', }, }} - width="calc(0% + 4em)" + width="calc(0% + 3.2em)" > Cores @@ -238,44 +268,44 @@ const AnvilSummary: FC = (props) => { ) : ( .${gridClasses.item}:nth-child(odd)`]: { alignItems: 'center', display: 'flex', - height: '2.5em', + height: '2.2em', }, }} > Node - + {anvilSummary} Subnodes - + {hostsSummary} CPU - + {cpuSummary} Memory - + {memorySummary} Storage - + {storeSummary} diff --git a/striker-ui/components/Anvils/AnvilSummaryList.tsx b/striker-ui/components/Anvils/AnvilSummaryList.tsx index 135b4b56..3a47e8d8 100644 --- a/striker-ui/components/Anvils/AnvilSummaryList.tsx +++ b/striker-ui/components/Anvils/AnvilSummaryList.tsx @@ -36,7 +36,7 @@ const AnvilSummaryList: FC = () => { previous[key] = { children: ( - + { host_name: hostName, host_uuid: hostUuid, maintenance_mode: maintenance, + server_count: serverCount, state, state_percent: stateProgress, } = current; @@ -18,6 +19,7 @@ const toAnvilDetail = (data: AnvilListItem): APIAnvilDetail => { previous[hostUuid] = { name: hostName, maintenance, + serverCount, state, stateProgress, uuid: hostUuid, diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index 4276e3c7..be4a7698 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -86,6 +86,7 @@ type AnvilStatusHost = { host_name: string; host_uuid: string; maintenance_mode: boolean; + server_count: number; state: 'offline' | 'booted' | 'crmd' | 'in_ccm' | 'online'; state_message: string; state_percent: number; @@ -134,6 +135,7 @@ type APIAnvilDetail = { [uuid: string]: { maintenance: boolean; name: string; + serverCount: number; state: AnvilStatusHost['state']; stateProgress: number; uuid: string; From 0b9d6be08cd900addb773bf5ef47c06d6189a6b2 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 3 Oct 2023 19:12:23 -0400 Subject: [PATCH 19/20] fix(striker-ui): prevent text overlap in anvil summary --- striker-ui/components/Anvils/AnvilSummary.tsx | 29 +++++++++---------- .../components/Anvils/AnvilSummaryList.tsx | 13 +++++++-- striker-ui/types/AnvilSummary.d.ts | 6 +--- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/striker-ui/components/Anvils/AnvilSummary.tsx b/striker-ui/components/Anvils/AnvilSummary.tsx index a8c9cd19..002ba2f6 100644 --- a/striker-ui/components/Anvils/AnvilSummary.tsx +++ b/striker-ui/components/Anvils/AnvilSummary.tsx @@ -90,7 +90,8 @@ const AnvilSummary: FC = (props) => { anvil && ( .${gridClasses.item}:nth-child(-n + 4)`]: { @@ -107,38 +108,34 @@ const AnvilSummary: FC = (props) => { let servers: ReactNode; if (['offline', 'online'].includes(state)) { - servers = ( - - Servers{' '} - - {serverCount} - - - ); + servers = {serverCount}; } else { stateValue = `${stateProgress}%`; } return [ - + {name} , - + {stateValue} , - - + + + , + + {servers && Servers} , {servers} , @@ -167,13 +164,13 @@ const AnvilSummary: FC = (props) => { alignItems="center" columns={2} container - minWidth="calc(0% + 3.2em)" sx={{ + width: '3.7em', + [`& > .${gridClasses.item}:nth-child(-n + 2)`]: { marginBottom: '-.6em', }, }} - width="calc(0% + 3.2em)" > Cores diff --git a/striker-ui/components/Anvils/AnvilSummaryList.tsx b/striker-ui/components/Anvils/AnvilSummaryList.tsx index 3a47e8d8..e22a4be3 100644 --- a/striker-ui/components/Anvils/AnvilSummaryList.tsx +++ b/striker-ui/components/Anvils/AnvilSummaryList.tsx @@ -1,3 +1,4 @@ +import { gridClasses } from '@mui/material'; import { FC, ReactNode, useMemo } from 'react'; import AnvilSummary from './AnvilSummary'; @@ -27,7 +28,7 @@ const AnvilSummaryList: FC = () => { () => anvils && ( ( (previous, current) => { const { description, name, uuid } = current; @@ -36,7 +37,7 @@ const AnvilSummaryList: FC = () => { previous[key] = { children: ( - + { }, {}, )} + spacing="1em" + sx={{ + alignContent: 'stretch', + + [`& > .${gridClasses.item}`]: { + minWidth: '20em', + }, + }} /> ), [anvils], diff --git a/striker-ui/types/AnvilSummary.d.ts b/striker-ui/types/AnvilSummary.d.ts index 8cfd05ff..2a00698d 100644 --- a/striker-ui/types/AnvilSummary.d.ts +++ b/striker-ui/types/AnvilSummary.d.ts @@ -1,7 +1,3 @@ -type AnvilSummaryOptionalProps = { - loading?: boolean; -}; - -type AnvilSummaryProps = AnvilSummaryOptionalProps & { +type AnvilSummaryProps = { anvilUuid: string; }; From 951dea75356761d87799b1e9e91ab62e99282843 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 3 Oct 2023 19:21:09 -0400 Subject: [PATCH 20/20] fix(striker-ui): align width, height of server previews in Dashboard --- striker-ui/pages/index.tsx | 109 +++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/striker-ui/pages/index.tsx b/striker-ui/pages/index.tsx index cb21383d..b4d7986b 100644 --- a/striker-ui/pages/index.tsx +++ b/striker-ui/pages/index.tsx @@ -1,8 +1,8 @@ +import { Add as AddIcon } from '@mui/icons-material'; +import { Box, Divider, Grid } from '@mui/material'; import Head from 'next/head'; import { NextRouter, useRouter } from 'next/router'; import { FC, useEffect, useRef, useState } from 'react'; -import { Box, Divider } from '@mui/material'; -import { Add as AddIcon } from '@mui/icons-material'; import API_BASE_URL from '../lib/consts/API_BASE_URL'; import { DIVIDER } from '../lib/consts/DEFAULT_THEME'; @@ -32,20 +32,11 @@ const createServerPreviewContainer = ( servers: ServerListItem[], router: NextRouter, ) => ( - *': { - width: { xs: '20em', md: '24em' }, - }, - - '& > :not(:last-child)': { - marginRight: '2em', - }, - }} + {servers.map( ({ @@ -59,43 +50,57 @@ const createServerPreviewContainer = ( serverUUID, timestamp, }) => ( - - {serverName} - , - - {anvilName} - , - ]} - isExternalLoading={loading} - isExternalPreviewStale={isScreenshotStale} - isFetchPreview={false} - isShowControls={false} - isUseInnerPanel - key={`server-preview-${serverUUID}`} - onClickPreview={() => { - router.push( - `/server?uuid=${serverUUID}&server_name=${serverName}&server_state=${serverState}&vnc=1`, - ); + div': { + height: '100%', + marginBottom: 0, + marginTop: 0, + }, }} - serverState={serverState} - serverUUID={serverUUID} - /> + xs={1} + > + + {serverName} + , + + {anvilName} + , + ]} + isExternalLoading={loading} + isExternalPreviewStale={isScreenshotStale} + isFetchPreview={false} + isShowControls={false} + isUseInnerPanel + onClickPreview={() => { + router.push( + `/server?uuid=${serverUUID}&server_name=${serverName}&server_state=${serverState}&vnc=1`, + ); + }} + serverState={serverState} + serverUUID={serverUUID} + /> + ), )} - + ); const filterServers = (allServers: ServerListItem[], searchTerm: string) => @@ -224,7 +229,7 @@ const Dashboard: FC = () => { ) : ( <> - + Servers setIsOpenProvisionServerDialog(true)}> @@ -235,7 +240,7 @@ const Dashboard: FC = () => { setInputSearchTerm(value); updateServerList(allServers, value); }} - sx={{ marginRight: '.6em' }} + sx={{ minWidth: '16em' }} value={inputSearchTerm} />