From 6cdca8ee5f5b01b7fbc4aef4451103f705d97510 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 6 Mar 2023 19:25:42 -0500 Subject: [PATCH 01/86] feat(striker-ui-api): add /manifest --- .../request_handlers/manifest/getManifest.ts | 33 +++++++++++++++++++ .../lib/request_handlers/manifest/index.ts | 1 + striker-ui-api/src/routes/index.ts | 2 ++ striker-ui-api/src/routes/manifest.ts | 9 +++++ striker-ui-api/src/types/APIManifest.d.ts | 4 +++ 5 files changed, 49 insertions(+) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/index.ts create mode 100644 striker-ui-api/src/routes/manifest.ts create mode 100644 striker-ui-api/src/types/APIManifest.d.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts new file mode 100644 index 00000000..bf0ce4e6 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts @@ -0,0 +1,33 @@ +import { RequestHandler } from 'express'; + +import buildGetRequestHandler from '../buildGetRequestHandler'; +import { buildQueryResultReducer } from '../../buildQueryResultModifier'; + +export const getManifest: RequestHandler = buildGetRequestHandler( + (response, buildQueryOptions) => { + const query = ` + SELECT + manifest_uuid, + manifest_name + FROM manifests + ORDER BY manifest_name ASC;`; + const afterQueryReturn: QueryResultModifierFunction | undefined = + buildQueryResultReducer<{ [manifestUUID: string]: ManifestOverview }>( + (previous, [manifestUUID, manifestName]) => { + previous[manifestUUID] = { + manifestName, + manifestUUID, + }; + + return previous; + }, + {}, + ); + + if (buildQueryOptions) { + buildQueryOptions.afterQueryReturn = afterQueryReturn; + } + + return query; + }, +); diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts new file mode 100644 index 00000000..79e54734 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -0,0 +1 @@ +export * from './getManifest'; diff --git a/striker-ui-api/src/routes/index.ts b/striker-ui-api/src/routes/index.ts index 618e952c..ef703777 100644 --- a/striker-ui-api/src/routes/index.ts +++ b/striker-ui-api/src/routes/index.ts @@ -7,6 +7,7 @@ import fenceRouter from './fence'; import fileRouter from './file'; import hostRouter from './host'; import jobRouter from './job'; +import manifestRouter from './manifest'; import networkInterfaceRouter from './network-interface'; import serverRouter from './server'; import sshKeyRouter from './ssh-key'; @@ -21,6 +22,7 @@ const routes: Readonly> = { file: fileRouter, host: hostRouter, job: jobRouter, + manifest: manifestRouter, 'network-interface': networkInterfaceRouter, server: serverRouter, 'ssh-key': sshKeyRouter, diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts new file mode 100644 index 00000000..2502a183 --- /dev/null +++ b/striker-ui-api/src/routes/manifest.ts @@ -0,0 +1,9 @@ +import express from 'express'; + +import { getManifest } from '../lib/request_handlers/manifest'; + +const router = express.Router(); + +router.get('/', getManifest); + +export default router; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts new file mode 100644 index 00000000..0e846a90 --- /dev/null +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -0,0 +1,4 @@ +type ManifestOverview = { + manifestName: string; + manifestUUID: string; +}; From 0ce470a034caa9fd102285861dcc9ac936e17ef7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 9 Mar 2023 22:32:47 -0500 Subject: [PATCH 02/86] fix(striker-ui): manifest list --- .../ManageManifest/ManageManifestPanel.tsx | 60 +++++++++++++++++++ .../components/ManageManifest/index.tsx | 3 + striker-ui/types/APIManifest.d.ts | 8 +++ 3 files changed, 71 insertions(+) create mode 100644 striker-ui/components/ManageManifest/ManageManifestPanel.tsx create mode 100644 striker-ui/components/ManageManifest/index.tsx create mode 100644 striker-ui/types/APIManifest.d.ts diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx new file mode 100644 index 00000000..f5256dfe --- /dev/null +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -0,0 +1,60 @@ +import { PlayCircle } from '@mui/icons-material'; +import { FC, useMemo, useState } from 'react'; +import API_BASE_URL from '../../lib/consts/API_BASE_URL'; +import periodicFetch from '../../lib/fetchers/periodicFetch'; +import FlexBox from '../FlexBox'; +import IconButton from '../IconButton'; +import List from '../List'; +import { Panel, PanelHeader } from '../Panels'; +import Spinner from '../Spinner'; +import { BodyText, HeaderText } from '../Text'; + +const ManageManifestPanel: FC = () => { + const [isEditManifests, setIsEditManifests] = useState(false); + + const { data: manifestOverviews, isLoading: isLoadingManifestOverviews } = + periodicFetch(`${API_BASE_URL}/manifest`, { + refreshInterval: 60000, + }); + + const listElement = useMemo( + () => ( + { + setIsEditManifests((previous) => !previous); + }} + renderListItem={(manifestUUID, { manifestName }) => ( + + + + + {manifestName} + + )} + /> + ), + [isEditManifests, manifestOverviews], + ); + + const panelContent = useMemo( + () => (isLoadingManifestOverviews ? : listElement), + [isLoadingManifestOverviews, listElement], + ); + + return ( + + + Manage manifests + + {panelContent} + + ); +}; + +export default ManageManifestPanel; diff --git a/striker-ui/components/ManageManifest/index.tsx b/striker-ui/components/ManageManifest/index.tsx new file mode 100644 index 00000000..815cbed8 --- /dev/null +++ b/striker-ui/components/ManageManifest/index.tsx @@ -0,0 +1,3 @@ +import ManageManifestPanel from './ManageManifestPanel'; + +export default ManageManifestPanel; diff --git a/striker-ui/types/APIManifest.d.ts b/striker-ui/types/APIManifest.d.ts new file mode 100644 index 00000000..85a07e86 --- /dev/null +++ b/striker-ui/types/APIManifest.d.ts @@ -0,0 +1,8 @@ +type APIManifestOverview = { + manifestName: string; + manifestUUID: string; +}; + +type APIManifestOverviewList = { + [manifestUUID: string]: APIManifestOverview; +}; From fb8e92ffb47f7e4c312a19a339ecff64372c1e06 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 13 Mar 2023 16:10:18 -0400 Subject: [PATCH 03/86] fix(striker-ui): consolidate network types --- striker-ui/components/NetworkInitForm.tsx | 24 ++++++++--------------- striker-ui/lib/consts/NETWORK_TYPES.ts | 2 ++ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/striker-ui/components/NetworkInitForm.tsx b/striker-ui/components/NetworkInitForm.tsx index 507454e1..db4a9e8d 100644 --- a/striker-ui/components/NetworkInitForm.tsx +++ b/striker-ui/components/NetworkInitForm.tsx @@ -34,6 +34,7 @@ import { v4 as uuidv4 } from 'uuid'; import API_BASE_URL from '../lib/consts/API_BASE_URL'; import { BLUE, GREY } from '../lib/consts/DEFAULT_THEME'; +import NETWORK_TYPES from '../lib/consts/NETWORK_TYPES'; import { REP_IPV4, REP_IPV4_CSV } from '../lib/consts/REG_EXP_PATTERNS'; import BriefNetworkInterface from './BriefNetworkInterface'; @@ -106,17 +107,6 @@ const CLASSES = { }; const INITIAL_IFACES = [undefined, undefined]; -const NETWORK_TYPES: Record = { - bcn: 'Back-Channel Network', - ifn: 'Internet-Facing Network', - sn: 'Storage Network', -}; - -const NODE_NETWORK_TYPES: Record = { - ...NETWORK_TYPES, - mn: 'Migration Network', -}; - const STRIKER_REQUIRED_NETWORKS: NetworkInput[] = [ { inputUUID: '30dd2ac5-8024-4a7e-83a1-6a3df7218972', @@ -349,11 +339,13 @@ const NetworkForm: FC<{ !isNode && networkInterfaceCount <= 2 ? [1] : NETWORK_INTERFACE_TEMPLATE, [isNode, networkInterfaceCount], ); - const netTypeList = useMemo( - () => - isNode && networkInterfaceCount >= 8 ? NODE_NETWORK_TYPES : NETWORK_TYPES, - [isNode, networkInterfaceCount], - ); + const netTypeList = useMemo(() => { + const { bcn, ifn, mn, sn } = NETWORK_TYPES; + + return isNode && networkInterfaceCount >= 8 + ? { bcn, ifn, mn, sn } + : { bcn, ifn, sn }; + }, [isNode, networkInterfaceCount]); useEffect(() => { const { ipAddressInputRef: ipRef, subnetMaskInputRef: maskRef } = diff --git a/striker-ui/lib/consts/NETWORK_TYPES.ts b/striker-ui/lib/consts/NETWORK_TYPES.ts index 6720774e..a1954860 100644 --- a/striker-ui/lib/consts/NETWORK_TYPES.ts +++ b/striker-ui/lib/consts/NETWORK_TYPES.ts @@ -1,6 +1,8 @@ const NETWORK_TYPES: Record = { bcn: 'Back-Channel Network', ifn: 'Internet-Facing Network', + mn: 'Migration Network', + sn: 'Storage Network', }; export default NETWORK_TYPES; From 15942d258de0c17a38dcce06d7c413904daca805 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 14 Mar 2023 17:04:51 -0400 Subject: [PATCH 04/86] fix(striker-ui): adjust inner panel margin --- striker-ui/components/Panels/InnerPanel.tsx | 34 ++++++++++++++++--- .../components/Panels/InnerPanelBody.tsx | 29 +++++++++------- striker-ui/types/InnerPanel.d.ts | 8 ++++- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/striker-ui/components/Panels/InnerPanel.tsx b/striker-ui/components/Panels/InnerPanel.tsx index 65239514..8abb0613 100644 --- a/striker-ui/components/Panels/InnerPanel.tsx +++ b/striker-ui/components/Panels/InnerPanel.tsx @@ -3,15 +3,33 @@ import { Box as MUIBox, SxProps, Theme } from '@mui/material'; import { BORDER_RADIUS, DIVIDER } from '../../lib/consts/DEFAULT_THEME'; -const InnerPanel: FC = ({ sx, ...muiBoxRestProps }) => { +const InnerPanel: FC = ({ + headerMarginOffset: hmo = '.3em', + ml, + mv = '1.4em', + sx, + // Props that depend on others. + mb = mv, + mt = mv, + + ...muiBoxRestProps +}) => { + const marginLeft = useMemo( + () => (ml ? `calc(${ml} + ${hmo})` : hmo), + [hmo, ml], + ); + const marginTop = useMemo(() => { + const resultMt = typeof mt === 'number' ? `${mt}px` : mt; + + return `calc(${resultMt} + ${hmo})`; + }, [hmo, mt]); + const combinedSx = useMemo>( () => ({ borderWidth: '1px', borderRadius: BORDER_RADIUS, borderStyle: 'solid', borderColor: DIVIDER, - marginTop: '1.4em', - marginBottom: '1.4em', paddingBottom: 0, position: 'relative', @@ -20,7 +38,15 @@ const InnerPanel: FC = ({ sx, ...muiBoxRestProps }) => { [sx], ); - return ; + return ( + + ); }; export default InnerPanel; diff --git a/striker-ui/components/Panels/InnerPanelBody.tsx b/striker-ui/components/Panels/InnerPanelBody.tsx index 3b7e694b..2ffa7e8d 100644 --- a/striker-ui/components/Panels/InnerPanelBody.tsx +++ b/striker-ui/components/Panels/InnerPanelBody.tsx @@ -1,17 +1,20 @@ -import { Box, BoxProps } from '@mui/material'; -import { FC } from 'react'; +import { Box, BoxProps, SxProps, Theme } from '@mui/material'; +import { FC, useMemo } from 'react'; -const InnerPanelBody: FC = ({ sx, ...innerPanelBodyRestProps }) => ( - = ({ sx, ...innerPanelBodyRestProps }) => { + const combinedSx = useMemo>( + () => ({ + position: 'relative', + zIndex: 20, - ...sx, - }, - }} - /> -); + ...sx, + }), + [sx], + ); + + return ( + + ); +}; export default InnerPanelBody; diff --git a/striker-ui/types/InnerPanel.d.ts b/striker-ui/types/InnerPanel.d.ts index 0f0b3adb..7e5e9353 100644 --- a/striker-ui/types/InnerPanel.d.ts +++ b/striker-ui/types/InnerPanel.d.ts @@ -1 +1,7 @@ -type InnerPanelProps = import('@mui/material').BoxProps; +type InnerPanelOptionalProps = { + headerMarginOffset?: number | string; + mv?: number | string; +}; + +type InnerPanelProps = InnerPanelOptionalProps & + import('@mui/material').BoxProps; From 29a3751524a98a08e14608778e736a718bfc70c0 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 14 Mar 2023 17:10:44 -0400 Subject: [PATCH 05/86] fix(striker-ui): export buildNumberTestBatch() --- striker-ui/lib/test_input/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/striker-ui/lib/test_input/index.ts b/striker-ui/lib/test_input/index.ts index 1f9e23cb..ca39f5f6 100644 --- a/striker-ui/lib/test_input/index.ts +++ b/striker-ui/lib/test_input/index.ts @@ -1,5 +1,6 @@ import buildDomainTestBatch from './buildDomainTestBatch'; import buildIPAddressTestBatch from './buildIPAddressTestBatch'; +import buildNumberTestBatch from './buildNumberTestBatch'; import buildPeacefulStringTestBatch from './buildPeacefulStringTestBatch'; import buildUUIDTestBatch from './buildUUIDTestBatch'; import createTestInputFunction from './createTestInputFunction'; @@ -12,6 +13,7 @@ import testRange from './testRange'; export { buildDomainTestBatch, buildIPAddressTestBatch, + buildNumberTestBatch, buildPeacefulStringTestBatch, buildUUIDTestBatch, createTestInputFunction, From 77abfdc933f6d2884308179e0269e786fbe789ce Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 14 Mar 2023 19:47:38 -0400 Subject: [PATCH 06/86] fix(striker-ui): export buildMessageSetter() --- striker-ui/lib/buildMapToMessageSetter.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/striker-ui/lib/buildMapToMessageSetter.ts b/striker-ui/lib/buildMapToMessageSetter.ts index 5152ab33..0b5f9035 100644 --- a/striker-ui/lib/buildMapToMessageSetter.ts +++ b/striker-ui/lib/buildMapToMessageSetter.ts @@ -47,4 +47,6 @@ const buildMapToMessageSetter = < return result; }; +export { buildMessageSetter }; + export default buildMapToMessageSetter; From ae07c8b66d96ed9ee69b9baed52c3f034d28cfc3 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 14 Mar 2023 19:48:46 -0400 Subject: [PATCH 07/86] fix(striker-ui): add setMsgSetter in FormUtils --- striker-ui/hooks/useFormUtils.ts | 15 ++++++++++++++- striker-ui/types/FormUtils.d.ts | 5 +++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 70727b88..f866bf1d 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -1,6 +1,8 @@ import { MutableRefObject, useCallback, useMemo, useState } from 'react'; -import buildMapToMessageSetter from '../lib/buildMapToMessageSetter'; +import buildMapToMessageSetter, { + buildMessageSetter, +} from '../lib/buildMapToMessageSetter'; import buildObjectStateSetterCallback from '../lib/buildObjectStateSetterCallback'; import { MessageGroupForwardedRefContent } from '../components/MessageGroup'; @@ -45,6 +47,16 @@ const useFormUtils = < [ids, messageGroupRef], ); + const setMsgSetter = useCallback( + (id: keyof M, setter?: MessageSetterFunction, isOverwrite?: boolean) => { + if (!msgSetters[id] || isOverwrite) { + msgSetters[id] = + setter ?? buildMessageSetter(String(id), messageGroupRef); + } + }, + [messageGroupRef, msgSetters], + ); + return { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, @@ -52,6 +64,7 @@ const useFormUtils = < isFormInvalid, msgSetters, setFormValidity, + setMsgSetter, setValidity, }; }; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 1f106121..5a665490 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -23,5 +23,10 @@ type FormUtils = { setFormValidity: import('react').Dispatch< import('react').SetStateAction> >; + setMsgSetter: ( + id: keyof M, + setter?: MessageSetterFunction, + isOverwrite?: boolean, + ) => void; setValidity: (key: keyof M, value: boolean) => void; }; From e5d7014b9fcc7f82a4c1bf76b1c57583e87bab25 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 14 Mar 2023 23:51:03 -0400 Subject: [PATCH 08/86] fix(striker-ui): run 'set validity on first render' in useEffect --- striker-ui/components/InputWithRef.tsx | 28 +++++++++++++------ .../components/ManageUps/AddUpsInputGroup.tsx | 14 ++++++---- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/striker-ui/components/InputWithRef.tsx b/striker-ui/components/InputWithRef.tsx index 16f769aa..86ebbdca 100644 --- a/striker-ui/components/InputWithRef.tsx +++ b/striker-ui/components/InputWithRef.tsx @@ -5,6 +5,7 @@ import { forwardRef, ReactElement, useCallback, + useEffect, useImperativeHandle, useMemo, useState, @@ -166,15 +167,24 @@ const InputWithRef = forwardRef( [initOnFocus, inputTestBatch], ); - if (isFirstRender) { - const isValid = - testInput?.call(null, { - inputs: { [INPUT_TEST_ID]: { value: inputValue } }, - isIgnoreOnCallbacks: true, - }) ?? false; - - onFirstRender?.call(null, { isValid }); - } + /** + * Using any setState function synchronously in the render function + * directly will trigger the 'cannot update a component while readering a + * different component' warning. This can be solved by wrapping the + * setState call(s) in a useEffect hook because it executes **after** the + * render function completes. + */ + useEffect(() => { + if (isFirstRender) { + const isValid = + testInput?.call(null, { + inputs: { [INPUT_TEST_ID]: { value: inputValue } }, + isIgnoreOnCallbacks: true, + }) ?? false; + + onFirstRender?.call(null, { isValid }); + } + }, [input.props.id, inputValue, isFirstRender, onFirstRender, testInput]); useImperativeHandle( ref, diff --git a/striker-ui/components/ManageUps/AddUpsInputGroup.tsx b/striker-ui/components/ManageUps/AddUpsInputGroup.tsx index e0896c23..9d8e83ff 100644 --- a/striker-ui/components/ManageUps/AddUpsInputGroup.tsx +++ b/striker-ui/components/ManageUps/AddUpsInputGroup.tsx @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode, useMemo, useState } from 'react'; +import { ReactElement, ReactNode, useEffect, useMemo, useState } from 'react'; import { BLACK } from '../../lib/consts/DEFAULT_THEME'; @@ -142,11 +142,13 @@ const AddUpsInputGroup = < ], ); - if (isFirstRender) { - buildInputFirstRenderFunction(INPUT_ID_UPS_TYPE)({ - isValid: Boolean(inputUpsTypeIdValue), - }); - } + useEffect(() => { + if (isFirstRender) { + buildInputFirstRenderFunction(INPUT_ID_UPS_TYPE)({ + isValid: Boolean(inputUpsTypeIdValue), + }); + } + }, [buildInputFirstRenderFunction, inputUpsTypeIdValue, isFirstRender]); return content; }; From 17657388af455f0878a2dc7ac842431b462d1c76 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 01:23:18 -0400 Subject: [PATCH 09/86] fix(striker-ui): add preset icon props in IconButton presets --- .../components/IconButton/IconButton.tsx | 41 +++++++++++-------- striker-ui/types/IconButton.d.ts | 13 ++++-- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/striker-ui/components/IconButton/IconButton.tsx b/striker-ui/components/IconButton/IconButton.tsx index 1eae04e5..11ef960b 100644 --- a/striker-ui/components/IconButton/IconButton.tsx +++ b/striker-ui/components/IconButton/IconButton.tsx @@ -14,6 +14,7 @@ import { createElement, FC, ReactNode, useMemo } from 'react'; import { BLACK, + BLUE, BORDER_RADIUS, DISABLED, GREY, @@ -39,19 +40,19 @@ const NormalIconButton = styled(MUIIconButton)({ color: GREY, }); -const MAP_TO_VISIBILITY_ICON: IconButtonMapToStateIcon = { - false: MUIVisibilityIcon, - true: MUIVisibilityOffIcon, +const MAP_TO_VISIBILITY_ICON: IconButtonMapToStateIconBundle = { + false: { iconType: MUIVisibilityIcon }, + true: { iconType: MUIVisibilityOffIcon }, }; -const MAP_TO_EDIT_ICON: IconButtonMapToStateIcon = { - false: MUIEditIcon, - true: MUIDoneIcon, +const MAP_TO_EDIT_ICON: IconButtonMapToStateIconBundle = { + false: { iconType: MUIEditIcon }, + true: { iconType: MUIDoneIcon, iconProps: { sx: { color: BLUE } } }, }; const MAP_TO_MAP_PRESET: Record< - IconButtonPresetMapToStateIcon, - IconButtonMapToStateIcon + IconButtonPresetMapToStateIconBundle, + IconButtonMapToStateIconBundle > = { edit: MAP_TO_EDIT_ICON, visibility: MAP_TO_VISIBILITY_ICON, @@ -72,28 +73,36 @@ const IconButton: FC = ({ variant = 'contained', ...restIconButtonProps }) => { - const mapToIcon = useMemo( + const mapToIcon = useMemo( () => externalMapToIcon ?? (mapPreset && MAP_TO_MAP_PRESET[mapPreset]), [externalMapToIcon, mapPreset], ); + const defaultIconBundle = useMemo>( + () => ({ iconType: defaultIcon }), + [defaultIcon], + ); + const iconButtonContent = useMemo(() => { let result: ReactNode; if (mapToIcon) { - const iconElementType: CreatableComponent | undefined = state - ? mapToIcon[state] ?? defaultIcon - : defaultIcon; - - if (iconElementType) { - result = createElement(iconElementType, iconProps); + const { iconType, iconProps: presetIconProps } = state + ? mapToIcon[state] ?? defaultIconBundle + : defaultIconBundle; + + if (iconType) { + result = createElement(iconType, { + ...presetIconProps, + ...iconProps, + }); } } else { result = children; } return result; - }, [children, mapToIcon, state, defaultIcon, iconProps]); + }, [mapToIcon, state, defaultIconBundle, iconProps, children]); const iconButtonElementType = useMemo( () => MAP_TO_VARIANT[variant], [variant], diff --git a/striker-ui/types/IconButton.d.ts b/striker-ui/types/IconButton.d.ts index af8ecf5a..7009aa4f 100644 --- a/striker-ui/types/IconButton.d.ts +++ b/striker-ui/types/IconButton.d.ts @@ -1,16 +1,21 @@ type CreatableComponent = Parameters[0]; -type IconButtonPresetMapToStateIcon = 'edit' | 'visibility'; +type IconButtonPresetMapToStateIconBundle = 'edit' | 'visibility'; -type IconButtonMapToStateIcon = Record; +type IconButtonStateIconBundle = { + iconType: CreatableComponent; + iconProps?: import('@mui/material').SvgIconProps; +}; + +type IconButtonMapToStateIconBundle = Record; type IconButtonVariant = 'contained' | 'normal'; type IconButtonOptionalProps = { defaultIcon?: CreatableComponent; iconProps?: import('@mui/material').SvgIconProps; - mapPreset?: IconButtonPresetMapToStateIcon; - mapToIcon?: IconButtonMapToStateIcon; + mapPreset?: IconButtonPresetMapToStateIconBundle; + mapToIcon?: IconButtonMapToStateIconBundle; state?: string; variant?: IconButtonVariant; }; From f59d273ebbde2d6f2fd5a0118880c66944624b13 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 02:09:12 -0400 Subject: [PATCH 10/86] fix(striker-ui): add 'add', 'close' presets to IconButton --- .../components/IconButton/IconButton.tsx | 33 +++++++++++-------- striker-ui/types/IconButton.d.ts | 6 +++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/striker-ui/components/IconButton/IconButton.tsx b/striker-ui/components/IconButton/IconButton.tsx index 11ef960b..1e289aad 100644 --- a/striker-ui/components/IconButton/IconButton.tsx +++ b/striker-ui/components/IconButton/IconButton.tsx @@ -1,4 +1,6 @@ import { + Add as MUIAddIcon, + Close as MUICloseIcon, Done as MUIDoneIcon, Edit as MUIEditIcon, Visibility as MUIVisibilityIcon, @@ -40,9 +42,12 @@ const NormalIconButton = styled(MUIIconButton)({ color: GREY, }); -const MAP_TO_VISIBILITY_ICON: IconButtonMapToStateIconBundle = { - false: { iconType: MUIVisibilityIcon }, - true: { iconType: MUIVisibilityOffIcon }, +const MAP_TO_ADD_ICON: IconButtonMapToStateIconBundle = { + none: { iconType: MUIAddIcon }, +}; + +const MAP_TO_CLOSE_ICON: IconButtonMapToStateIconBundle = { + none: { iconType: MUICloseIcon }, }; const MAP_TO_EDIT_ICON: IconButtonMapToStateIconBundle = { @@ -50,10 +55,17 @@ const MAP_TO_EDIT_ICON: IconButtonMapToStateIconBundle = { true: { iconType: MUIDoneIcon, iconProps: { sx: { color: BLUE } } }, }; +const MAP_TO_VISIBILITY_ICON: IconButtonMapToStateIconBundle = { + false: { iconType: MUIVisibilityIcon }, + true: { iconType: MUIVisibilityOffIcon }, +}; + const MAP_TO_MAP_PRESET: Record< IconButtonPresetMapToStateIconBundle, IconButtonMapToStateIconBundle > = { + add: MAP_TO_ADD_ICON, + close: MAP_TO_CLOSE_ICON, edit: MAP_TO_EDIT_ICON, visibility: MAP_TO_VISIBILITY_ICON, }; @@ -69,7 +81,7 @@ const IconButton: FC = ({ iconProps, mapPreset, mapToIcon: externalMapToIcon, - state, + state = 'none', variant = 'contained', ...restIconButtonProps }) => { @@ -78,18 +90,13 @@ const IconButton: FC = ({ [externalMapToIcon, mapPreset], ); - const defaultIconBundle = useMemo>( - () => ({ iconType: defaultIcon }), - [defaultIcon], - ); - const iconButtonContent = useMemo(() => { let result: ReactNode; if (mapToIcon) { - const { iconType, iconProps: presetIconProps } = state - ? mapToIcon[state] ?? defaultIconBundle - : defaultIconBundle; + const { iconType, iconProps: presetIconProps } = mapToIcon[state] ?? { + iconType: defaultIcon, + }; if (iconType) { result = createElement(iconType, { @@ -102,7 +109,7 @@ const IconButton: FC = ({ } return result; - }, [mapToIcon, state, defaultIconBundle, iconProps, children]); + }, [mapToIcon, state, defaultIcon, iconProps, children]); const iconButtonElementType = useMemo( () => MAP_TO_VARIANT[variant], [variant], diff --git a/striker-ui/types/IconButton.d.ts b/striker-ui/types/IconButton.d.ts index 7009aa4f..9907155f 100644 --- a/striker-ui/types/IconButton.d.ts +++ b/striker-ui/types/IconButton.d.ts @@ -1,6 +1,10 @@ type CreatableComponent = Parameters[0]; -type IconButtonPresetMapToStateIconBundle = 'edit' | 'visibility'; +type IconButtonPresetMapToStateIconBundle = + | 'add' + | 'close' + | 'edit' + | 'visibility'; type IconButtonStateIconBundle = { iconType: CreatableComponent; From 768ef98f5110df1258461ecd2737df0d50691e61 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 02:16:57 -0400 Subject: [PATCH 11/86] fix(striker-ui): add input groups to build add/edit manifest forms --- .../AddAnvilManifestInputGroup.tsx | 34 +++ .../ManageManifest/AnvilHostInputGroup.tsx | 172 +++++++++++++++ .../ManageManifest/AnvilIdInputGroup.tsx | 152 +++++++++++++ .../AnvilNetworkConfigInputGroup.tsx | 203 ++++++++++++++++++ .../ManageManifest/AnvilNetworkInputGroup.tsx | 174 +++++++++++++++ .../ManageManifest/ManageManifestPanel.tsx | 88 +++++++- striker-ui/types/ManageManifest.d.ts | 94 ++++++++ 7 files changed, 908 insertions(+), 9 deletions(-) create mode 100644 striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx create mode 100644 striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx create mode 100644 striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx create mode 100644 striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx create mode 100644 striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx create mode 100644 striker-ui/types/ManageManifest.d.ts diff --git a/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx new file mode 100644 index 00000000..af863052 --- /dev/null +++ b/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx @@ -0,0 +1,34 @@ +import { ReactElement } from 'react'; + +import AnvilIdInputGroup, { + INPUT_ID_ANVIL_ID_DOMAIN, + INPUT_ID_ANVIL_ID_PREFIX, + INPUT_ID_ANVIL_ID_SEQUENCE, +} from './AnvilIdInputGroup'; +import AnvilNetworkConfigInputGroup, { + INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, +} from './AnvilNetworkConfigInputGroup'; +import FlexBox from '../FlexBox'; + +const AddAnvilManifestInputGroup = < + M extends { + [K in + | typeof INPUT_ID_ANVIL_ID_DOMAIN + | typeof INPUT_ID_ANVIL_ID_PREFIX + | typeof INPUT_ID_ANVIL_ID_SEQUENCE + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + }, +>({ + formUtils, +}: AddAnvilInputGroupProps): ReactElement => ( + + + + +); + +export default AddAnvilManifestInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx new file mode 100644 index 00000000..ddefa929 --- /dev/null +++ b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx @@ -0,0 +1,172 @@ +import { ReactElement, useMemo } from 'react'; + +import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; + +import Grid from '../Grid'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { InnerPanel, InnerPanelBody, InnerPanelHeader } from '../Panels'; +import SwitchWithLabel from '../SwitchWithLabel'; +import { + buildIPAddressTestBatch, + buildNumberTestBatch, +} from '../../lib/test_input'; +import { BodyText } from '../Text'; + +const INPUT_ID_PREFIX_ANVIL_HOST_CONFIG = 'anvil-host-config-input'; + +const INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-cell`; + +const AnvilHostInputGroup = ({ + formUtils: { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + setMsgSetter, + }, + hostLabel, + previous: { fences = {}, networks = {}, upses = {} } = {}, +}: AnvilHostInputGroupProps): ReactElement => { + const gridLayout = useMemo(() => { + let result: GridLayout = {}; + + result = Object.entries(networks).reduce( + (previous, [networkId, { networkIp, networkNumber, networkType }]) => { + const idPostfix = `${networkId}-ip`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; + + setMsgSetter(inputId); + + previous[cellId] = { + children: ( + + } + inputTestBatch={buildIPAddressTestBatch( + inputLabel, + () => { + msgSetters[inputId](); + }, + { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, + (message) => { + msgSetters[inputId]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(inputId)} + required + /> + ), + }; + + return previous; + }, + result, + ); + + result = Object.entries(fences).reduce( + (previous, [fenceId, { fenceName, fencePort }]) => { + const idPostfix = `${fenceId}-port`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + const inputLabel = fenceName; + + setMsgSetter(inputId); + + previous[cellId] = { + children: ( + + } + inputTestBatch={buildNumberTestBatch( + inputLabel, + () => { + msgSetters[inputId](); + }, + { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, + (message) => { + msgSetters[inputId]({ + children: message, + }); + }, + )} + required + valueType="number" + /> + ), + }; + + return previous; + }, + result, + ); + + result = Object.entries(upses).reduce( + (previous, [upsId, { isPowerHost, upsName }]) => { + const idPostfix = `${upsId}-power-host`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; + + previous[cellId] = { + children: ( + + } + valueType="boolean" + /> + ), + }; + + return previous; + }, + result, + ); + + return result; + }, [ + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + setMsgSetter, + fences, + msgSetters, + networks, + upses, + ]); + + return ( + + + {hostLabel} + + + + + + ); +}; + +export default AnvilHostInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx new file mode 100644 index 00000000..e7e4b5f0 --- /dev/null +++ b/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx @@ -0,0 +1,152 @@ +import { ReactElement } from 'react'; + +import Grid from '../Grid'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { + buildNumberTestBatch, + buildPeacefulStringTestBatch, +} from '../../lib/test_input'; + +const INPUT_ID_ANVIL_ID_DOMAIN = 'anvil-id-input-domain'; +const INPUT_ID_ANVIL_ID_PREFIX = 'anvil-id-input-prefix'; +const INPUT_ID_ANVIL_ID_SEQUENCE = 'anvil-id-input-sequence'; + +const INPUT_LABEL_ANVIL_ID_DOMAIN = 'Domain name'; +const INPUT_LABEL_ANVIL_ID_PREFIX = 'Anvil! prefix'; +const INPUT_LABEL_ANVIL_ID_SEQUENCE = 'Anvil! sequence'; + +const AnvilIdInputGroup = < + M extends { + [K in + | typeof INPUT_ID_ANVIL_ID_DOMAIN + | typeof INPUT_ID_ANVIL_ID_PREFIX + | typeof INPUT_ID_ANVIL_ID_SEQUENCE]: string; + }, +>({ + formUtils: { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + }, + previous: { + anvilIdDomain: previousDomain, + anvilIdPrefix: previousPrefix, + anvilIdSequence: previousSequence, + } = {}, +}: AnvilIdInputGroupProps): ReactElement => ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_ANVIL_ID_PREFIX, + () => { + msgSetters[INPUT_ID_ANVIL_ID_PREFIX](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_ANVIL_ID_PREFIX, + ), + }, + (message) => { + msgSetters[INPUT_ID_ANVIL_ID_PREFIX]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_ANVIL_ID_PREFIX, + )} + required + /> + ), + }, + 'anvil-id-input-cell-domain': { + children: ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_ANVIL_ID_DOMAIN, + () => { + msgSetters[INPUT_ID_ANVIL_ID_DOMAIN](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_ANVIL_ID_DOMAIN, + ), + }, + (message) => { + msgSetters[INPUT_ID_ANVIL_ID_DOMAIN]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_ANVIL_ID_DOMAIN, + )} + required + /> + ), + }, + 'anvil-id-input-cell-sequence': { + children: ( + + } + inputTestBatch={buildNumberTestBatch( + INPUT_LABEL_ANVIL_ID_SEQUENCE, + () => { + msgSetters[INPUT_ID_ANVIL_ID_SEQUENCE](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_ANVIL_ID_SEQUENCE, + ), + }, + (message) => { + msgSetters[INPUT_ID_ANVIL_ID_SEQUENCE]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_ANVIL_ID_SEQUENCE, + )} + required + valueType="number" + /> + ), + }, + }} + spacing="1em" + /> +); + +export { + INPUT_ID_ANVIL_ID_DOMAIN, + INPUT_ID_ANVIL_ID_PREFIX, + INPUT_ID_ANVIL_ID_SEQUENCE, +}; + +export default AnvilIdInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx new file mode 100644 index 00000000..673cc298 --- /dev/null +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -0,0 +1,203 @@ +import { ReactElement, useMemo } from 'react'; + +import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; + +import AnvilNetworkInputGroup from './AnvilNetworkInputGroup'; +import Grid from '../Grid'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { buildNumberTestBatch } from '../../lib/test_input'; + +const INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG = 'anvil-network-config-input'; + +const INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-cell`; + +const INPUT_ID_ANVIL_NETWORK_CONFIG_DNS = 'anvil-network-config-input-dns'; +const INPUT_ID_ANVIL_NETWORK_CONFIG_MTU = 'anvil-network-config-input-mtu'; +const INPUT_ID_ANVIL_NETWORK_CONFIG_NTP = 'anvil-network-config-input-ntp'; + +const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; +const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; +const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; + +const DEFAULT_NETWORKS: { [networkId: string]: AnvilNetworkConfigNetwork } = { + bcn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'bcn', + }, + sn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'sn', + }, + ifn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'ifn', + }, +}; + +const AnvilNetworkConfigInputGroup = < + M extends MapToInputTestID & { + [K in + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + }, +>({ + formUtils, + previous: { + dnsCsv: previousDnsCsv, + mtu: previousMtu, + networks = DEFAULT_NETWORKS, + ntpCsv: previousNtpCsv, + } = {}, +}: AnvilNetworkConfigInputGroupProps): ReactElement => { + const { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + } = formUtils; + + const networksGridLayout = useMemo(() => { + let result: GridLayout = {}; + + result = Object.entries(networks).reduce( + ( + previous, + [ + networkId, + { + networkGateway: previousGateway, + networkMinIp: previousMinIp, + networkNumber, + networkSubnetMask: previousSubnetMask, + networkType, + }, + ], + ) => { + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; + + const idPrefix = `anvil-network-${networkId}`; + + const inputIdPrefix = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; + const inputGatewayId = `${inputIdPrefix}-gateway`; + const inputMinIpId = `${inputIdPrefix}-min-ip`; + const inputSubnetMaskId = `${inputIdPrefix}-subnet-mask`; + + const networkName = `${NETWORK_TYPES[networkType]} ${networkNumber}`; + + const isShowGateway = networkType === 'ifn'; + + previous[cellId] = { + children: ( + + ), + md: 3, + sm: 2, + }; + + return previous; + }, + result, + ); + + return result; + }, [formUtils, networks]); + + return ( + + } + required + /> + ), + }, + 'anvil-network-config-input-cell-ntp': { + children: ( + + } + /> + ), + }, + 'anvil-network-config-input-cell-mtu': { + children: ( + + } + inputTestBatch={buildNumberTestBatch( + INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU, + () => { + msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + ), + }, + (message) => { + msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + )} + valueType="number" + /> + ), + }, + }} + spacing="1em" + /> + ); +}; + +export { + INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, +}; + +export default AnvilNetworkConfigInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx new file mode 100644 index 00000000..d4c1983c --- /dev/null +++ b/striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx @@ -0,0 +1,174 @@ +import { ReactElement, ReactNode, useEffect, useMemo } from 'react'; + +import Grid from '../Grid'; +import IconButton from '../IconButton'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { InnerPanel, InnerPanelBody, InnerPanelHeader } from '../Panels'; +import { buildIPAddressTestBatch } from '../../lib/test_input'; +import { BodyText } from '../Text'; + +const AnvilNetworkInputGroup = ({ + formUtils: { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + setMsgSetter, + }, + idPrefix, + inputGatewayId, + inputGatewayLabel = 'Gateway', + inputMinIpId, + inputMinIpLabel = 'IP address', + inputSubnetMaskId, + inputSubnetMaskLabel = 'Subnet mask', + networkName, + previous: { + gateway: previousGateway, + minIp: previousIpAddress, + subnetMask: previousSubnetMask, + } = {}, + showGateway: isShowGateway, +}: AnvilNetworkInputGroupProps): ReactElement => { + const inputCellGatewayId = useMemo( + () => `${idPrefix}-input-cell-gateway`, + [idPrefix], + ); + const inputCellIpId = useMemo(() => `${idPrefix}-input-cell-ip`, [idPrefix]); + const inputCellSubnetMaskId = useMemo( + () => `${idPrefix}-input-cell-subnet-mask`, + [idPrefix], + ); + + const inputCellGatewayDisplay = useMemo( + () => (isShowGateway ? undefined : 'none'), + [isShowGateway], + ); + + const inputGatewayElement = useMemo(() => { + let result: ReactNode; + + if (isShowGateway && inputGatewayId) { + setMsgSetter(inputGatewayId); + + result = ( + + } + inputTestBatch={buildIPAddressTestBatch( + `${networkName} ${inputGatewayLabel}`, + () => { + msgSetters[inputGatewayId](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction(inputGatewayId), + }, + (message) => { + msgSetters[inputGatewayId]({ + children: message, + }); + }, + )} + required={isShowGateway} + /> + ); + } + + return result; + }, [ + isShowGateway, + inputGatewayId, + setMsgSetter, + inputGatewayLabel, + previousGateway, + networkName, + buildFinishInputTestBatchFunction, + msgSetters, + ]); + + useEffect(() => { + setMsgSetter(inputMinIpId); + setMsgSetter(inputSubnetMaskId); + }, [inputMinIpId, inputSubnetMaskId, setMsgSetter]); + + return ( + + + {networkName} + + + + + + } + inputTestBatch={buildIPAddressTestBatch( + `${networkName} ${inputMinIpLabel}`, + () => { + msgSetters[inputMinIpId](); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(inputMinIpId), + }, + (message) => { + msgSetters[inputMinIpId]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(inputMinIpId)} + required + /> + ), + }, + [inputCellSubnetMaskId]: { + children: ( + + } + required + /> + ), + }, + [inputCellGatewayId]: { + children: inputGatewayElement, + display: inputCellGatewayDisplay, + }, + }} + spacing="1em" + /> + + + ); +}; + +export default AnvilNetworkInputGroup; diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index f5256dfe..ae00f4a2 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -1,15 +1,40 @@ import { PlayCircle } from '@mui/icons-material'; -import { FC, useMemo, useState } from 'react'; +import { FC, useMemo, useRef, useState } from 'react'; + import API_BASE_URL from '../../lib/consts/API_BASE_URL'; -import periodicFetch from '../../lib/fetchers/periodicFetch'; + +import AddAnvilManifestInputGroup from './AddAnvilManifestInputGroup'; +import { + INPUT_ID_ANVIL_ID_DOMAIN, + INPUT_ID_ANVIL_ID_PREFIX, + INPUT_ID_ANVIL_ID_SEQUENCE, +} from './AnvilIdInputGroup'; +import { + INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, +} from './AnvilNetworkConfigInputGroup'; +import ConfirmDialog from '../ConfirmDialog'; import FlexBox from '../FlexBox'; +import FormDialog from '../FormDialog'; import IconButton from '../IconButton'; import List from '../List'; +import { MessageGroupForwardedRefContent } from '../MessageGroup'; import { Panel, PanelHeader } from '../Panels'; +import periodicFetch from '../../lib/fetchers/periodicFetch'; import Spinner from '../Spinner'; import { BodyText, HeaderText } from '../Text'; +import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; +import useFormUtils from '../../hooks/useFormUtils'; const ManageManifestPanel: FC = () => { + const confirmDialogRef = useRef({}); + const formDialogRef = useRef({}); + const messageGroupRef = useRef({}); + + const [confirmDialogProps] = useConfirmDialogProps(); + const [formDialogProps, setFormDialogProps] = useConfirmDialogProps(); + const [isEditManifests, setIsEditManifests] = useState(false); const { data: manifestOverviews, isLoading: isLoadingManifestOverviews } = @@ -17,6 +42,28 @@ const ManageManifestPanel: FC = () => { refreshInterval: 60000, }); + const formUtils = useFormUtils( + [ + INPUT_ID_ANVIL_ID_DOMAIN, + INPUT_ID_ANVIL_ID_PREFIX, + INPUT_ID_ANVIL_ID_SEQUENCE, + INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, + ], + messageGroupRef, + ); + const { isFormInvalid } = formUtils; + + const addAnvilManifestFormDialogProps = useMemo( + () => ({ + actionProceedText: 'Add', + content: , + titleText: 'Add a Anvil! manifest', + }), + [formUtils], + ); + const listElement = useMemo( () => ( { header listEmpty="No manifest(s) registered." listItems={manifestOverviews} + onAdd={() => { + setFormDialogProps(addAnvilManifestFormDialogProps); + formDialogRef.current.setOpen?.call(null, true); + }} onEdit={() => { setIsEditManifests((previous) => !previous); }} @@ -39,7 +90,12 @@ const ManageManifestPanel: FC = () => { )} /> ), - [isEditManifests, manifestOverviews], + [ + addAnvilManifestFormDialogProps, + isEditManifests, + manifestOverviews, + setFormDialogProps, + ], ); const panelContent = useMemo( @@ -48,12 +104,26 @@ const ManageManifestPanel: FC = () => { ); return ( - - - Manage manifests - - {panelContent} - + <> + + + Manage manifests + + {panelContent} + + + + ); }; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts new file mode 100644 index 00000000..63286503 --- /dev/null +++ b/striker-ui/types/ManageManifest.d.ts @@ -0,0 +1,94 @@ +type AnvilIdInputGroupOptionalProps = { + previous?: { + anvilIdPrefix?: string; + anvilIdDomain?: string; + anvilIdSequence?: number; + }; +}; + +type AnvilIdInputGroupProps = + AnvilIdInputGroupOptionalProps & { + formUtils: FormUtils; + }; + +type AnvilNetworkInputGroupOptionalProps = { + inputGatewayId?: string; + inputGatewayLabel?: string; + inputMinIpLabel?: string; + inputSubnetMaskLabel?: string; + previous?: { + gateway?: string; + minIp?: string; + subnetMask?: string; + }; + showGateway?: boolean; +}; + +type AnvilNetworkInputGroupProps = + AnvilNetworkInputGroupOptionalProps & { + formUtils: FormUtils; + idPrefix: string; + inputMinIpId: string; + inputSubnetMaskId: string; + networkName: string; + }; + +type AnvilHostInputGroupOptionalProps = { + previous?: { + fences?: { + [fenceId: string]: { + fenceName: string; + fencePort: number; + }; + }; + networks?: { + [networkId: string]: { + networkIp: string; + networkNumber: number; + networkType: string; + }; + }; + upses?: { + [upsId: string]: { + isPowerHost: boolean; + upsName: string; + }; + }; + }; +}; + +type AnvilHostInputGroupProps = + AnvilHostInputGroupOptionalProps & { + formUtils: FormUtils; + hostLabel: string; + idPrefix: string; + }; + +type AnvilNetworkConfigNetwork = { + networkGateway?: string; + networkMinIp: string; + networkNumber: number; + networkSubnetMask: string; + networkType: string; +}; + +type AnvilNetworkConfigInputGroupOptionalProps = { + previous?: { + dnsCsv?: string; + /** Max Transmission Unit (MTU); unit: bytes */ + mtu?: number; + networks?: { + [networkId: string]: AnvilNetworkConfigNetwork; + }; + ntpCsv?: string; + }; +}; + +type AnvilNetworkConfigInputGroupProps = + AnvilNetworkConfigInputGroupOptionalProps & { + formUtils: FormUtils; + }; + +type AddAnvilInputGroupProps = { + formUtils: FormUtils; +}; From 8154fd4dbd148770cc12b135396148e3a4b13b21 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 19:21:44 -0400 Subject: [PATCH 12/86] fix(striker-ui): allow remove key in object setter builder --- striker-ui/lib/buildObjectStateSetterCallback.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/striker-ui/lib/buildObjectStateSetterCallback.ts b/striker-ui/lib/buildObjectStateSetterCallback.ts index 15170e85..98cecfcf 100644 --- a/striker-ui/lib/buildObjectStateSetterCallback.ts +++ b/striker-ui/lib/buildObjectStateSetterCallback.ts @@ -1,9 +1,13 @@ const buildObjectStateSetterCallback = - >(key: keyof S, value: S[keyof S]) => - ({ [key]: toReplace, ...restPrevious }: S): S => - ({ - ...restPrevious, - [key]: value, - } as S); + >(key: keyof S, value?: S[keyof S]) => + ({ [key]: toReplace, ...restPrevious }: S): S => { + const result = { ...restPrevious } as S; + + if (value !== undefined) { + result[key] = value; + } + + return result; + }; export default buildObjectStateSetterCallback; From 45a0d1a6c86bc05d50ae07bf36c092eec8832dd0 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 22:42:11 -0400 Subject: [PATCH 13/86] fix(striker-ui): add icon button mouse event handler type --- striker-ui/types/IconButton.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/striker-ui/types/IconButton.d.ts b/striker-ui/types/IconButton.d.ts index 9907155f..93c46f53 100644 --- a/striker-ui/types/IconButton.d.ts +++ b/striker-ui/types/IconButton.d.ts @@ -15,6 +15,9 @@ type IconButtonMapToStateIconBundle = Record; type IconButtonVariant = 'contained' | 'normal'; +type IconButtonMouseEventHandler = + import('@mui/material').IconButtonProps['onClick']; + type IconButtonOptionalProps = { defaultIcon?: CreatableComponent; iconProps?: import('@mui/material').SvgIconProps; From b269770d3a186fdece2385c06eb4b554698018c8 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 23:03:55 -0400 Subject: [PATCH 14/86] fix(striker-ui): enable add/remove networks --- .../AnvilNetworkConfigInputGroup.tsx | 152 ++++++++++++++++-- .../ManageManifest/AnvilNetworkInputGroup.tsx | 47 ++++-- striker-ui/types/ManageManifest.d.ts | 35 ++-- 3 files changed, 193 insertions(+), 41 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 673cc298..9ea7fb8c 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -1,9 +1,10 @@ -import { ReactElement, useMemo } from 'react'; - -import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; +import { ReactElement, useCallback, useMemo, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; import AnvilNetworkInputGroup from './AnvilNetworkInputGroup'; +import buildObjectStateSetterCallback from '../../lib/buildObjectStateSetterCallback'; import Grid from '../Grid'; +import IconButton from '../IconButton'; import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import { buildNumberTestBatch } from '../../lib/test_input'; @@ -20,7 +21,7 @@ const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; -const DEFAULT_NETWORKS: { [networkId: string]: AnvilNetworkConfigNetwork } = { +const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { bcn1: { networkMinIp: '', networkNumber: 1, @@ -41,6 +42,8 @@ const DEFAULT_NETWORKS: { [networkId: string]: AnvilNetworkConfigNetwork } = { }, }; +const isIfn = (type: string) => type === 'ifn'; + const AnvilNetworkConfigInputGroup = < M extends MapToInputTestID & { [K in @@ -53,7 +56,7 @@ const AnvilNetworkConfigInputGroup = < previous: { dnsCsv: previousDnsCsv, mtu: previousMtu, - networks = DEFAULT_NETWORKS, + networks: previousNetworks = DEFAULT_NETWORKS, ntpCsv: previousNtpCsv, } = {}, }: AnvilNetworkConfigInputGroupProps): ReactElement => { @@ -63,19 +66,119 @@ const AnvilNetworkConfigInputGroup = < msgSetters, } = formUtils; + const [networkList, setNetworkList] = + useState(previousNetworks); + + const networkListArray = useMemo( + () => Object.entries(networkList), + [networkList], + ); + + const getNetworkNumber = useCallback( + ( + type: string, + { + input = networkListArray, + end = networkListArray.length, + }: { + input?: Array<[string, AnvilNetworkConfigNetwork]>; + end?: number; + } = {}, + ) => { + let netNum = 0; + + input.every(([, { networkType }], networkIndex) => { + if (networkType === type) { + netNum += 1; + } + + return networkIndex < end; + }); + + return netNum; + }, + [networkListArray], + ); + + const buildNetwork = useCallback( + ({ + networkMinIp = '', + networkSubnetMask = '', + networkType = 'ifn', + // Params that depend on others. + networkGateway = isIfn(networkType) ? '' : undefined, + networkNumber = getNetworkNumber(networkType) + 1, + }: Partial = {}): { + network: AnvilNetworkConfigNetwork; + networkId: string; + } => ({ + network: { + networkGateway, + networkMinIp, + networkNumber, + networkSubnetMask, + networkType, + }, + networkId: uuidv4(), + }), + [getNetworkNumber], + ); + + const setNetwork = useCallback( + (key: string, value?: AnvilNetworkConfigNetwork) => + setNetworkList(buildObjectStateSetterCallback(key, value)), + [], + ); + + const removeNetwork = useCallback( + ({ networkId: rmId, networkType: rmType }) => { + let isIdMatch = false; + let networkNumber = 0; + + const newList = networkListArray.reduce( + (previous, [networkId, networkValue]) => { + const { networkType } = networkValue; + + if (networkId === rmId) { + isIdMatch = true; + } else { + if (networkType === rmType) { + networkNumber += 1; + } + + if (isIdMatch) { + previous[networkId] = { + ...networkValue, + networkNumber, + }; + } else { + previous[networkId] = networkValue; + } + } + + return previous; + }, + {}, + ); + + setNetworkList(newList); + }, + [networkListArray], + ); + const networksGridLayout = useMemo(() => { let result: GridLayout = {}; - result = Object.entries(networks).reduce( + result = networkListArray.reduce( ( previous, [ networkId, { - networkGateway: previousGateway, - networkMinIp: previousMinIp, + networkGateway, + networkMinIp, networkNumber, - networkSubnetMask: previousSubnetMask, + networkSubnetMask, networkType, }, ], @@ -89,9 +192,8 @@ const AnvilNetworkConfigInputGroup = < const inputMinIpId = `${inputIdPrefix}-min-ip`; const inputSubnetMaskId = `${inputIdPrefix}-subnet-mask`; - const networkName = `${NETWORK_TYPES[networkType]} ${networkNumber}`; - - const isShowGateway = networkType === 'ifn'; + const isFirstNetwork = networkNumber === 1; + const isShowGateway = isIfn(networkType); previous[cellId] = { children: ( @@ -101,12 +203,16 @@ const AnvilNetworkConfigInputGroup = < inputGatewayId={inputGatewayId} inputMinIpId={inputMinIpId} inputSubnetMaskId={inputSubnetMaskId} - networkName={networkName} + networkId={networkId} + networkNumber={networkNumber} + networkType={networkType} + onClose={removeNetwork} previous={{ - gateway: previousGateway, - minIp: previousMinIp, - subnetMask: previousSubnetMask, + gateway: networkGateway, + minIp: networkMinIp, + subnetMask: networkSubnetMask, }} + showCloseButton={!isFirstNetwork} showGateway={isShowGateway} /> ), @@ -120,13 +226,25 @@ const AnvilNetworkConfigInputGroup = < ); return result; - }, [formUtils, networks]); + }, [formUtils, networkListArray, removeNetwork]); return ( { + const { network: newNet, networkId: newNetId } = buildNetwork(); + + setNetwork(newNetId, newNet); + }} + /> + ), + }, 'anvil-network-config-input-cell-dns': { children: ( ({ inputMinIpLabel = 'IP address', inputSubnetMaskId, inputSubnetMaskLabel = 'Subnet mask', - networkName, + networkId, + networkNumber, + networkType, + onClose, previous: { gateway: previousGateway, minIp: previousIpAddress, subnetMask: previousSubnetMask, } = {}, + showCloseButton: isShowCloseButton, showGateway: isShowGateway, }: AnvilNetworkInputGroupProps): ReactElement => { + const networkName = useMemo( + () => `${NETWORK_TYPES[networkType]} ${networkNumber}`, + [networkNumber, networkType], + ); + const inputCellGatewayId = useMemo( () => `${idPrefix}-input-cell-gateway`, [idPrefix], @@ -45,6 +56,26 @@ const AnvilNetworkInputGroup = ({ [isShowGateway], ); + const closeButtonElement = useMemo( + () => + isShowCloseButton && ( + { + onClose?.call(null, { networkId, networkType }, ...args); + }} + sx={{ + padding: '.2em', + position: 'absolute', + right: '-.6rem', + top: '-.2rem', + }} + /> + ), + [isShowCloseButton, networkId, networkType, onClose], + ); + const inputGatewayElement = useMemo(() => { let result: ReactNode; @@ -74,6 +105,7 @@ const AnvilNetworkInputGroup = ({ }); }, )} + onFirstRender={buildInputFirstRenderFunction(inputGatewayId)} required={isShowGateway} /> ); @@ -88,6 +120,7 @@ const AnvilNetworkInputGroup = ({ previousGateway, networkName, buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, msgSetters, ]); @@ -100,18 +133,8 @@ const AnvilNetworkInputGroup = ({ {networkName} - + {closeButtonElement} - = formUtils: FormUtils; }; +type AnvilNetworkConfigNetwork = { + networkGateway?: string; + networkMinIp: string; + networkNumber: number; + networkSubnetMask: string; + networkType: string; +}; + +type AnvilNetworkConfigNetworkList = { + [networkId: string]: AnvilNetworkConfigNetwork; +}; + +type AnvilNetworkCloseHandler = ( + args: { networkId: string } & Pick, + ...handlerArgs: Parameters +) => ReturnType; + type AnvilNetworkInputGroupOptionalProps = { inputGatewayId?: string; inputGatewayLabel?: string; inputMinIpLabel?: string; inputSubnetMaskLabel?: string; + onClose?: AnvilNetworkCloseHandler; previous?: { gateway?: string; minIp?: string; subnetMask?: string; }; + showCloseButton?: boolean; showGateway?: boolean; }; @@ -30,7 +49,9 @@ type AnvilNetworkInputGroupProps = idPrefix: string; inputMinIpId: string; inputSubnetMaskId: string; - networkName: string; + networkId: string; + networkNumber: number; + networkType: string; }; type AnvilHostInputGroupOptionalProps = { @@ -64,22 +85,12 @@ type AnvilHostInputGroupProps = idPrefix: string; }; -type AnvilNetworkConfigNetwork = { - networkGateway?: string; - networkMinIp: string; - networkNumber: number; - networkSubnetMask: string; - networkType: string; -}; - type AnvilNetworkConfigInputGroupOptionalProps = { previous?: { dnsCsv?: string; /** Max Transmission Unit (MTU); unit: bytes */ mtu?: number; - networks?: { - [networkId: string]: AnvilNetworkConfigNetwork; - }; + networks?: AnvilNetworkConfigNetworkList; ntpCsv?: string; }; }; From 81139bb1adff7023871418d82156d78f2af02f3b Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 23:23:09 -0400 Subject: [PATCH 15/86] fix(striker-ui): center add network button --- .../ManageManifest/AnvilNetworkConfigInputGroup.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 9ea7fb8c..a2a93f06 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -244,6 +244,10 @@ const AnvilNetworkConfigInputGroup = < }} /> ), + display: 'flex', + justifyContent: 'center', + md: 3, + sm: 2, }, 'anvil-network-config-input-cell-dns': { children: ( From adf01f9bddb75e507d4182c481e8ee2aa52ed2bc Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 16 Mar 2023 16:41:45 -0400 Subject: [PATCH 16/86] fix(striker-ui): complete edit/remove network(s) in manifest network config --- .../AnvilNetworkConfigInputGroup.tsx | 131 ++++++++++++++---- .../ManageManifest/AnvilNetworkInputGroup.tsx | 27 +++- striker-ui/types/ManageManifest.d.ts | 9 ++ striker-ui/types/Select.d.ts | 5 + 4 files changed, 144 insertions(+), 28 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index a2a93f06..896a26de 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -1,6 +1,8 @@ import { ReactElement, useCallback, useMemo, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; +import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; + import AnvilNetworkInputGroup from './AnvilNetworkInputGroup'; import buildObjectStateSetterCallback from '../../lib/buildObjectStateSetterCallback'; import Grid from '../Grid'; @@ -21,6 +23,8 @@ const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; +const NETWORK_TYPE_ENTRIES = Object.entries(NETWORK_TYPES); + const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { bcn1: { networkMinIp: '', @@ -42,7 +46,8 @@ const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { }, }; -const isIfn = (type: string) => type === 'ifn'; +const assertIfn = (type: string) => type === 'ifn'; +const assertMn = (type: string) => type === 'mn'; const AnvilNetworkConfigInputGroup = < M extends MapToInputTestID & { @@ -69,7 +74,7 @@ const AnvilNetworkConfigInputGroup = < const [networkList, setNetworkList] = useState(previousNetworks); - const networkListArray = useMemo( + const networkListEntries = useMemo( () => Object.entries(networkList), [networkList], ); @@ -78,13 +83,15 @@ const AnvilNetworkConfigInputGroup = < ( type: string, { - input = networkListArray, - end = networkListArray.length, + input = networkListEntries, + end = networkListEntries.length, }: { input?: Array<[string, AnvilNetworkConfigNetwork]>; end?: number; } = {}, ) => { + const limit = end - 1; + let netNum = 0; input.every(([, { networkType }], networkIndex) => { @@ -92,12 +99,21 @@ const AnvilNetworkConfigInputGroup = < netNum += 1; } - return networkIndex < end; + return networkIndex < limit; }); return netNum; }, - [networkListArray], + [networkListEntries], + ); + + const networkTypeOptions = useMemo( + () => + NETWORK_TYPE_ENTRIES.map(([key, value]) => ({ + displayValue: value, + value: key, + })), + [], ); const buildNetwork = useCallback( @@ -106,7 +122,7 @@ const AnvilNetworkConfigInputGroup = < networkSubnetMask = '', networkType = 'ifn', // Params that depend on others. - networkGateway = isIfn(networkType) ? '' : undefined, + networkGateway = assertIfn(networkType) ? '' : undefined, networkNumber = getNetworkNumber(networkType) + 1, }: Partial = {}): { network: AnvilNetworkConfigNetwork; @@ -130,30 +146,80 @@ const AnvilNetworkConfigInputGroup = < [], ); - const removeNetwork = useCallback( + const handleNetworkTypeChange = useCallback( + ( + { networkId: targetId, networkType: previousType }, + { target: { value } }, + ) => { + const newType = String(value); + + let isIdMatch = false; + let newTypeNumber = 0; + + const newList = networkListEntries.reduce( + (previous, [networkId, networkValue]) => { + const { networkNumber: initnn, networkType: initnt } = networkValue; + + let networkNumber = initnn; + let networkType = initnt; + + if (networkId === targetId) { + isIdMatch = true; + + networkType = newType; + } + + const isTypeMatch = networkType === newType; + + if (isTypeMatch) { + newTypeNumber += 1; + } + + if (isIdMatch) { + if (isTypeMatch) { + networkNumber = newTypeNumber; + } else if (networkType === previousType) { + networkNumber -= 1; + } + + previous[networkId] = { + ...networkValue, + networkNumber, + networkType, + }; + } else { + previous[networkId] = networkValue; + } + + return previous; + }, + {}, + ); + + setNetworkList(newList); + }, + [networkListEntries], + ); + + const handleNetworkRemove = useCallback( ({ networkId: rmId, networkType: rmType }) => { let isIdMatch = false; let networkNumber = 0; - const newList = networkListArray.reduce( + const newList = networkListEntries.reduce( (previous, [networkId, networkValue]) => { - const { networkType } = networkValue; - if (networkId === rmId) { isIdMatch = true; } else { + const { networkType } = networkValue; + if (networkType === rmType) { networkNumber += 1; } - if (isIdMatch) { - previous[networkId] = { - ...networkValue, - networkNumber, - }; - } else { - previous[networkId] = networkValue; - } + previous[networkId] = isIdMatch + ? { ...networkValue, networkNumber } + : networkValue; } return previous; @@ -163,13 +229,13 @@ const AnvilNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListArray], + [networkListEntries], ); const networksGridLayout = useMemo(() => { let result: GridLayout = {}; - result = networkListArray.reduce( + result = networkListEntries.reduce( ( previous, [ @@ -190,10 +256,13 @@ const AnvilNetworkConfigInputGroup = < const inputIdPrefix = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; const inputGatewayId = `${inputIdPrefix}-gateway`; const inputMinIpId = `${inputIdPrefix}-min-ip`; + const inputNetworkTypeId = `${inputIdPrefix}-network-type`; const inputSubnetMaskId = `${inputIdPrefix}-subnet-mask`; const isFirstNetwork = networkNumber === 1; - const isShowGateway = isIfn(networkType); + const isIfn = assertIfn(networkType); + const isMn = assertMn(networkType); + const isOptional = isMn || !isFirstNetwork; previous[cellId] = { children: ( @@ -202,18 +271,22 @@ const AnvilNetworkConfigInputGroup = < idPrefix={idPrefix} inputGatewayId={inputGatewayId} inputMinIpId={inputMinIpId} + inputNetworkTypeId={inputNetworkTypeId} inputSubnetMaskId={inputSubnetMaskId} networkId={networkId} networkNumber={networkNumber} networkType={networkType} - onClose={removeNetwork} + networkTypeOptions={networkTypeOptions} + onClose={handleNetworkRemove} + onNetworkTypeChange={handleNetworkTypeChange} previous={{ gateway: networkGateway, minIp: networkMinIp, subnetMask: networkSubnetMask, }} - showCloseButton={!isFirstNetwork} - showGateway={isShowGateway} + readonlyNetworkName={!isOptional} + showCloseButton={isOptional} + showGateway={isIfn} /> ), md: 3, @@ -226,7 +299,13 @@ const AnvilNetworkConfigInputGroup = < ); return result; - }, [formUtils, networkListArray, removeNetwork]); + }, [ + formUtils, + networkListEntries, + networkTypeOptions, + handleNetworkRemove, + handleNetworkTypeChange, + ]); return ( ({ formUtils: { @@ -22,17 +22,21 @@ const AnvilNetworkInputGroup = ({ inputGatewayLabel = 'Gateway', inputMinIpId, inputMinIpLabel = 'IP address', + inputNetworkTypeId, inputSubnetMaskId, inputSubnetMaskLabel = 'Subnet mask', networkId, networkNumber, networkType, + networkTypeOptions, onClose, + onNetworkTypeChange, previous: { gateway: previousGateway, minIp: previousIpAddress, subnetMask: previousSubnetMask, } = {}, + readonlyNetworkName: isReadonlyNetworkName, showCloseButton: isShowCloseButton, showGateway: isShowGateway, }: AnvilNetworkInputGroupProps): ReactElement => { @@ -132,7 +136,26 @@ const AnvilNetworkInputGroup = ({ return ( - {networkName} + { + onNetworkTypeChange?.call( + null, + { networkId, networkType }, + ...args, + ); + }} + selectItems={networkTypeOptions} + selectProps={{ + renderValue: () => networkName, + }} + value={networkType} + /> + } + /> {closeButtonElement} diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 82c25b7a..3e08fd7a 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -28,17 +28,24 @@ type AnvilNetworkCloseHandler = ( ...handlerArgs: Parameters ) => ReturnType; +type AnvilNetworkTypeChangeHandler = ( + args: { networkId: string } & Pick, + ...handlerArgs: Parameters +) => ReturnType; + type AnvilNetworkInputGroupOptionalProps = { inputGatewayId?: string; inputGatewayLabel?: string; inputMinIpLabel?: string; inputSubnetMaskLabel?: string; onClose?: AnvilNetworkCloseHandler; + onNetworkTypeChange?: AnvilNetworkTypeChangeHandler; previous?: { gateway?: string; minIp?: string; subnetMask?: string; }; + readonlyNetworkName?: boolean; showCloseButton?: boolean; showGateway?: boolean; }; @@ -48,10 +55,12 @@ type AnvilNetworkInputGroupProps = formUtils: FormUtils; idPrefix: string; inputMinIpId: string; + inputNetworkTypeId: string; inputSubnetMaskId: string; networkId: string; networkNumber: number; networkType: string; + networkTypeOptions: SelectItem[]; }; type AnvilHostInputGroupOptionalProps = { diff --git a/striker-ui/types/Select.d.ts b/striker-ui/types/Select.d.ts index 6b656304..fd678f4d 100644 --- a/striker-ui/types/Select.d.ts +++ b/striker-ui/types/Select.d.ts @@ -1,3 +1,8 @@ +type SelectChangeEventHandler = Exclude< + import('@mui/material').SelectProps['onChange'], + undefined +>; + type SelectOptionalProps = { onClearIndicatorClick?: import('@mui/material').IconButtonProps['onClick']; }; From 20aba7c559f7c6bd91c44d38740d2641657699fb Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 16 Mar 2023 18:10:02 -0400 Subject: [PATCH 17/86] fix(striker-ui): hoist network list in manifest network config --- .../AddAnvilManifestInputGroup.tsx | 34 --------- .../ManageManifest/AddManifestInputGroup.tsx | 69 +++++++++++++++++++ .../AnvilNetworkConfigInputGroup.tsx | 35 ++-------- .../ManageManifest/ManageManifestPanel.tsx | 4 +- striker-ui/types/ManageManifest.d.ts | 15 +++- 5 files changed, 90 insertions(+), 67 deletions(-) delete mode 100644 striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx create mode 100644 striker-ui/components/ManageManifest/AddManifestInputGroup.tsx diff --git a/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx deleted file mode 100644 index af863052..00000000 --- a/striker-ui/components/ManageManifest/AddAnvilManifestInputGroup.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { ReactElement } from 'react'; - -import AnvilIdInputGroup, { - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, -} from './AnvilIdInputGroup'; -import AnvilNetworkConfigInputGroup, { - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, -} from './AnvilNetworkConfigInputGroup'; -import FlexBox from '../FlexBox'; - -const AddAnvilManifestInputGroup = < - M extends { - [K in - | typeof INPUT_ID_ANVIL_ID_DOMAIN - | typeof INPUT_ID_ANVIL_ID_PREFIX - | typeof INPUT_ID_ANVIL_ID_SEQUENCE - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; - }, ->({ - formUtils, -}: AddAnvilInputGroupProps): ReactElement => ( - - - - -); - -export default AddAnvilManifestInputGroup; diff --git a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx new file mode 100644 index 00000000..c5220fad --- /dev/null +++ b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx @@ -0,0 +1,69 @@ +import { ReactElement, useState } from 'react'; + +import AnvilIdInputGroup, { + INPUT_ID_ANVIL_ID_DOMAIN, + INPUT_ID_ANVIL_ID_PREFIX, + INPUT_ID_ANVIL_ID_SEQUENCE, +} from './AnvilIdInputGroup'; +import AnvilNetworkConfigInputGroup, { + INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, + INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, + INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, +} from './AnvilNetworkConfigInputGroup'; +import FlexBox from '../FlexBox'; + +const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { + bcn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'bcn', + }, + sn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'sn', + }, + ifn1: { + networkMinIp: '', + networkNumber: 1, + networkSubnetMask: '', + networkType: 'ifn', + }, +}; + +const AddManifestInputGroup = < + M extends { + [K in + | typeof INPUT_ID_ANVIL_ID_DOMAIN + | typeof INPUT_ID_ANVIL_ID_PREFIX + | typeof INPUT_ID_ANVIL_ID_SEQUENCE + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU + | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + }, +>({ + formUtils, + previous: { networkConfig: previousNetworkConfig = {} } = {}, +}: AddManifestInputGroupProps): ReactElement => { + const { networks: previousNetworkList = DEFAULT_NETWORKS } = + previousNetworkConfig; + + const [networkList, setNetworkList] = + useState(previousNetworkList); + + return ( + + + + + ); +}; + +export default AddManifestInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 896a26de..8c4719f5 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useCallback, useMemo, useState } from 'react'; +import { ReactElement, useCallback, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; @@ -25,27 +25,6 @@ const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; const NETWORK_TYPE_ENTRIES = Object.entries(NETWORK_TYPES); -const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { - bcn1: { - networkMinIp: '', - networkNumber: 1, - networkSubnetMask: '', - networkType: 'bcn', - }, - sn1: { - networkMinIp: '', - networkNumber: 1, - networkSubnetMask: '', - networkType: 'sn', - }, - ifn1: { - networkMinIp: '', - networkNumber: 1, - networkSubnetMask: '', - networkType: 'ifn', - }, -}; - const assertIfn = (type: string) => type === 'ifn'; const assertMn = (type: string) => type === 'mn'; @@ -58,12 +37,13 @@ const AnvilNetworkConfigInputGroup = < }, >({ formUtils, + networkList, previous: { dnsCsv: previousDnsCsv, mtu: previousMtu, - networks: previousNetworks = DEFAULT_NETWORKS, ntpCsv: previousNtpCsv, } = {}, + setNetworkList, }: AnvilNetworkConfigInputGroupProps): ReactElement => { const { buildFinishInputTestBatchFunction, @@ -71,9 +51,6 @@ const AnvilNetworkConfigInputGroup = < msgSetters, } = formUtils; - const [networkList, setNetworkList] = - useState(previousNetworks); - const networkListEntries = useMemo( () => Object.entries(networkList), [networkList], @@ -143,7 +120,7 @@ const AnvilNetworkConfigInputGroup = < const setNetwork = useCallback( (key: string, value?: AnvilNetworkConfigNetwork) => setNetworkList(buildObjectStateSetterCallback(key, value)), - [], + [setNetworkList], ); const handleNetworkTypeChange = useCallback( @@ -198,7 +175,7 @@ const AnvilNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries], + [networkListEntries, setNetworkList], ); const handleNetworkRemove = useCallback( @@ -229,7 +206,7 @@ const AnvilNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries], + [networkListEntries, setNetworkList], ); const networksGridLayout = useMemo(() => { diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index ae00f4a2..838e9bc1 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -3,7 +3,7 @@ import { FC, useMemo, useRef, useState } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; -import AddAnvilManifestInputGroup from './AddAnvilManifestInputGroup'; +import AddManifestInputGroup from './AddManifestInputGroup'; import { INPUT_ID_ANVIL_ID_DOMAIN, INPUT_ID_ANVIL_ID_PREFIX, @@ -58,7 +58,7 @@ const ManageManifestPanel: FC = () => { const addAnvilManifestFormDialogProps = useMemo( () => ({ actionProceedText: 'Add', - content: , + content: , titleText: 'Add a Anvil! manifest', }), [formUtils], diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 3e08fd7a..e53dbbe4 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -107,8 +107,19 @@ type AnvilNetworkConfigInputGroupOptionalProps = { type AnvilNetworkConfigInputGroupProps = AnvilNetworkConfigInputGroupOptionalProps & { formUtils: FormUtils; + networkList: AnvilNetworkConfigNetworkList; + setNetworkList: import('react').Dispatch< + import('react').SetStateAction + >; }; -type AddAnvilInputGroupProps = { - formUtils: FormUtils; +type AddManifestInputGroupOptionalProps = { + previous?: { + networkConfig?: AnvilNetworkConfigInputGroupOptionalProps['previous']; + }; }; + +type AddManifestInputGroupProps = + AddManifestInputGroupOptionalProps & { + formUtils: FormUtils; + }; From 172674db9a19bcb87e68606e15e0d27c5a14293f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 16 Mar 2023 18:22:04 -0400 Subject: [PATCH 18/86] refactor(striker-ui): rename manifest network object and network event handlers --- .../ManageManifest/AddManifestInputGroup.tsx | 4 +- .../AnvilNetworkConfigInputGroup.tsx | 97 ++++++++++--------- striker-ui/types/ManageManifest.d.ts | 28 +++--- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx index c5220fad..7617f204 100644 --- a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx @@ -12,7 +12,7 @@ import AnvilNetworkConfigInputGroup, { } from './AnvilNetworkConfigInputGroup'; import FlexBox from '../FlexBox'; -const DEFAULT_NETWORKS: AnvilNetworkConfigNetworkList = { +const DEFAULT_NETWORKS: ManifestNetworkList = { bcn1: { networkMinIp: '', networkNumber: 1, @@ -51,7 +51,7 @@ const AddManifestInputGroup = < previousNetworkConfig; const [networkList, setNetworkList] = - useState(previousNetworkList); + useState(previousNetworkList); return ( diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 8c4719f5..332276f8 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -63,7 +63,7 @@ const AnvilNetworkConfigInputGroup = < input = networkListEntries, end = networkListEntries.length, }: { - input?: Array<[string, AnvilNetworkConfigNetwork]>; + input?: Array<[string, ManifestNetwork]>; end?: number; } = {}, ) => { @@ -101,8 +101,8 @@ const AnvilNetworkConfigInputGroup = < // Params that depend on others. networkGateway = assertIfn(networkType) ? '' : undefined, networkNumber = getNetworkNumber(networkType) + 1, - }: Partial = {}): { - network: AnvilNetworkConfigNetwork; + }: Partial = {}): { + network: ManifestNetwork; networkId: string; } => ({ network: { @@ -118,72 +118,73 @@ const AnvilNetworkConfigInputGroup = < ); const setNetwork = useCallback( - (key: string, value?: AnvilNetworkConfigNetwork) => + (key: string, value?: ManifestNetwork) => setNetworkList(buildObjectStateSetterCallback(key, value)), [setNetworkList], ); - const handleNetworkTypeChange = useCallback( - ( - { networkId: targetId, networkType: previousType }, - { target: { value } }, - ) => { - const newType = String(value); - - let isIdMatch = false; - let newTypeNumber = 0; + const handleNetworkTypeChange = + useCallback( + ( + { networkId: targetId, networkType: previousType }, + { target: { value } }, + ) => { + const newType = String(value); - const newList = networkListEntries.reduce( - (previous, [networkId, networkValue]) => { - const { networkNumber: initnn, networkType: initnt } = networkValue; + let isIdMatch = false; + let newTypeNumber = 0; - let networkNumber = initnn; - let networkType = initnt; + const newList = networkListEntries.reduce( + (previous, [networkId, networkValue]) => { + const { networkNumber: initnn, networkType: initnt } = networkValue; - if (networkId === targetId) { - isIdMatch = true; + let networkNumber = initnn; + let networkType = initnt; - networkType = newType; - } + if (networkId === targetId) { + isIdMatch = true; - const isTypeMatch = networkType === newType; + networkType = newType; + } - if (isTypeMatch) { - newTypeNumber += 1; - } + const isTypeMatch = networkType === newType; - if (isIdMatch) { if (isTypeMatch) { - networkNumber = newTypeNumber; - } else if (networkType === previousType) { - networkNumber -= 1; + newTypeNumber += 1; } - previous[networkId] = { - ...networkValue, - networkNumber, - networkType, - }; - } else { - previous[networkId] = networkValue; - } + if (isIdMatch) { + if (isTypeMatch) { + networkNumber = newTypeNumber; + } else if (networkType === previousType) { + networkNumber -= 1; + } - return previous; - }, - {}, - ); + previous[networkId] = { + ...networkValue, + networkNumber, + networkType, + }; + } else { + previous[networkId] = networkValue; + } - setNetworkList(newList); - }, - [networkListEntries, setNetworkList], - ); + return previous; + }, + {}, + ); + + setNetworkList(newList); + }, + [networkListEntries, setNetworkList], + ); - const handleNetworkRemove = useCallback( + const handleNetworkRemove = useCallback( ({ networkId: rmId, networkType: rmType }) => { let isIdMatch = false; let networkNumber = 0; - const newList = networkListEntries.reduce( + const newList = networkListEntries.reduce( (previous, [networkId, networkValue]) => { if (networkId === rmId) { isIdMatch = true; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index e53dbbe4..aac4dd01 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -11,7 +11,7 @@ type AnvilIdInputGroupProps = formUtils: FormUtils; }; -type AnvilNetworkConfigNetwork = { +type ManifestNetwork = { networkGateway?: string; networkMinIp: string; networkNumber: number; @@ -19,17 +19,21 @@ type AnvilNetworkConfigNetwork = { networkType: string; }; -type AnvilNetworkConfigNetworkList = { - [networkId: string]: AnvilNetworkConfigNetwork; +type ManifestNetworkList = { + [networkId: string]: ManifestNetwork; }; -type AnvilNetworkCloseHandler = ( - args: { networkId: string } & Pick, +type AnvilNetworkEventHandlerPreviousArgs = { + networkId: string; +} & Pick; + +type AnvilNetworkCloseEventHandler = ( + args: AnvilNetworkEventHandlerPreviousArgs, ...handlerArgs: Parameters ) => ReturnType; -type AnvilNetworkTypeChangeHandler = ( - args: { networkId: string } & Pick, +type AnvilNetworkTypeChangeEventHandler = ( + args: AnvilNetworkEventHandlerPreviousArgs, ...handlerArgs: Parameters ) => ReturnType; @@ -38,8 +42,8 @@ type AnvilNetworkInputGroupOptionalProps = { inputGatewayLabel?: string; inputMinIpLabel?: string; inputSubnetMaskLabel?: string; - onClose?: AnvilNetworkCloseHandler; - onNetworkTypeChange?: AnvilNetworkTypeChangeHandler; + onClose?: AnvilNetworkCloseEventHandler; + onNetworkTypeChange?: AnvilNetworkTypeChangeEventHandler; previous?: { gateway?: string; minIp?: string; @@ -99,7 +103,7 @@ type AnvilNetworkConfigInputGroupOptionalProps = { dnsCsv?: string; /** Max Transmission Unit (MTU); unit: bytes */ mtu?: number; - networks?: AnvilNetworkConfigNetworkList; + networks?: ManifestNetworkList; ntpCsv?: string; }; }; @@ -107,9 +111,9 @@ type AnvilNetworkConfigInputGroupOptionalProps = { type AnvilNetworkConfigInputGroupProps = AnvilNetworkConfigInputGroupOptionalProps & { formUtils: FormUtils; - networkList: AnvilNetworkConfigNetworkList; + networkList: ManifestNetworkList; setNetworkList: import('react').Dispatch< - import('react').SetStateAction + import('react').SetStateAction >; }; From b58a1cb63ac003de3a976f5036565dca71dc0d92 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 17 Mar 2023 01:09:01 -0400 Subject: [PATCH 19/86] fix(striker-ui): add initial host config to add manifest form --- .../ManageManifest/AddManifestInputGroup.tsx | 24 +- .../AnvilHostConfigInputGroup.tsx | 103 ++++++ .../ManageManifest/AnvilHostInputGroup.tsx | 311 ++++++++++-------- .../AnvilNetworkConfigInputGroup.tsx | 7 +- striker-ui/types/ManageManifest.d.ts | 68 ++-- 5 files changed, 347 insertions(+), 166 deletions(-) create mode 100644 striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx diff --git a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx index 7617f204..774c9c11 100644 --- a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx @@ -1,5 +1,6 @@ -import { ReactElement, useState } from 'react'; +import { ReactElement, useMemo, useState } from 'react'; +import AnvilHostConfigInputGroup from './AnvilHostConfigInputGroup'; import AnvilIdInputGroup, { INPUT_ID_ANVIL_ID_DOMAIN, INPUT_ID_ANVIL_ID_PREFIX, @@ -12,7 +13,7 @@ import AnvilNetworkConfigInputGroup, { } from './AnvilNetworkConfigInputGroup'; import FlexBox from '../FlexBox'; -const DEFAULT_NETWORKS: ManifestNetworkList = { +const DEFAULT_NETWORK_LIST: ManifestNetworkList = { bcn1: { networkMinIp: '', networkNumber: 1, @@ -45,23 +46,36 @@ const AddManifestInputGroup = < }, >({ formUtils, - previous: { networkConfig: previousNetworkConfig = {} } = {}, + previous: { + hostConfig: previousHostConfig = {}, + networkConfig: previousNetworkConfig = {}, + } = {}, }: AddManifestInputGroupProps): ReactElement => { - const { networks: previousNetworkList = DEFAULT_NETWORKS } = + const { networks: previousNetworkList = DEFAULT_NETWORK_LIST } = previousNetworkConfig; const [networkList, setNetworkList] = useState(previousNetworkList); + const networkListEntries = useMemo( + () => Object.entries(networkList), + [networkList], + ); + return ( + ); }; diff --git a/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx new file mode 100644 index 00000000..d755395c --- /dev/null +++ b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx @@ -0,0 +1,103 @@ +import { ReactElement, useMemo } from 'react'; + +import AnvilHostInputGroup from './AnvilHostInputGroup'; +import Grid from '../Grid'; + +const INPUT_ID_PREFIX_ANVIL_HOST_CONFIG = 'anvil-host-config-input'; + +const INPUT_GROUP_ID_PREFIX_ANVIL_HOST_CONFIG = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-group`; +const INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG = `${INPUT_GROUP_ID_PREFIX_ANVIL_HOST_CONFIG}-cell`; + +const DEFAULT_HOST_LIST: ManifestHostList = { + node1: { + fences: { + fence1: { fenceName: 'ex_pdu01', fencePort: 0 }, + fence2: { fenceName: 'ex_pdu02', fencePort: 0 }, + }, + hostNumber: 1, + hostType: 'node', + upses: { + ups1: { isPowerHost: true, upsName: 'ex_ups01' }, + ups2: { isPowerHost: false, upsName: 'ex_ups02' }, + }, + }, + node2: { + hostNumber: 2, + hostType: 'node', + }, + dr1: { + hostNumber: 1, + hostType: 'dr', + }, +}; + +const AnvilHostConfigInputGroup = ({ + formUtils, + networkListEntries, + previous: { hosts: previousHostList = DEFAULT_HOST_LIST } = {}, +}: AnvilHostConfigInputGroupProps): ReactElement => { + const hostListEntries = useMemo( + () => Object.entries(previousHostList), + [previousHostList], + ); + + const hostNetworkList = useMemo( + () => + networkListEntries.reduce( + (previous, [networkId, { networkNumber, networkType }]) => { + previous[networkId] = { + networkIp: '', + networkNumber, + networkType, + }; + + return previous; + }, + {}, + ), + [networkListEntries], + ); + + const hostListGridLayout = useMemo( + () => + hostListEntries.reduce( + (previous, [hostId, previousHostArgs]) => { + const { + hostNumber, + hostType, + networks = hostNetworkList, + }: ManifestHost = previousHostArgs; + + const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${hostId}`; + + const hostLabel = `${hostType} ${hostNumber}`; + + previous[cellId] = { + children: ( + + ), + md: 3, + sm: 2, + }; + + return previous; + }, + {}, + ), + [formUtils, hostListEntries, hostNetworkList], + ); + + return ( + + ); +}; + +export default AnvilHostConfigInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx index ddefa929..fd955138 100644 --- a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx @@ -2,6 +2,7 @@ import { ReactElement, useMemo } from 'react'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; +import FlexBox from '../FlexBox'; import Grid from '../Grid'; import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; @@ -13,9 +14,9 @@ import { } from '../../lib/test_input'; import { BodyText } from '../Text'; -const INPUT_ID_PREFIX_ANVIL_HOST_CONFIG = 'anvil-host-config-input'; +const INPUT_ID_PREFIX_ANVIL_HOST = 'anvil-host-input'; -const INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-cell`; +const INPUT_CELL_ID_PREFIX_ANVIL_HOST = `${INPUT_ID_PREFIX_ANVIL_HOST}-cell`; const AnvilHostInputGroup = ({ formUtils: { @@ -25,148 +26,194 @@ const AnvilHostInputGroup = ({ setMsgSetter, }, hostLabel, - previous: { fences = {}, networks = {}, upses = {} } = {}, + previous: { + fences: fenceList = {}, + networks: networkList = {}, + upses: upsList = {}, + } = {}, }: AnvilHostInputGroupProps): ReactElement => { - const gridLayout = useMemo(() => { - let result: GridLayout = {}; - - result = Object.entries(networks).reduce( - (previous, [networkId, { networkIp, networkNumber, networkType }]) => { - const idPostfix = `${networkId}-ip`; - - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; - - setMsgSetter(inputId); - - previous[cellId] = { - children: ( - - } - inputTestBatch={buildIPAddressTestBatch( - inputLabel, - () => { - msgSetters[inputId](); - }, - { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, - (message) => { - msgSetters[inputId]({ - children: message, - }); - }, - )} - onFirstRender={buildInputFirstRenderFunction(inputId)} - required - /> - ), - }; - - return previous; - }, - result, - ); - - result = Object.entries(fences).reduce( - (previous, [fenceId, { fenceName, fencePort }]) => { - const idPostfix = `${fenceId}-port`; - - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - const inputLabel = fenceName; - - setMsgSetter(inputId); - - previous[cellId] = { - children: ( - - } - inputTestBatch={buildNumberTestBatch( - inputLabel, - () => { - msgSetters[inputId](); - }, - { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, - (message) => { - msgSetters[inputId]({ - children: message, - }); - }, - )} - required - valueType="number" - /> - ), - }; - - return previous; - }, - result, - ); - - result = Object.entries(upses).reduce( - (previous, [upsId, { isPowerHost, upsName }]) => { - const idPostfix = `${upsId}-power-host`; - - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST_CONFIG}-${idPostfix}`; - - previous[cellId] = { - children: ( - - } - valueType="boolean" - /> - ), - }; + const fenceListEntries = useMemo( + () => Object.entries(fenceList), + [fenceList], + ); - return previous; - }, - result, - ); + const networkListEntries = useMemo( + () => Object.entries(networkList), + [networkList], + ); - return result; - }, [ - buildFinishInputTestBatchFunction, - buildInputFirstRenderFunction, - setMsgSetter, - fences, - msgSetters, - networks, - upses, - ]); + const upsListEntries = useMemo(() => Object.entries(upsList), [upsList]); + + const fenceListGridLayout = useMemo( + () => + fenceListEntries.reduce( + (previous, [fenceId, { fenceName, fencePort }]) => { + const idPostfix = `${fenceId}-port`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const inputLabel = fenceName; + + setMsgSetter(inputId); + + previous[cellId] = { + children: ( + + } + inputTestBatch={buildNumberTestBatch( + inputLabel, + () => { + msgSetters[inputId](); + }, + { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, + (message) => { + msgSetters[inputId]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(inputId)} + required + valueType="number" + /> + ), + }; + + return previous; + }, + {}, + ), + [ + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + fenceListEntries, + msgSetters, + setMsgSetter, + ], + ); + + const networkListGridLayout = useMemo( + () => + networkListEntries.reduce( + (previous, [networkId, { networkIp, networkNumber, networkType }]) => { + const idPostfix = `${networkId}-ip`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; + + setMsgSetter(inputId); + + previous[cellId] = { + children: ( + + } + inputTestBatch={buildIPAddressTestBatch( + inputLabel, + () => { + msgSetters[inputId](); + }, + { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, + (message) => { + msgSetters[inputId]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(inputId)} + required + /> + ), + }; + + return previous; + }, + {}, + ), + [ + networkListEntries, + setMsgSetter, + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + ], + ); + + const upsListGridLayout = useMemo( + () => + upsListEntries.reduce( + (previous, [upsId, { isPowerHost, upsName }]) => { + const idPostfix = `${upsId}-power-host`; + + const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + + const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + + previous[cellId] = { + children: ( + + } + valueType="boolean" + /> + ), + }; + + return previous; + }, + {}, + ), + [upsListEntries], + ); return ( - + {hostLabel} - + + + {Boolean(fenceListEntries.length || upsListEntries.length) && ( + + )} + ); }; +export { INPUT_ID_PREFIX_ANVIL_HOST }; + export default AnvilHostInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 332276f8..63ea5d8a 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -37,7 +37,7 @@ const AnvilNetworkConfigInputGroup = < }, >({ formUtils, - networkList, + networkListEntries, previous: { dnsCsv: previousDnsCsv, mtu: previousMtu, @@ -51,11 +51,6 @@ const AnvilNetworkConfigInputGroup = < msgSetters, } = formUtils; - const networkListEntries = useMemo( - () => Object.entries(networkList), - [networkList], - ); - const getNetworkNumber = useCallback( ( type: string, diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index aac4dd01..8aa911ec 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -23,6 +23,36 @@ type ManifestNetworkList = { [networkId: string]: ManifestNetwork; }; +type ManifestHostNetworkList = { + [networkId: string]: { + networkIp: string; + networkNumber: number; + networkType: string; + }; +}; + +type ManifestHost = { + fences?: { + [fenceId: string]: { + fenceName: string; + fencePort: number; + }; + }; + hostNumber: number; + hostType: string; + networks?: ManifestHostNetworkList; + upses?: { + [upsId: string]: { + isPowerHost: boolean; + upsName: string; + }; + }; +}; + +type ManifestHostList = { + [hostId: string]: ManifestHost; +}; + type AnvilNetworkEventHandlerPreviousArgs = { networkId: string; } & Pick; @@ -68,34 +98,13 @@ type AnvilNetworkInputGroupProps = }; type AnvilHostInputGroupOptionalProps = { - previous?: { - fences?: { - [fenceId: string]: { - fenceName: string; - fencePort: number; - }; - }; - networks?: { - [networkId: string]: { - networkIp: string; - networkNumber: number; - networkType: string; - }; - }; - upses?: { - [upsId: string]: { - isPowerHost: boolean; - upsName: string; - }; - }; - }; + previous?: Pick; }; type AnvilHostInputGroupProps = AnvilHostInputGroupOptionalProps & { formUtils: FormUtils; hostLabel: string; - idPrefix: string; }; type AnvilNetworkConfigInputGroupOptionalProps = { @@ -111,15 +120,28 @@ type AnvilNetworkConfigInputGroupOptionalProps = { type AnvilNetworkConfigInputGroupProps = AnvilNetworkConfigInputGroupOptionalProps & { formUtils: FormUtils; - networkList: ManifestNetworkList; + networkListEntries: Array<[string, ManifestNetwork]>; setNetworkList: import('react').Dispatch< import('react').SetStateAction >; }; +type AnvilHostConfigInputGroupOptionalProps = { + previous?: { + hosts?: ManifestHostList; + }; +}; + +type AnvilHostConfigInputGroupProps = + AnvilHostConfigInputGroupOptionalProps & { + formUtils: FormUtils; + networkListEntries: Array<[string, ManifestNetwork]>; + }; + type AddManifestInputGroupOptionalProps = { previous?: { networkConfig?: AnvilNetworkConfigInputGroupOptionalProps['previous']; + hostConfig?: AnvilHostConfigInputGroupOptionalProps['previous']; }; }; From 7b0a7e711d953d811062924fb292d0c3fbd4c191 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 17 Mar 2023 16:20:29 -0400 Subject: [PATCH 20/86] fix(striker-ui-api): add get host name to access module --- striker-ui-api/src/lib/accessModule.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/striker-ui-api/src/lib/accessModule.ts b/striker-ui-api/src/lib/accessModule.ts index f1b02268..6365cd54 100644 --- a/striker-ui-api/src/lib/accessModule.ts +++ b/striker-ui-api/src/lib/accessModule.ts @@ -148,6 +148,22 @@ const getAnvilData = ( spawnSyncOptions, ).stdout; +const getLocalHostName = () => { + let result: string; + + try { + result = execModuleSubroutine('host_name', { + subModuleName: 'Get', + }).stdout; + } catch (subError) { + throw new Error(`Failed to get local host name; CAUSE: ${subError}`); + } + + shout(`localHostName=${result}`); + + return result; +}; + const getLocalHostUUID = () => { let result: string; @@ -156,7 +172,7 @@ const getLocalHostUUID = () => { subModuleName: 'Get', }).stdout; } catch (subError) { - throw new Error(`Failed to get localhost UUID; CAUSE: ${subError}`); + throw new Error(`Failed to get local host UUID; CAUSE: ${subError}`); } shout(`localHostUUID=[${result}]`); @@ -201,6 +217,7 @@ export { dbSubRefreshTimestamp, dbWrite, getAnvilData, + getLocalHostName, getLocalHostUUID, getPeerData, execModuleSubroutine as sub, From db248efd96a033fae10e0f3a14beaa1aa42f0aea Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 17 Mar 2023 16:21:44 -0400 Subject: [PATCH 21/86] fix(striker-ui-api): consolidate disassemble host name functions --- striker-ui-api/src/lib/disassembleHostName.ts | 8 ++++++++ striker-ui-api/src/lib/getShortHostName.ts | 2 -- .../src/lib/request_handlers/host/buildQueryHostDetail.ts | 2 +- striker-ui-api/src/lib/request_handlers/host/getHost.ts | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 striker-ui-api/src/lib/disassembleHostName.ts delete mode 100644 striker-ui-api/src/lib/getShortHostName.ts diff --git a/striker-ui-api/src/lib/disassembleHostName.ts b/striker-ui-api/src/lib/disassembleHostName.ts new file mode 100644 index 00000000..c30593a5 --- /dev/null +++ b/striker-ui-api/src/lib/disassembleHostName.ts @@ -0,0 +1,8 @@ +export const getHostNameDomain = (hostName: string) => + hostName.replace(/^.*?[.]/, ''); + +export const getHostNamePrefix = (hostName: string) => + hostName.replace(/-.*$/, ''); + +export const getShortHostName = (hostName: string) => + hostName.replace(/[.].*$/, ''); diff --git a/striker-ui-api/src/lib/getShortHostName.ts b/striker-ui-api/src/lib/getShortHostName.ts deleted file mode 100644 index 1cd3daa1..00000000 --- a/striker-ui-api/src/lib/getShortHostName.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const getShortHostName = (hostName: string) => - hostName.replace(/[.].*$/, ''); diff --git a/striker-ui-api/src/lib/request_handlers/host/buildQueryHostDetail.ts b/striker-ui-api/src/lib/request_handlers/host/buildQueryHostDetail.ts index 05da6d53..155fd06b 100644 --- a/striker-ui-api/src/lib/request_handlers/host/buildQueryHostDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/host/buildQueryHostDetail.ts @@ -1,7 +1,7 @@ import { buildKnownIDCondition } from '../../buildCondition'; import { buildQueryResultModifier } from '../../buildQueryResultModifier'; import { cap } from '../../cap'; -import { getShortHostName } from '../../getShortHostName'; +import { getShortHostName } from '../../disassembleHostName'; import { stdout } from '../../shell'; type ExtractVariableKeyFunction = (parts: string[]) => string; diff --git a/striker-ui-api/src/lib/request_handlers/host/getHost.ts b/striker-ui-api/src/lib/request_handlers/host/getHost.ts index 8e5b2000..1b47cdaa 100644 --- a/striker-ui-api/src/lib/request_handlers/host/getHost.ts +++ b/striker-ui-api/src/lib/request_handlers/host/getHost.ts @@ -4,7 +4,7 @@ import buildGetRequestHandler from '../buildGetRequestHandler'; import { buildQueryHostDetail } from './buildQueryHostDetail'; import { buildQueryResultReducer } from '../../buildQueryResultModifier'; import { toLocal } from '../../convertHostUUID'; -import { getShortHostName } from '../../getShortHostName'; +import { getShortHostName } from '../../disassembleHostName'; import { sanitize } from '../../sanitize'; export const getHost = buildGetRequestHandler((request, buildQueryOptions) => { From 9e9b84604f238cdcf1b5b97d7ccaf6763bb9b5ff Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 17 Mar 2023 19:34:47 -0400 Subject: [PATCH 22/86] fix(striker-ui-api): add /manifest/template --- .../manifest/getManifestTemplate.ts | 107 ++++++++++++++++++ .../lib/request_handlers/manifest/index.ts | 1 + striker-ui-api/src/routes/manifest.ts | 7 +- 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts new file mode 100644 index 00000000..ad94ba63 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts @@ -0,0 +1,107 @@ +import { RequestHandler } from 'express'; + +import { dbQuery, getLocalHostName } from '../../accessModule'; +import { + getHostNameDomain, + getHostNamePrefix, + getShortHostName, +} from '../../disassembleHostName'; +import { stderr } from '../../shell'; + +export const getManifestTemplate: RequestHandler = (request, response) => { + let localHostName = ''; + + try { + localHostName = getLocalHostName(); + } catch (subError) { + stderr(String(subError)); + + response.status(500).send(); + + return; + } + + const localDomain = getHostNameDomain(localHostName); + const localShort = getShortHostName(localHostName); + const localPrefix = getHostNamePrefix(localShort); + + let rawQueryResult: Array< + [fenceUUID: string, fenceName: string, upsUUID: string, upsName: string] + >; + + try { + ({ stdout: rawQueryResult } = dbQuery( + `SELECT + fence_uuid, + fence_name, + ups_uuid, + ups_name + FROM ( + SELECT + ROW_NUMBER() OVER (ORDER BY fence_name), + fence_uuid, + fence_name + FROM fences + ORDER BY fence_name + ) AS a + FULL JOIN ( + SELECT + ROW_NUMBER() OVER (ORDER BY ups_name), + ups_uuid, + ups_name + FROM upses + ORDER BY ups_name + ) AS b ON a.row_number = b.row_number;`, + )); + } catch (queryError) { + stderr(`Failed to execute query; CAUSE: ${queryError}`); + + response.status(500).send(); + + return; + } + + const queryResult = rawQueryResult.reduce<{ + fences: { + [fenceUUID: string]: { + fenceName: string; + fenceUUID: string; + }; + }; + upses: { + [upsUUID: string]: { + upsName: string; + upsUUID: string; + }; + }; + }>( + (previous, [fenceUUID, fenceName, upsUUID, upsName]) => { + const { fences, upses } = previous; + + if (fenceUUID) { + fences[fenceUUID] = { + fenceName, + fenceUUID, + }; + } + + if (upsUUID) { + upses[upsUUID] = { + upsName, + upsUUID, + }; + } + + return previous; + }, + { fences: {}, upses: {} }, + ); + + response.status(200).send({ + localHostName, + localShort, + localPrefix, + localDomain, + ...queryResult, + }); +}; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts index 79e54734..bf41d98e 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/index.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -1 +1,2 @@ export * from './getManifest'; +export * from './getManifestTemplate'; diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts index 2502a183..6b3e5e5c 100644 --- a/striker-ui-api/src/routes/manifest.ts +++ b/striker-ui-api/src/routes/manifest.ts @@ -1,9 +1,12 @@ import express from 'express'; -import { getManifest } from '../lib/request_handlers/manifest'; +import { + getManifest, + getManifestTemplate, +} from '../lib/request_handlers/manifest'; const router = express.Router(); -router.get('/', getManifest); +router.get('/', getManifest).get('/template', getManifestTemplate); export default router; From 7575bf8aed3212bbb5706e9a38e267d00efff7e5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 18 Mar 2023 02:45:51 -0400 Subject: [PATCH 23/86] fix(striker-ui-api): add function to disassemble entity id --- striker-ui-api/src/lib/disassembleEntityId.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 striker-ui-api/src/lib/disassembleEntityId.ts diff --git a/striker-ui-api/src/lib/disassembleEntityId.ts b/striker-ui-api/src/lib/disassembleEntityId.ts new file mode 100644 index 00000000..609d46f3 --- /dev/null +++ b/striker-ui-api/src/lib/disassembleEntityId.ts @@ -0,0 +1,20 @@ +export const getEntityName = (id: string) => id.replace(/\d*$/, ''); + +export const getEntityNumber = (id: string) => + Number.parseInt(id.replace(/^[^\d]*/, '')); + +export const getEntityParts = (id: string) => { + let name = ''; + let number = NaN; + + const matchResult = id.match(/^([^\d]*)(\d*)$/); + + if (matchResult) { + const parts = matchResult; + + name = parts[1]; + number = Number.parseInt(parts[2]); + } + + return { name, number }; +}; From a066fb7ede5de6204301fcedd87f3024b86117a9 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 18 Mar 2023 02:48:07 -0400 Subject: [PATCH 24/86] fix(striker-ui-api): allow get manifest detail --- .../manifest/getManifestDetail.ts | 250 ++++++++++++++++++ .../lib/request_handlers/manifest/index.ts | 1 + striker-ui-api/src/routes/manifest.ts | 6 +- striker-ui-api/src/types/APIManifest.d.ts | 69 +++++ .../src/types/GetAnvilDataFunction.d.ts | 68 +++++ 5 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts new file mode 100644 index 00000000..ac6752b1 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -0,0 +1,250 @@ +import { RequestHandler } from 'express'; + +import { getAnvilData } from '../../accessModule'; +import { getEntityParts } from '../../disassembleEntityId'; +import { stderr, stdout } from '../../shell'; + +const handleSortEntries = ( + [aId]: T, + [bId]: T, +): number => { + const { name: at, number: an } = getEntityParts(aId); + const { name: bt, number: bn } = getEntityParts(bId); + + let result = 0; + + if (at === bt) { + if (an > bn) { + result = 1; + } else if (an < bn) { + result = -1; + } + } else if (at > bt) { + result = 1; + } else if (at < bt) { + result = -1; + } + + return result; +}; + +const handleSortNetworks = ( + [aId]: T, + [bId]: T, +): number => { + const isAIfn = /^ifn/.test(aId); + const isBIfn = /^ifn/.test(bId); + const { name: at, number: an } = getEntityParts(aId); + const { name: bt, number: bn } = getEntityParts(bId); + + let result = 0; + + if (at === bt) { + if (an > bn) { + result = 1; + } else if (an < bn) { + result = -1; + } + } else if (isAIfn) { + result = 1; + } else if (isBIfn) { + result = -1; + } else if (at > bt) { + result = 1; + } else if (at < bt) { + result = -1; + } + + return result; +}; + +export const getManifestDetail: RequestHandler = (request, response) => { + const { + params: { manifestUUID }, + } = request; + + let rawManifestListData: AnvilDataManifestListHash | undefined; + + try { + ({ manifests: rawManifestListData } = getAnvilData<{ + manifests?: AnvilDataManifestListHash; + }>( + { manifests: true }, + { + predata: [['Striker->load_manifest', { manifest_uuid: manifestUUID }]], + }, + )); + } catch (subError) { + stderr( + `Failed to get install manifest ${manifestUUID}; CAUSE: ${subError}`, + ); + + response.status(500).send(); + + return; + } + + stdout( + `Raw install manifest list:\n${JSON.stringify( + rawManifestListData, + null, + 2, + )}`, + ); + + if (!rawManifestListData) { + response.status(404).send(); + + return; + } + + const { + manifest_uuid: { + [manifestUUID]: { + parsed: { + domain, + fences: fenceUuidList = {}, + machine, + name, + networks: { dns: dnsCsv, mtu, name: networkList, ntp: ntpCsv }, + prefix, + sequence, + upses: upsUuidList = {}, + }, + }, + }, + } = rawManifestListData; + + const manifestData: ManifestDetail = { + domain, + hostConfig: { + hosts: Object.entries(machine) + .sort(handleSortEntries) + .reduce( + ( + previous, + [hostId, { fence = {}, ipmi_ip: ipmiIp, network, ups = {} }], + ) => { + const { name: hostType, number: hostNumber } = + getEntityParts(hostId); + + stdout(`host=${hostType},n=${hostNumber}`); + + previous[hostId] = { + fences: Object.entries(fence) + .sort(handleSortEntries) + .reduce( + (fences, [fenceName, { port: fencePort }]) => { + const fenceUuidContainer = fenceUuidList[fenceName]; + + if (fenceUuidContainer) { + const { uuid: fenceUuid } = fenceUuidContainer; + + fences[fenceName] = { + fenceName, + fencePort, + fenceUuid, + }; + } + + return fences; + }, + {}, + ), + hostNumber, + hostType, + ipmiIp, + networks: Object.entries(network) + .sort(handleSortNetworks) + .reduce( + (hostNetworks, [networkId, { ip: networkIp }]) => { + const { name: networkType, number: networkNumber } = + getEntityParts(networkId); + + stdout(`hostnetwork=${networkType},n=${networkNumber}`); + + hostNetworks[networkId] = { + networkIp, + networkNumber, + networkType, + }; + + return hostNetworks; + }, + {}, + ), + upses: Object.entries(ups) + .sort(handleSortEntries) + .reduce((upses, [upsName, { used }]) => { + const upsUuidContainer = upsUuidList[upsName]; + + if (upsUuidContainer) { + const { uuid: upsUuid } = upsUuidContainer; + + upses[upsName] = { + isUsed: Boolean(used), + upsName, + upsUuid, + }; + } + + return upses; + }, {}), + }; + + return previous; + }, + {}, + ), + }, + name, + networkConfig: { + dnsCsv, + mtu: Number.parseInt(mtu), + networks: Object.entries(networkList) + .sort(handleSortNetworks) + .reduce( + ( + networks, + [ + networkId, + { + gateway: networkGateway, + network: networkMinIp, + subnet: networkSubnetMask, + }, + ], + ) => { + const { name: networkType, number: networkNumber } = + getEntityParts(networkId); + + stdout(`network=${networkType},n=${networkNumber}`); + + networks[networkId] = { + networkGateway, + networkMinIp, + networkNumber, + networkSubnetMask, + networkType, + }; + + return networks; + }, + {}, + ), + ntpCsv, + }, + prefix, + sequence, + }; + + stdout( + `Extracted install manifest data:\n${JSON.stringify( + manifestData, + null, + 2, + )}`, + ); + + response.status(200).send(manifestData); +}; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts index bf41d98e..93271202 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/index.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -1,2 +1,3 @@ export * from './getManifest'; +export * from './getManifestDetail'; export * from './getManifestTemplate'; diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts index 6b3e5e5c..e68bca88 100644 --- a/striker-ui-api/src/routes/manifest.ts +++ b/striker-ui-api/src/routes/manifest.ts @@ -2,11 +2,15 @@ import express from 'express'; import { getManifest, + getManifestDetail, getManifestTemplate, } from '../lib/request_handlers/manifest'; const router = express.Router(); -router.get('/', getManifest).get('/template', getManifestTemplate); +router + .get('/', getManifest) + .get('/template', getManifestTemplate) + .get('/:manifestUUID', getManifestDetail); export default router; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index 0e846a90..b886ad0e 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -2,3 +2,72 @@ type ManifestOverview = { manifestName: string; manifestUUID: string; }; + +type ManifestDetailNetwork = { + networkGateway: string; + networkMinIp: string; + networkNumber: number; + networkSubnetMask: string; + networkType: string; +}; + +type ManifestDetailNetworkList = { + [networkId: string]: ManifestDetailNetwork; +}; + +type ManifestDetailFence = { + fenceName: string; + fencePort: string; + fenceUuid: string; +}; + +type ManifestDetailFenceList = { + [fenceId: string]: ManifestDetailFence; +}; + +type ManifestDetailHostNetwork = { + networkIp: string; + networkNumber: number; + networkType: string; +}; + +type ManifestDetailHostNetworkList = { + [networkId: string]: ManifestDetailHostNetwork; +}; + +type ManifestDetailUps = { + isUsed: boolean; + upsName: string; + upsUuid: string; +}; + +type ManifestDetailUpsList = { + [upsId: string]: ManifestDetailUps; +}; + +type ManifestDetailHostList = { + [hostId: string]: { + fences: ManifestDetailFenceList; + hostNumber: number; + hostType: string; + ipmiIp: string; + networks: ManifestDetailHostNetworkList; + upses: ManifestDetailUpsList; + }; +}; + +type ManifestDetail = { + domain: string; + hostConfig: { + hosts: ManifestDetailHostList; + }; + name: string; + networkConfig: { + dnsCsv: string; + mtu: number; + networks: ManifestDetailNetworkList; + ntpCsv: string; + }; + prefix: string; + sequence: string; +}; diff --git a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts index 191337b3..d82d05b6 100644 --- a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts +++ b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts @@ -13,6 +13,74 @@ type AnvilDataDatabaseHash = { }; }; +type AnvilDataManifestListHash = { + manifest_uuid: { + [manifestUUID: string]: { + parsed: { + domain: string; + fences?: { + [fenceId: string]: { + uuid: string; + }; + }; + machine: { + [hostId: string]: { + fence?: { + [fenceName: string]: { + port: string; + }; + }; + ipmi_ip: string; + name: string; + network: { + [networkId: string]: { + ip: string; + }; + }; + ups?: { + [upsName: string]: { + used: string; + }; + }; + }; + }; + name: string; + networks: { + count: { + [networkType: string]: number; + }; + dns: string; + mtu: string; + name: { + [networkId: string]: { + gateway: string; + network: string; + subnet: string; + }; + }; + ntp: string; + }; + prefix: string; + sequence: string; + upses?: { + [upsId: string]: { + uuid: string; + }; + }; + }; + }; + }; + name_to_uuid: Record; +} & Record< + string, + { + manifest_last_ran: number; + manifest_name: string; + manifest_note: string; + manifest_xml: string; + } +>; + type AnvilDataUPSHash = { [upsName: string]: { agent: string; From 4104b87b2db92d9163607fd988568456ed0a40e6 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 18 Mar 2023 03:32:01 -0400 Subject: [PATCH 25/86] fix(striker-ui-api): add next sequence number to manifest template --- .../manifest/getManifestTemplate.ts | 74 +++++++++++-------- striker-ui-api/src/types/APIManifest.d.ts | 18 +++++ 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts index ad94ba63..d3a87085 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts @@ -21,21 +21,30 @@ export const getManifestTemplate: RequestHandler = (request, response) => { return; } - const localDomain = getHostNameDomain(localHostName); - const localShort = getShortHostName(localHostName); - const localPrefix = getHostNamePrefix(localShort); + const localShortHostName = getShortHostName(localHostName); + + const domain = getHostNameDomain(localHostName); + const prefix = getHostNamePrefix(localShortHostName); let rawQueryResult: Array< - [fenceUUID: string, fenceName: string, upsUUID: string, upsName: string] + [ + fenceUUID: string, + fenceName: string, + upsUUID: string, + upsName: string, + manifestUuid: string, + lastSequence: string, + ] >; try { ({ stdout: rawQueryResult } = dbQuery( `SELECT - fence_uuid, - fence_name, - ups_uuid, - ups_name + a.fence_uuid, + a.fence_name, + b.ups_uuid, + b.ups_name, + c.last_sequence FROM ( SELECT ROW_NUMBER() OVER (ORDER BY fence_name), @@ -51,7 +60,17 @@ export const getManifestTemplate: RequestHandler = (request, response) => { ups_name FROM upses ORDER BY ups_name - ) AS b ON a.row_number = b.row_number;`, + ) AS b ON a.row_number = b.row_number + FULL JOIN ( + SELECT + ROW_NUMBER() OVER (ORDER BY manifest_name DESC), + CAST( + SUBSTRING(manifest_name, '([\\d]*)$') AS INTEGER + ) AS last_sequence + FROM manifests + ORDER BY manifest_name DESC + LIMIT 1 + ) AS c ON a.row_number = c.row_number;`, )); } catch (queryError) { stderr(`Failed to execute query; CAUSE: ${queryError}`); @@ -61,21 +80,10 @@ export const getManifestTemplate: RequestHandler = (request, response) => { return; } - const queryResult = rawQueryResult.reduce<{ - fences: { - [fenceUUID: string]: { - fenceName: string; - fenceUUID: string; - }; - }; - upses: { - [upsUUID: string]: { - upsName: string; - upsUUID: string; - }; - }; - }>( - (previous, [fenceUUID, fenceName, upsUUID, upsName]) => { + const queryResult = rawQueryResult.reduce< + Pick + >( + (previous, [fenceUUID, fenceName, upsUUID, upsName, lastSequence]) => { const { fences, upses } = previous; if (fenceUUID) { @@ -92,16 +100,20 @@ export const getManifestTemplate: RequestHandler = (request, response) => { }; } + if (lastSequence) { + previous.sequence = Number.parseInt(lastSequence) + 1; + } + return previous; }, - { fences: {}, upses: {} }, + { fences: {}, sequence: 1, upses: {} }, ); - response.status(200).send({ - localHostName, - localShort, - localPrefix, - localDomain, + const result: ManifestTemplate = { + domain, + prefix, ...queryResult, - }); + }; + + response.status(200).send(result); }; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index b886ad0e..b2f3fe2d 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -71,3 +71,21 @@ type ManifestDetail = { prefix: string; sequence: string; }; + +type ManifestTemplate = { + domain: string; + fences: { + [fenceUUID: string]: { + fenceName: string; + fenceUUID: string; + }; + }; + prefix: string; + sequence: number; + upses: { + [upsUUID: string]: { + upsName: string; + upsUUID: string; + }; + }; +}; From 340a04de4759679e1e394cb332ec0cbb2b20ffa5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 18 Mar 2023 03:35:49 -0400 Subject: [PATCH 26/86] fix(striker-ui-api): change sequence number type in manifest detail --- .../lib/request_handlers/manifest/getManifestDetail.ts | 10 +--------- striker-ui-api/src/types/APIManifest.d.ts | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts index ac6752b1..e0c24bd1 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -235,16 +235,8 @@ export const getManifestDetail: RequestHandler = (request, response) => { ntpCsv, }, prefix, - sequence, + sequence: Number.parseInt(sequence), }; - stdout( - `Extracted install manifest data:\n${JSON.stringify( - manifestData, - null, - 2, - )}`, - ); - response.status(200).send(manifestData); }; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index b2f3fe2d..0c433240 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -69,7 +69,7 @@ type ManifestDetail = { ntpCsv: string; }; prefix: string; - sequence: string; + sequence: number; }; type ManifestTemplate = { From a4359b1fa432e97dc7136287315bbfeb460f4e35 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 20 Mar 2023 15:29:28 -0400 Subject: [PATCH 27/86] docs(striker-ui-api): explain sorting networks in GET manifest detail --- .../lib/request_handlers/manifest/getManifestDetail.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts index e0c24bd1..4291394c 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -28,6 +28,15 @@ const handleSortEntries = ( return result; }; +/** + * This handler sorts networks in ascending order. But, it groups IFNs at the + * end of the list in ascending order. + * + * When the sort callback returns: + * - positive, element `a` will get a higher index than element `b` + * - negative, element `a` will get a lower index than element `b` + * - zero, elements' index will remain unchanged + */ const handleSortNetworks = ( [aId]: T, [bId]: T, From d368f8dd7bce3944c45947bf96d65dcd8c129489 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 20 Mar 2023 16:38:20 -0400 Subject: [PATCH 28/86] fix(striker-ui-api): ignore deleted manifest(s) when listing --- striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts index bf0ce4e6..380acee0 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifest.ts @@ -10,6 +10,7 @@ export const getManifest: RequestHandler = buildGetRequestHandler( manifest_uuid, manifest_name FROM manifests + WHERE manifest_note != 'DELETED' ORDER BY manifest_name ASC;`; const afterQueryReturn: QueryResultModifierFunction | undefined = buildQueryResultReducer<{ [manifestUUID: string]: ManifestOverview }>( From 2e599971cca95a7c62b46e97c45bf98c3f2f2f12 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 20 Mar 2023 16:39:00 -0400 Subject: [PATCH 29/86] fix(striker-ui-api): add DELETE one or more manifest --- .../manifest/deleteManifest.ts | 37 +++++++++++++++++++ .../lib/request_handlers/manifest/index.ts | 1 + striker-ui-api/src/routes/manifest.ts | 5 ++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/deleteManifest.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/deleteManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/deleteManifest.ts new file mode 100644 index 00000000..0ba2d14b --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/deleteManifest.ts @@ -0,0 +1,37 @@ +import { RequestHandler } from 'express'; + +import { sub } from '../../accessModule'; +import { stderr, stdout } from '../../shell'; + +export const deleteManifest: RequestHandler< + { manifestUuid: string }, + undefined, + { uuids: string[] } +> = (request, response) => { + const { + params: { manifestUuid: rawManifestUuid }, + body: { uuids: rawManifestUuidList } = {}, + } = request; + + const manifestUuidList: string[] = rawManifestUuidList + ? rawManifestUuidList + : [rawManifestUuid]; + + manifestUuidList.forEach((uuid) => { + stdout(`Begin delete manifest ${uuid}.`); + + try { + sub('insert_or_update_manifests', { + subParams: { delete: 1, manifest_uuid: uuid }, + }); + } catch (subError) { + stderr(`Failed to delete manifest ${uuid}; CAUSE: ${subError}`); + + response.status(500).send(); + + return; + } + }); + + response.status(204).send(); +}; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts index 93271202..5b9845b3 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/index.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -1,3 +1,4 @@ +export * from './deleteManifest'; export * from './getManifest'; export * from './getManifestDetail'; export * from './getManifestTemplate'; diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts index e68bca88..4b462bc9 100644 --- a/striker-ui-api/src/routes/manifest.ts +++ b/striker-ui-api/src/routes/manifest.ts @@ -1,6 +1,7 @@ import express from 'express'; import { + deleteManifest, getManifest, getManifestDetail, getManifestTemplate, @@ -11,6 +12,8 @@ const router = express.Router(); router .get('/', getManifest) .get('/template', getManifestTemplate) - .get('/:manifestUUID', getManifestDetail); + .get('/:manifestUUID', getManifestDetail) + .delete('/', deleteManifest) + .delete('/manifestUuid', deleteManifest); export default router; From 226c423af07323b98ca0839f18c306da3ebc6d0d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 20 Mar 2023 18:01:47 -0400 Subject: [PATCH 30/86] fix: allow param override in generate_manifest in Striker.pm --- Anvil/Tools/Striker.pm | 71 +++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/Anvil/Tools/Striker.pm b/Anvil/Tools/Striker.pm index 54d46a22..a6cef8c1 100644 --- a/Anvil/Tools/Striker.pm +++ b/Anvil/Tools/Striker.pm @@ -308,68 +308,89 @@ sub generate_manifest my $self = shift; my $parameter = shift; my $anvil = $self->parent; - my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + my $debug = $parameter->{debug} // 3; + + my $domain = $parameter->{domain} // $anvil->data->{cgi}{domain}{value}; + my $manifest_uuid = $parameter->{manifest_uuid} // $anvil->data->{cgi}{manifest_uuid}{value}; + my $name_prefix = $parameter->{prefix} // $anvil->data->{cgi}{prefix}{value}; + my $network_dns = $parameter->{dns} // $anvil->data->{cgi}{dns}{value}; + my $network_mtu = $parameter->{mtu} // $anvil->data->{cgi}{mtu}{value}; + my $network_ntp = $parameter->{ntp} // $anvil->data->{cgi}{ntp}{value}; + my $padded_sequence = $parameter->{sequence} // $anvil->data->{cgi}{sequence}{value}; + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Striker->generate_manifest()" }}); $anvil->Database->get_upses({debug => $debug}); $anvil->Database->get_fences({debug => $debug}); - my $manifest_uuid = $anvil->data->{cgi}{manifest_uuid}{value} eq "new" ? "" : $anvil->data->{cgi}{manifest_uuid}{value}; - my $padded_sequence = $anvil->data->{cgi}{sequence}{value}; + $manifest_uuid = $manifest_uuid eq "new" ? "" : $manifest_uuid; + if (length($padded_sequence) == 1) { $padded_sequence = sprintf("%02d", $padded_sequence); } - my $anvil_name = $anvil->data->{cgi}{prefix}{value}."-anvil-".$padded_sequence; - my $node1_name = $anvil->data->{cgi}{prefix}{value}."-a".$padded_sequence."n01"; - my $node2_name = $anvil->data->{cgi}{prefix}{value}."-a".$padded_sequence."n02"; - my $dr1_name = $anvil->data->{cgi}{prefix}{value}."-a".$padded_sequence."dr01"; + + my $anvil_name = $name_prefix."-anvil-".$padded_sequence; + my $node1_name = $name_prefix."-a".$padded_sequence."n01"; + my $node2_name = $name_prefix."-a".$padded_sequence."n02"; + my $dr1_name = $name_prefix."-a".$padded_sequence."dr01"; my $machines = {}; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { anvil_name => $anvil_name }}); my $manifest_xml = ' - - + + '; foreach my $network ("bcn", "sn", "ifn") { my $count_key = $network."_count"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cgi::${count_key}::value" => $anvil->data->{cgi}{$count_key}{value} }}); - foreach my $i (1..$anvil->data->{cgi}{$count_key}{value}) + my $count_value = $parameter->{$count_key} // $anvil->data->{cgi}{$count_key}{value}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "${count_key}" => $count_value }}); + foreach my $i (1..$count_value) { - my $network_name = $network.$i; - my $network_key = $network_name."_network"; - my $subnet_key = $network_name."_subnet"; - my $gateway_key = $network_name."_gateway"; - $manifest_xml .= ' '."\n"; + my $network_name = $network.$i; + my $network_key = $network_name."_network"; + my $network_value = $parameter->{$network_key} // $anvil->data->{cgi}{$network_key}{value}; + my $subnet_key = $network_name."_subnet"; + my $subnet_value = $parameter->{$subnet_key} // $anvil->data->{cgi}{$subnet_key}{value}; + my $gateway_key = $network_name."_gateway"; + my $gateway_value = $parameter->{$gateway_key} // $anvil->data->{cgi}{$gateway_key}{value}; + + $manifest_xml .= ' '."\n"; # While we're here, gather the network data for the machines. foreach my $machine ("node1", "node2", "dr1") { # Record the network - my $ip_key = $machine."_".$network_name."_ip"; - $machines->{$machine}{network}{$network_name} = defined $anvil->data->{cgi}{$ip_key}{value} ? $anvil->data->{cgi}{$ip_key}{value} : ""; + my $ip_key = $machine."_".$network_name."_ip"; + my $ip_value = ($parameter->{$ip_key} // $anvil->data->{cgi}{$ip_key}{value}) // ""; + + $machines->{$machine}{network}{$network_name} = $ip_value; # On the first loop (bcn1), pull in the other information as well. if (($network eq "bcn") && ($i eq "1")) { # Get the IP. - my $ipmi_ip_key = $machine."_ipmi_ip"; - $machines->{$machine}{ipmi_ip} = defined $anvil->data->{cgi}{$ipmi_ip_key}{value} ? $anvil->data->{cgi}{$ipmi_ip_key}{value} : ""; + my $ipmi_ip_key = $machine."_ipmi_ip"; + my $ipmi_ip_value = ($parameter->{$ipmi_ip_key} // $anvil->data->{cgi}{$ipmi_ip_key}{value}) // ""; + + $machines->{$machine}{ipmi_ip} = $ipmi_ip_value; # Find the UPSes. foreach my $ups_name (sort {$a cmp $b} keys %{$anvil->data->{upses}{ups_name}}) { my $ups_key = $machine."_ups_".$ups_name; - $anvil->data->{cgi}{$ups_key}{value} = "" if not defined $anvil->data->{cgi}{$ups_key}{value}; - $machines->{$machine}{ups}{$ups_name} = $anvil->data->{cgi}{$ups_key}{value} ? "1" : "0"; + my $ups_value = ($parameter->{$ups_key} // $anvil->data->{cgi}{$ups_key}{value}) // ""; + + $machines->{$machine}{ups}{$ups_name} = $ups_value ? "1" : "0"; } # Find the Fence devices. foreach my $fence_name (sort {$a cmp $b} keys %{$anvil->data->{fences}{fence_name}}) { - my $fence_key = $machine."_fence_".$fence_name; - $anvil->data->{cgi}{$fence_key}{value} = "" if not defined $anvil->data->{cgi}{$fence_key}{value}; - $machines->{$machine}{fence}{$fence_name} = $anvil->data->{cgi}{$fence_key}{value}; + my $fence_key = $machine."_fence_".$fence_name; + my $fence_value = ($parameter->{$fence_key} // $anvil->data->{cgi}{$fence_key}{value}) // ""; + + $machines->{$machine}{fence}{$fence_name} = $fence_value; } } } From 5de6046715132180486bffb33f4312366e0c1c64 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 21 Mar 2023 01:28:45 -0400 Subject: [PATCH 31/86] fix(striker-ui-api): add install manifest builder --- .../manifest/buildManifest.ts | 277 ++++++++++++++++++ striker-ui-api/src/types/APIManifest.d.ts | 2 + 2 files changed, 279 insertions(+) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts new file mode 100644 index 00000000..ca66ba49 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts @@ -0,0 +1,277 @@ +import assert from 'assert'; +import { RequestHandler } from 'express'; + +import { + REP_INTEGER, + REP_IPV4, + REP_IPV4_CSV, + REP_PEACEFUL_STRING, + REP_UUID, +} from '../../consts/REG_EXP_PATTERNS'; + +import { sub } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stdout } from '../../shell'; + +export const buildManifest = ( + ...[request]: Parameters< + RequestHandler< + { manifestUuid?: string }, + undefined, + BuildManifestRequestBody + > + > +) => { + const { + body: { + domain: rawDomain, + hostConfig: { hosts: hostList = {} } = {}, + networkConfig: { + dnsCsv: rawDns, + mtu: rawMtu, + networks: networkList = {}, + ntpCsv: rawNtp, + } = {}, + prefix: rawPrefix, + sequence: rawSequence, + } = {}, + params: { manifestUuid: rawManifestUuid = 'new' }, + } = request; + + stdout('Begin building install manifest.'); + + const dns = sanitize(rawDns, 'string'); + assert(REP_IPV4_CSV.test(dns), `DNS must be an IPv4 CSV; got [${dns}]`); + + const domain = sanitize(rawDomain, 'string'); + assert( + REP_PEACEFUL_STRING.test(domain), + `Domain must be a peaceful string; got [${domain}]`, + ); + + const manifestUuid = sanitize(rawManifestUuid, 'string'); + assert( + REP_UUID.test(manifestUuid), + `Manifest UUID must be a UUIDv4; got [${manifestUuid}]`, + ); + + const mtu = sanitize(rawMtu, 'number'); + assert(REP_INTEGER.test(String(mtu)), `MTU must be an integer; got [${mtu}]`); + + const ntp = sanitize(rawNtp, 'string'); + assert(REP_IPV4_CSV.test(ntp), `NTP must be an IPv4 CSV; got [${ntp}]`); + + const prefix = sanitize(rawPrefix, 'string'); + assert( + REP_PEACEFUL_STRING.test(prefix), + `Prefix must be a peaceful string; got [${prefix}]`, + ); + + const sequence = sanitize(rawSequence, 'number'); + assert( + REP_INTEGER.test(String(sequence)), + `Sequence must be an integer; got [${sequence}]`, + ); + + let buildResult: [manifestUuid: string, anvilName: string] | undefined; + + const { counts: networkCountContainer, networks: networkContainer } = + Object.values(networkList).reduce<{ + counts: Record; + networks: Record; + }>( + ( + previous, + { + networkGateway: rawGateway, + networkMinIp: rawMinIp, + networkNumber: rawNetworkNumber, + networkSubnetMask: rawSubnetMask, + networkType: rawNetworkType, + }, + ) => { + const networkType = sanitize(rawNetworkType, 'string'); + assert( + REP_PEACEFUL_STRING.test(networkType), + `Network type must be a peaceful string; got [${networkType}]`, + ); + + const networkNumber = sanitize(rawNetworkNumber, 'number'); + assert( + REP_INTEGER.test(String(networkNumber)), + `Network number must be an integer; got [${networkNumber}]`, + ); + + const networkId = `${networkType}${networkNumber}`; + + const gateway = sanitize(rawGateway, 'string'); + assert( + REP_IPV4.test(gateway), + `Gateway of ${networkId} must be an IPv4; got [${gateway}]`, + ); + + const minIp = sanitize(rawMinIp, 'string'); + assert( + REP_IPV4.test(minIp), + `Minimum IP of ${networkId} must be an IPv4; got [${minIp}]`, + ); + + const subnetMask = sanitize(rawSubnetMask, 'string'); + assert( + REP_IPV4.test(subnetMask), + `Subnet mask of ${networkId} must be an IPv4; got [${subnetMask}]`, + ); + + const { counts: countContainer, networks: networkContainer } = previous; + + const countKey = `${networkType}_count`; + const countValue = countContainer[countKey] ?? 0; + + countContainer[countKey] = countValue + 1; + + const gatewayKey = `${networkId}_gateway`; + const minIpKey = `${networkId}_network`; + const subnetMaskKey = `${networkId}_subnet`; + + networkContainer[gatewayKey] = gateway; + networkContainer[minIpKey] = minIp; + networkContainer[subnetMaskKey] = subnetMask; + + return previous; + }, + { counts: {}, networks: {} }, + ); + + const hostContainer = Object.values(hostList).reduce>( + ( + previous, + { + fences, + hostNumber: rawHostNumber, + hostType: rawHostType, + ipmiIp: rawIpmiIp, + networks, + upses, + }, + ) => { + const hostType = sanitize(rawHostType, 'string'); + assert( + REP_PEACEFUL_STRING.test(hostType), + `Host type must be a peaceful string; got [${hostType}]`, + ); + + const hostNumber = sanitize(rawHostNumber, 'number'); + assert( + REP_INTEGER.test(String(hostNumber)), + `Host number must be an integer; got [${hostNumber}]`, + ); + + const hostId = `${hostType}${hostNumber}`; + + const ipmiIp = sanitize(rawIpmiIp, 'string'); + assert( + REP_IPV4.test(ipmiIp), + `IPMI IP of ${hostId} must be an IPv4; got [${ipmiIp}]`, + ); + + const ipmiIpKey = `${hostId}_ipmi_ip`; + + previous[ipmiIpKey] = ipmiIp; + + Object.values(networks).forEach( + ({ + networkIp: rawIp, + networkNumber: rawNetworkNumber, + networkType: rawNetworkType, + }) => { + const networkType = sanitize(rawNetworkType, 'string'); + assert( + REP_PEACEFUL_STRING.test(networkType), + `Network type must be a peaceful string; got [${networkType}]`, + ); + + const networkNumber = sanitize(rawNetworkNumber, 'number'); + assert( + REP_INTEGER.test(String(networkNumber)), + `Network number must be an integer; got [${networkNumber}]`, + ); + + const networkId = `${networkType}${networkNumber}`; + + const ip = sanitize(rawIp, 'string'); + assert( + REP_IPV4.test(ip), + `IP of host network ${networkId} must be an IPv4; got [${ip}]`, + ); + + const networkIpKey = `${hostId}_${networkId}_ip`; + + previous[networkIpKey] = ip; + }, + ); + + Object.values(fences).forEach( + ({ fenceName: rawFenceName, fencePort: rawPort }) => { + const fenceName = sanitize(rawFenceName, 'string'); + assert( + REP_PEACEFUL_STRING.test(fenceName), + `Fence name must be a peaceful string; got [${fenceName}]`, + ); + + const fenceKey = `${hostId}_fence_${fenceName}`; + + const port = sanitize(rawPort, 'string'); + assert( + REP_PEACEFUL_STRING.test(port), + `Port of ${fenceName} must be a peaceful string; got [${port}]`, + ); + + previous[fenceKey] = port; + }, + ); + + Object.values(upses).forEach( + ({ isUsed: rawIsUsed, upsName: rawUpsName }) => { + const upsName = sanitize(rawUpsName, 'string'); + assert( + REP_PEACEFUL_STRING.test(upsName), + `UPS name must be a peaceful string; got [${upsName}]`, + ); + + const upsKey = `${hostId}_ups_${upsName}`; + + const isUsed = sanitize(rawIsUsed, 'boolean'); + + if (isUsed) { + previous[upsKey] = 'checked'; + } + }, + ); + + return previous; + }, + {}, + ); + + try { + buildResult = sub('generate_manifest', { + subModuleName: 'Striker', + subParams: { + dns, + domain, + manifest_uuid: manifestUuid, + mtu, + ntp, + prefix, + sequence, + ...networkCountContainer, + ...networkContainer, + ...hostContainer, + }, + }).stdout; + } catch (subError) { + throw new Error(`Failed to generate manifest; CAUSE: ${subError}`); + } + + return buildResult; +}; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index 0c433240..08469aa4 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -72,6 +72,8 @@ type ManifestDetail = { sequence: number; }; +type BuildManifestRequestBody = Omit; + type ManifestTemplate = { domain: string; fences: { From b7b4e13028dfb44e45471e1aef20c96077f5ccb4 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 21 Mar 2023 17:41:57 -0400 Subject: [PATCH 32/86] fix(striker-ui-api): add POST (create) and PUT (update) for /manifest --- .../manifest/buildManifest.ts | 12 ++++--- .../manifest/createManifest.ts | 29 ++++++++++++++++ .../lib/request_handlers/manifest/index.ts | 2 ++ .../manifest/updateManifest.ts | 34 +++++++++++++++++++ striker-ui-api/src/routes/manifest.ts | 8 +++-- 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts create mode 100644 striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts diff --git a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts index ca66ba49..5a3d4908 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts @@ -73,8 +73,6 @@ export const buildManifest = ( `Sequence must be an integer; got [${sequence}]`, ); - let buildResult: [manifestUuid: string, anvilName: string] | undefined; - const { counts: networkCountContainer, networks: networkContainer } = Object.values(networkList).reduce<{ counts: Record; @@ -253,8 +251,10 @@ export const buildManifest = ( {}, ); + let result: { name: string; uuid: string } | undefined; + try { - buildResult = sub('generate_manifest', { + const [uuid, name] = sub('generate_manifest', { subModuleName: 'Striker', subParams: { dns, @@ -268,10 +268,12 @@ export const buildManifest = ( ...networkContainer, ...hostContainer, }, - }).stdout; + }).stdout as [manifestUuid: string, anvilName: string]; + + result = { name, uuid }; } catch (subError) { throw new Error(`Failed to generate manifest; CAUSE: ${subError}`); } - return buildResult; + return result; }; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts new file mode 100644 index 00000000..b2c5331d --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts @@ -0,0 +1,29 @@ +import { AssertionError } from 'assert'; +import { RequestHandler } from 'express'; + +import { buildManifest } from './buildManifest'; +import { stderr } from '../../shell'; + +export const createManifest: RequestHandler = (...handlerArgs) => { + const [, response] = handlerArgs; + + let result: Record = {}; + + try { + result = buildManifest(...handlerArgs); + } catch (buildError) { + stderr(`Failed to create new install manifest; CAUSE ${buildError}`); + + let code = 500; + + if (buildError instanceof AssertionError) { + code = 400; + } + + response.status(code).send(); + + return; + } + + response.status(201).send(result); +}; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/index.ts b/striker-ui-api/src/lib/request_handlers/manifest/index.ts index 5b9845b3..da85b0ec 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/index.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/index.ts @@ -1,4 +1,6 @@ +export * from './createManifest'; export * from './deleteManifest'; export * from './getManifest'; export * from './getManifestDetail'; export * from './getManifestTemplate'; +export * from './updateManifest'; diff --git a/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts new file mode 100644 index 00000000..728d0af7 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts @@ -0,0 +1,34 @@ +import { AssertionError } from 'assert'; +import { RequestHandler } from 'express'; + +import { buildManifest } from './buildManifest'; +import { stderr } from '../../shell'; + +export const updateManifest: RequestHandler = (...args) => { + const [request, response] = args; + const { + params: { manifestUuid }, + } = request; + + let result: Record = {}; + + try { + result = buildManifest(...args); + } catch (buildError) { + stderr( + `Failed to update install manifest ${manifestUuid}; CAUSE: ${buildError}`, + ); + + let code = 500; + + if (buildError instanceof AssertionError) { + code = 400; + } + + response.status(code).send(); + + return; + } + + response.status(200).send(result); +}; diff --git a/striker-ui-api/src/routes/manifest.ts b/striker-ui-api/src/routes/manifest.ts index 4b462bc9..6d554b43 100644 --- a/striker-ui-api/src/routes/manifest.ts +++ b/striker-ui-api/src/routes/manifest.ts @@ -1,19 +1,23 @@ import express from 'express'; import { + createManifest, deleteManifest, getManifest, getManifestDetail, getManifestTemplate, + updateManifest, } from '../lib/request_handlers/manifest'; const router = express.Router(); router + .delete('/', deleteManifest) + .delete('/manifestUuid', deleteManifest) .get('/', getManifest) .get('/template', getManifestTemplate) .get('/:manifestUUID', getManifestDetail) - .delete('/', deleteManifest) - .delete('/manifestUuid', deleteManifest); + .post('/', createManifest) + .put('/:manifestUuid', updateManifest); export default router; From a50321eb9fc024a5450c22201085313a199348cc Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 23 Mar 2023 16:26:47 -0400 Subject: [PATCH 33/86] fix(striker-ui-api): add command to run manifest --- striker-ui-api/src/lib/consts/SERVER_PATHS.ts | 1 + .../src/lib/request_handlers/command/index.ts | 1 + .../request_handlers/command/runManifest.ts | 193 ++++++++++++++++++ striker-ui-api/src/routes/command.ts | 2 + striker-ui-api/src/types/APIManifest.d.ts | 23 ++- .../src/types/GetAnvilDataFunction.d.ts | 22 ++ 6 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 striker-ui-api/src/lib/request_handlers/command/runManifest.ts diff --git a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts index f8cc8a2c..132d3015 100644 --- a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts +++ b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts @@ -19,6 +19,7 @@ const EMPTY_SERVER_PATHS: ServerPath = { 'anvil-access-module': {}, 'anvil-configure-host': {}, 'anvil-get-server-screenshot': {}, + 'anvil-join-anvil': {}, 'anvil-manage-keys': {}, 'anvil-manage-power': {}, 'anvil-provision-server': {}, diff --git a/striker-ui-api/src/lib/request_handlers/command/index.ts b/striker-ui-api/src/lib/request_handlers/command/index.ts index b42e11b0..2888de07 100644 --- a/striker-ui-api/src/lib/request_handlers/command/index.ts +++ b/striker-ui-api/src/lib/request_handlers/command/index.ts @@ -1,4 +1,5 @@ export * from './getHostSSH'; export * from './poweroffHost'; export * from './rebootHost'; +export * from './runManifest'; export * from './updateSystem'; diff --git a/striker-ui-api/src/lib/request_handlers/command/runManifest.ts b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts new file mode 100644 index 00000000..a445e934 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts @@ -0,0 +1,193 @@ +import assert from 'assert'; +import { RequestHandler } from 'express'; + +import { REP_PEACEFUL_STRING, REP_UUID } from '../../consts/REG_EXP_PATTERNS'; +import SERVER_PATHS from '../../consts/SERVER_PATHS'; + +import { getAnvilData, job, sub } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stderr } from '../../shell'; + +export const runManifest: RequestHandler< + { manifestUuid: string }, + undefined, + RunManifestRequestBody +> = (request, response) => { + const { + params: { manifestUuid }, + body: { + debug = 2, + description: rawDescription, + hosts: rawHostList = {}, + password: rawPassword, + } = {}, + } = request; + + const description = sanitize(rawDescription, 'string'); + const password = sanitize(rawPassword, 'string'); + + const hostList: ManifestExecutionHostList = {}; + + const handleAssertError = (assertError: unknown) => { + stderr( + `Failed to assert value when trying to run manifest ${manifestUuid}; CAUSE: ${assertError}`, + ); + + response.status(400).send(); + }; + + try { + assert( + REP_PEACEFUL_STRING.test(description), + `Description must be a peaceful string; got: [${description}]`, + ); + + assert( + REP_PEACEFUL_STRING.test(password), + `Password must be a peaceful string; got [${password}]`, + ); + + const uniqueList: Record = {}; + const isHostListUnique = !Object.values(rawHostList).some( + ({ hostNumber, hostType, hostUuid }) => { + const hostId = `${hostType}${hostNumber}`; + assert( + /^node[12]$/.test(hostId), + `Host ID must be "node" followed by 1 or 2; got [${hostId}]`, + ); + + assert( + REP_UUID.test(hostUuid), + `Host UUID assigned to ${hostId} must be a UUIDv4; got [${hostUuid}]`, + ); + + const isIdDuplicate = Boolean(uniqueList[hostId]); + const isUuidDuplicate = Boolean(uniqueList[hostUuid]); + + uniqueList[hostId] = true; + uniqueList[hostUuid] = true; + + hostList[hostId] = { hostNumber, hostType, hostUuid, hostId }; + + return isIdDuplicate || isUuidDuplicate; + }, + ); + + assert(isHostListUnique, `Each entry in hosts must be unique`); + } catch (assertError) { + handleAssertError(assertError); + + return; + } + + let rawHostListData: AnvilDataHostListHash | undefined; + let rawManifestListData: AnvilDataManifestListHash | undefined; + let rawSysData: AnvilDataSysHash | undefined; + + try { + ({ manifests: rawManifestListData, sys: rawSysData } = getAnvilData<{ + hosts?: AnvilDataHostListHash; + manifests?: AnvilDataManifestListHash; + sys?: AnvilDataSysHash; + }>( + { hosts: true, manifests: true, sys: true }, + { + predata: [ + ['Database->get_hosts'], + [ + 'Striker->load_manifest', + { + debug, + manifest_uuid: manifestUuid, + }, + ], + ], + }, + )); + } catch (subError) { + stderr( + `Failed to get install manifest ${manifestUuid}; CAUSE: ${subError}`, + ); + + response.status(500).send(); + + return; + } + + if (!rawHostListData || !rawManifestListData || !rawSysData) { + response.status(404).send(); + + return; + } + + const { host_uuid: hostUuidMapToData } = rawHostListData; + const { + manifest_uuid: { + [manifestUuid]: { + parsed: { name: manifestName }, + }, + }, + } = rawManifestListData; + const { hosts: { by_uuid: mapToHostNameData = {} } = {} } = rawSysData; + + const joinAnJobs: DBJobParams[] = []; + + let anParams: Record | undefined; + + try { + anParams = Object.values(hostList).reduce>( + (previous, { hostId = '', hostUuid }) => { + const hostName = mapToHostNameData[hostUuid]; + const { anvil_name: anName } = hostUuidMapToData[hostUuid]; + + assert( + anName && anName !== manifestName, + `Host ${hostName} cannot be used for ${manifestName} because it belongs to ${anName}`, + ); + + joinAnJobs.push({ + debug, + file: __filename, + job_command: SERVER_PATHS.usr.sbin['anvil-join-anvil'].self, + job_data: `as_machine=${hostId},manifest_uuid=${manifestUuid},anvil_uuid=`, + job_description: 'job_0073', + job_host_uuid: hostUuid, + job_name: `join_anvil::${hostId}`, + job_progress: 0, + job_title: 'job_0072', + }); + + previous[`anvil_${hostId}_host_uuid`] = hostUuid; + + return previous; + }, + { + anvil_description: description, + anvil_name: manifestName, + anvil_password: password, + }, + ); + } catch (assertError) { + handleAssertError(assertError); + + return; + } + + try { + const [newAnUuid] = sub('insert_or_update_anvils', { subParams: anParams }) + .stdout as [string]; + + joinAnJobs.forEach((jobParams) => { + jobParams.job_data += newAnUuid; + job(jobParams); + }); + } catch (subError) { + stderr(`Failed to record new anvil node entry; CAUSE: ${subError}`); + + response.status(500).send(); + + return; + } + + response.status(204).send(); +}; diff --git a/striker-ui-api/src/routes/command.ts b/striker-ui-api/src/routes/command.ts index 347c4f37..d547aba1 100644 --- a/striker-ui-api/src/routes/command.ts +++ b/striker-ui-api/src/routes/command.ts @@ -4,6 +4,7 @@ import { getHostSSH, poweroffHost, rebootHost, + runManifest, updateSystem, } from '../lib/request_handlers/command'; @@ -13,6 +14,7 @@ router .put('/inquire-host', getHostSSH) .put('/poweroff-host', poweroffHost) .put('/reboot-host', rebootHost) + .put('/run-manifest/:manifestUuid', runManifest) .put('/update-system', updateSystem); export default router; diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index 08469aa4..fc1e5693 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -72,7 +72,19 @@ type ManifestDetail = { sequence: number; }; -type BuildManifestRequestBody = Omit; +type ManifestExecutionHost = { + anName?: string; + anUuid?: string; + hostId?: string; + hostName?: string; + hostNumber: number; + hostType: string; + hostUuid: string; +}; + +type ManifestExecutionHostList = { + [hostId: string]: ManifestExecutionHost; +}; type ManifestTemplate = { domain: string; @@ -91,3 +103,12 @@ type ManifestTemplate = { }; }; }; + +type BuildManifestRequestBody = Omit; + +type RunManifestRequestBody = { + debug?: number; + description: string; + hosts: ManifestExecutionHostList; + password: string; +}; diff --git a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts index d82d05b6..0d8c2a0c 100644 --- a/striker-ui-api/src/types/GetAnvilDataFunction.d.ts +++ b/striker-ui-api/src/types/GetAnvilDataFunction.d.ts @@ -13,6 +13,21 @@ type AnvilDataDatabaseHash = { }; }; +type AnvilDataHostListHash = { + host_uuid: { + [hostUuid: string]: { + anvil_name?: string; + anvil_uuid?: string; + host_ipmi: string; + host_key: string; + host_name: string; + host_status: string; + host_type: string; + short_host_name: string; + }; + }; +}; + type AnvilDataManifestListHash = { manifest_uuid: { [manifestUUID: string]: { @@ -81,6 +96,13 @@ type AnvilDataManifestListHash = { } >; +type AnvilDataSysHash = { + hosts?: { + by_uuid: { [hostUuid: string]: string }; + by_name: { [hostName: string]: string }; + }; +}; + type AnvilDataUPSHash = { [upsName: string]: { agent: string; From 17a011e76837c9662f817941b90377d9ec3d1315 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 14:17:41 -0400 Subject: [PATCH 34/86] fix(striker-ui): correct types for manifest management --- striker-ui/types/APIManifest.d.ts | 26 ++++++++++++++++++++ striker-ui/types/ManageManifest.d.ts | 36 ++++++++++++++++++---------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/striker-ui/types/APIManifest.d.ts b/striker-ui/types/APIManifest.d.ts index 85a07e86..0022f5aa 100644 --- a/striker-ui/types/APIManifest.d.ts +++ b/striker-ui/types/APIManifest.d.ts @@ -6,3 +6,29 @@ type APIManifestOverview = { type APIManifestOverviewList = { [manifestUUID: string]: APIManifestOverview; }; + +type APIManifestTemplateFence = { + fenceName: string; + fenceUUID: string; +}; + +type APIManifestTemplateUps = { + upsName: string; + upsUUID: string; +}; + +type APIManifestTemplateFenceList = { + [fenceUuid: string]: APIManifestTemplateFence; +}; + +type APIManifestTemplateUpsList = { + [upsUuid: string]: APIManifestTemplateUps; +}; + +type APIManifestTemplate = { + domain: string; + fences: APIManifestTemplateFenceList; + prefix: string; + sequence: number; + upses: APIManifestTemplateUpsList; +}; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 8aa911ec..80dd3535 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -23,6 +23,13 @@ type ManifestNetworkList = { [networkId: string]: ManifestNetwork; }; +type ManifestHostFenceList = { + [fenceId: string]: { + fenceName: string; + fencePort: string; + }; +}; + type ManifestHostNetworkList = { [networkId: string]: { networkIp: string; @@ -31,22 +38,19 @@ type ManifestHostNetworkList = { }; }; -type ManifestHost = { - fences?: { - [fenceId: string]: { - fenceName: string; - fencePort: number; - }; +type ManifestHostUpsList = { + [upsId: string]: { + isUsed: boolean; + upsName: string; }; +}; + +type ManifestHost = { + fences?: ManifestHostFenceList; hostNumber: number; hostType: string; networks?: ManifestHostNetworkList; - upses?: { - [upsId: string]: { - isPowerHost: boolean; - upsName: string; - }; - }; + upses?: ManifestHostUpsList; }; type ManifestHostList = { @@ -127,6 +131,8 @@ type AnvilNetworkConfigInputGroupProps = }; type AnvilHostConfigInputGroupOptionalProps = { + knownFences?: APIManifestTemplateFenceList; + knownUpses?: APIManifestTemplateUpsList; previous?: { hosts?: ManifestHostList; }; @@ -138,8 +144,12 @@ type AnvilHostConfigInputGroupProps = networkListEntries: Array<[string, ManifestNetwork]>; }; -type AddManifestInputGroupOptionalProps = { +type AddManifestInputGroupOptionalProps = Pick< + AnvilHostConfigInputGroupOptionalProps, + 'knownFences' | 'knownUpses' +> & { previous?: { + anId?: AnvilIdInputGroupOptionalProps['previous']; networkConfig?: AnvilNetworkConfigInputGroupOptionalProps['previous']; hostConfig?: AnvilHostConfigInputGroupOptionalProps['previous']; }; From c35b2d7969b066759b01c9055ba303ea4e80f0d5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 14:29:38 -0400 Subject: [PATCH 35/86] fix(striker-ui): rearrange fence and ups inputs for host in manage manifest --- .../ManageManifest/AnvilHostInputGroup.tsx | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx index fd955138..91903303 100644 --- a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx @@ -18,6 +18,8 @@ const INPUT_ID_PREFIX_ANVIL_HOST = 'anvil-host-input'; const INPUT_CELL_ID_PREFIX_ANVIL_HOST = `${INPUT_ID_PREFIX_ANVIL_HOST}-cell`; +const GRID_SPACING = '1em'; + const AnvilHostInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, @@ -36,14 +38,25 @@ const AnvilHostInputGroup = ({ () => Object.entries(fenceList), [fenceList], ); - const networkListEntries = useMemo( () => Object.entries(networkList), [networkList], ); - const upsListEntries = useMemo(() => Object.entries(upsList), [upsList]); + const isShowFenceListGrid = useMemo( + () => Boolean(fenceListEntries.length), + [fenceListEntries.length], + ); + const isShowUpsListGrid = useMemo( + () => Boolean(upsListEntries.length), + [upsListEntries.length], + ); + const isShowFenceAndUpsListGrid = useMemo( + () => isShowFenceListGrid || isShowUpsListGrid, + [isShowFenceListGrid, isShowUpsListGrid], + ); + const fenceListGridLayout = useMemo( () => fenceListEntries.reduce( @@ -53,7 +66,7 @@ const AnvilHostInputGroup = ({ const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; - const inputLabel = fenceName; + const inputLabel = `Port on ${fenceName}`; setMsgSetter(inputId); @@ -63,7 +76,7 @@ const AnvilHostInputGroup = ({ input={ } @@ -81,7 +94,6 @@ const AnvilHostInputGroup = ({ )} onFirstRender={buildInputFirstRenderFunction(inputId)} required - valueType="number" /> ), }; @@ -156,7 +168,7 @@ const AnvilHostInputGroup = ({ const upsListGridLayout = useMemo( () => upsListEntries.reduce( - (previous, [upsId, { isPowerHost, upsName }]) => { + (previous, [upsId, { isUsed, upsName }]) => { const idPostfix = `${upsId}-power-host`; const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; @@ -168,9 +180,10 @@ const AnvilHostInputGroup = ({ } valueType="boolean" @@ -195,17 +208,32 @@ const AnvilHostInputGroup = ({ - {Boolean(fenceListEntries.length || upsListEntries.length) && ( + {isShowFenceAndUpsListGrid && ( + ), + }, + 'an-host-ups-input-group': { + children: ( + + ), + }, }} - spacing="1em" + spacing={GRID_SPACING} /> )} From 89c7f6b980b5ab5bac6fe50cf6021a55e5fd43bb Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 14:51:18 -0400 Subject: [PATCH 36/86] fix(striker-ui): build fence and ups list from manifest template --- .../AnvilHostConfigInputGroup.tsx | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx index d755395c..083fe36e 100644 --- a/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx @@ -10,29 +10,19 @@ const INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG = `${INPUT_GROUP_ID_PREFIX_AN const DEFAULT_HOST_LIST: ManifestHostList = { node1: { - fences: { - fence1: { fenceName: 'ex_pdu01', fencePort: 0 }, - fence2: { fenceName: 'ex_pdu02', fencePort: 0 }, - }, hostNumber: 1, hostType: 'node', - upses: { - ups1: { isPowerHost: true, upsName: 'ex_ups01' }, - ups2: { isPowerHost: false, upsName: 'ex_ups02' }, - }, }, node2: { hostNumber: 2, hostType: 'node', }, - dr1: { - hostNumber: 1, - hostType: 'dr', - }, }; const AnvilHostConfigInputGroup = ({ formUtils, + knownFences = {}, + knownUpses = {}, networkListEntries, previous: { hosts: previousHostList = DEFAULT_HOST_LIST } = {}, }: AnvilHostConfigInputGroupProps): ReactElement => { @@ -40,6 +30,14 @@ const AnvilHostConfigInputGroup = ({ () => Object.entries(previousHostList), [previousHostList], ); + const knownFenceListValues = useMemo( + () => Object.values(knownFences), + [knownFences], + ); + const knownUpsListValues = useMemo( + () => Object.values(knownUpses), + [knownUpses], + ); const hostNetworkList = useMemo( () => @@ -63,13 +61,41 @@ const AnvilHostConfigInputGroup = ({ hostListEntries.reduce( (previous, [hostId, previousHostArgs]) => { const { + fences: previousFenceList = {}, hostNumber, hostType, networks = hostNetworkList, + upses: previousUpsList = {}, }: ManifestHost = previousHostArgs; - const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${hostId}`; + const fences = knownFenceListValues.reduce( + (fenceList, { fenceName }) => { + const { fencePort = '' } = previousFenceList[fenceName] ?? {}; + fenceList[fenceName] = { + fenceName, + fencePort, + }; + + return fenceList; + }, + {}, + ); + const upses = knownUpsListValues.reduce( + (upsList, { upsName }) => { + const { isUsed = true } = previousUpsList[upsName] ?? {}; + + upsList[upsName] = { + isUsed, + upsName, + }; + + return upsList; + }, + {}, + ); + + const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${hostId}`; const hostLabel = `${hostType} ${hostNumber}`; previous[cellId] = { @@ -77,7 +103,7 @@ const AnvilHostConfigInputGroup = ({ ), md: 3, @@ -88,7 +114,13 @@ const AnvilHostConfigInputGroup = ({ }, {}, ), - [formUtils, hostListEntries, hostNetworkList], + [ + formUtils, + hostListEntries, + hostNetworkList, + knownFenceListValues, + knownUpsListValues, + ], ); return ( From 85dde72db7c1274979b5360e21562bc97d11f403 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 14:52:26 -0400 Subject: [PATCH 37/86] fix(striker-ui): add default DNS CSV --- .../ManageManifest/AnvilNetworkConfigInputGroup.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx index 63ea5d8a..b5554e16 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx @@ -23,6 +23,8 @@ const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; +const DEFAULT_DNS_CSV = '8.8.8.8, 8.8.4.4'; + const NETWORK_TYPE_ENTRIES = Object.entries(NETWORK_TYPES); const assertIfn = (type: string) => type === 'ifn'; @@ -39,7 +41,7 @@ const AnvilNetworkConfigInputGroup = < formUtils, networkListEntries, previous: { - dnsCsv: previousDnsCsv, + dnsCsv: previousDnsCsv = DEFAULT_DNS_CSV, mtu: previousMtu, ntpCsv: previousNtpCsv, } = {}, From 952d95feac19c894f01cfb84a5dd84fef3b037fe Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 20:55:52 -0400 Subject: [PATCH 38/86] fix(striker-ui): add general loading prop to ConfirmDialog --- striker-ui/components/ConfirmDialog.tsx | 30 ++++++++++++++++++++----- striker-ui/types/ConfirmDialog.d.ts | 1 + 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index 5259db49..e2a587f9 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -43,6 +43,7 @@ const ConfirmDialog = forwardRef< ...restDialogProps } = {}, formContent: isFormContent, + loading: isLoading = false, loadingAction: isLoadingAction = false, onActionAppend, onCancelAppend, @@ -208,6 +209,29 @@ const ConfirmDialog = forwardRef< [isScrollContent, scrollBoxSx], ); + const contentAreaElement = useMemo( + () => + isLoading ? ( + + ) : ( + <> + + {contentElement} + + {preActionArea} + {actionAreaElement} + + ), + [ + actionAreaElement, + combinedScrollBoxSx, + contentElement, + isLoading, + preActionArea, + restScrollBoxProps, + ], + ); + useImperativeHandle( ref, () => ({ @@ -232,11 +256,7 @@ const ConfirmDialog = forwardRef< onSubmit={contentContainerSubmitEventHandler} {...contentContainerProps} > - - {contentElement} - - {preActionArea} - {actionAreaElement} + {contentAreaElement} ); diff --git a/striker-ui/types/ConfirmDialog.d.ts b/striker-ui/types/ConfirmDialog.d.ts index 24e94d11..0be940a8 100644 --- a/striker-ui/types/ConfirmDialog.d.ts +++ b/striker-ui/types/ConfirmDialog.d.ts @@ -4,6 +4,7 @@ type ConfirmDialogOptionalProps = { contentContainerProps?: import('../components/FlexBox').FlexBoxProps; dialogProps?: Partial; formContent?: boolean; + loading?: boolean; loadingAction?: boolean; onActionAppend?: ContainedButtonProps['onClick']; onProceedAppend?: ContainedButtonProps['onClick']; From fc016ea719f035b464294996790ad53f5c23b2df Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 24 Mar 2023 23:11:25 -0400 Subject: [PATCH 39/86] fix(striker-ui): expose disableProceed in ConfirmDialog --- striker-ui/components/ConfirmDialog.tsx | 10 ++++++++-- striker-ui/types/ConfirmDialog.d.ts | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index e2a587f9..5252b248 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -42,6 +42,7 @@ const ConfirmDialog = forwardRef< PaperProps: paperProps = {}, ...restDialogProps } = {}, + disableProceed: isDisableProceed, formContent: isFormContent, loading: isLoading = false, loadingAction: isLoadingAction = false, @@ -60,8 +61,11 @@ const ConfirmDialog = forwardRef< ref, ) => { const { sx: paperSx, ...restPaperProps } = paperProps; - const { sx: proceedButtonSx, ...restProceedButtonProps } = - proceedButtonProps; + const { + disabled: proceedButtonDisabled = isDisableProceed, + sx: proceedButtonSx, + ...restProceedButtonProps + } = proceedButtonProps; const [isOpen, setIsOpen] = useState(openInitially); @@ -142,6 +146,7 @@ const ConfirmDialog = forwardRef< const proceedButtonElement = useMemo( () => ( ; + disableProceed?: boolean; formContent?: boolean; loading?: boolean; loadingAction?: boolean; From 18533ba0c8ad8b8a7d47b4bb9fc8606bbfe085c7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 25 Mar 2023 01:16:11 -0400 Subject: [PATCH 40/86] fix(striker-ui): move scroll style offsets to ConfirmDialog --- striker-ui/components/ConfirmDialog.tsx | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index 5252b248..319bf313 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -203,17 +203,30 @@ const ConfirmDialog = forwardRef< ), [titleText], ); - const combinedScrollBoxSx = useMemo | undefined>( - () => - isScrollContent - ? { - maxHeight: '60vh', - overflowY: 'scroll', - ...scrollBoxSx, - } - : undefined, - [isScrollContent, scrollBoxSx], - ); + const combinedScrollBoxSx = useMemo(() => { + let result: SxProps | undefined; + + if (isScrollContent) { + let overflowX: 'hidden' | undefined; + let paddingTop: string | undefined; + + if (isFormContent) { + overflowX = 'hidden'; + paddingTop = '.6em'; + } + + result = { + maxHeight: '60vh', + overflowX, + overflowY: 'scroll', + paddingRight: '.4em', + paddingTop, + ...scrollBoxSx, + }; + } + + return result; + }, [isFormContent, isScrollContent, scrollBoxSx]); const contentAreaElement = useMemo( () => From 6aaa4f62377d1f31281a2e8bae3a8b8526cc8c28 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 25 Mar 2023 01:26:42 -0400 Subject: [PATCH 41/86] fix(striker-ui): add edit manifest form --- .../ManageManifest/AddManifestInputGroup.tsx | 17 +- .../AnvilHostConfigInputGroup.tsx | 45 ++--- .../ManageManifest/AnvilIdInputGroup.tsx | 6 +- .../ManageManifest/EditManifestInputGroup.tsx | 39 +++++ .../ManageManifest/ManageManifestPanel.tsx | 161 +++++++++++++++--- striker-ui/types/APIManifest.d.ts | 7 + striker-ui/types/ManageManifest.d.ts | 58 ++++--- 7 files changed, 245 insertions(+), 88 deletions(-) create mode 100644 striker-ui/components/ManageManifest/EditManifestInputGroup.tsx diff --git a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx index 774c9c11..7f4c68c5 100644 --- a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx @@ -15,15 +15,15 @@ import FlexBox from '../FlexBox'; const DEFAULT_NETWORK_LIST: ManifestNetworkList = { bcn1: { - networkMinIp: '', + networkMinIp: '10.201.0.0', networkNumber: 1, - networkSubnetMask: '', + networkSubnetMask: '255.255.0.0', networkType: 'bcn', }, sn1: { - networkMinIp: '', + networkMinIp: '10.101.0.0', networkNumber: 1, - networkSubnetMask: '', + networkSubnetMask: '255.255.0.0', networkType: 'sn', }, ifn1: { @@ -46,9 +46,12 @@ const AddManifestInputGroup = < }, >({ formUtils, + knownFences, + knownUpses, previous: { - hostConfig: previousHostConfig = {}, + hostConfig: previousHostConfig, networkConfig: previousNetworkConfig = {}, + ...previousAnId } = {}, }: AddManifestInputGroupProps): ReactElement => { const { networks: previousNetworkList = DEFAULT_NETWORK_LIST } = @@ -64,7 +67,7 @@ const AddManifestInputGroup = < return ( - + diff --git a/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx index 083fe36e..9a27583a 100644 --- a/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilHostConfigInputGroup.tsx @@ -39,23 +39,6 @@ const AnvilHostConfigInputGroup = ({ [knownUpses], ); - const hostNetworkList = useMemo( - () => - networkListEntries.reduce( - (previous, [networkId, { networkNumber, networkType }]) => { - previous[networkId] = { - networkIp: '', - networkNumber, - networkType, - }; - - return previous; - }, - {}, - ), - [networkListEntries], - ); - const hostListGridLayout = useMemo( () => hostListEntries.reduce( @@ -64,7 +47,7 @@ const AnvilHostConfigInputGroup = ({ fences: previousFenceList = {}, hostNumber, hostType, - networks = hostNetworkList, + networks: previousNetworkList = {}, upses: previousUpsList = {}, }: ManifestHost = previousHostArgs; @@ -72,23 +55,31 @@ const AnvilHostConfigInputGroup = ({ (fenceList, { fenceName }) => { const { fencePort = '' } = previousFenceList[fenceName] ?? {}; - fenceList[fenceName] = { - fenceName, - fencePort, - }; + fenceList[fenceName] = { fenceName, fencePort }; return fenceList; }, {}, ); + const networks = networkListEntries.reduce( + (networkList, [networkId, { networkNumber, networkType }]) => { + const { networkIp = '' } = previousNetworkList[networkId] ?? {}; + + networkList[networkId] = { + networkIp, + networkNumber, + networkType, + }; + + return networkList; + }, + {}, + ); const upses = knownUpsListValues.reduce( (upsList, { upsName }) => { const { isUsed = true } = previousUpsList[upsName] ?? {}; - upsList[upsName] = { - isUsed, - upsName, - }; + upsList[upsName] = { isUsed, upsName }; return upsList; }, @@ -117,9 +108,9 @@ const AnvilHostConfigInputGroup = ({ [ formUtils, hostListEntries, - hostNetworkList, knownFenceListValues, knownUpsListValues, + networkListEntries, ], ); diff --git a/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx index e7e4b5f0..3457db5c 100644 --- a/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx @@ -30,9 +30,9 @@ const AnvilIdInputGroup = < msgSetters, }, previous: { - anvilIdDomain: previousDomain, - anvilIdPrefix: previousPrefix, - anvilIdSequence: previousSequence, + domain: previousDomain, + prefix: previousPrefix, + sequence: previousSequence, } = {}, }: AnvilIdInputGroupProps): ReactElement => ( ({ + formUtils, + knownFences, + knownUpses, + previous, +}: EditManifestInputGroupProps): ReactElement => ( + +); + +export default EditManifestInputGroup; diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 838e9bc1..14b38f76 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -1,5 +1,5 @@ import { PlayCircle } from '@mui/icons-material'; -import { FC, useMemo, useRef, useState } from 'react'; +import { FC, useCallback, useMemo, useRef, useState } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; @@ -14,9 +14,12 @@ import { INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, } from './AnvilNetworkConfigInputGroup'; +import api from '../../lib/api'; import ConfirmDialog from '../ConfirmDialog'; +import EditManifestInputGroup from './EditManifestInputGroup'; import FlexBox from '../FlexBox'; import FormDialog from '../FormDialog'; +import handleAPIError from '../../lib/handleAPIError'; import IconButton from '../IconButton'; import List from '../List'; import { MessageGroupForwardedRefContent } from '../MessageGroup'; @@ -26,16 +29,32 @@ import Spinner from '../Spinner'; import { BodyText, HeaderText } from '../Text'; import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; import useFormUtils from '../../hooks/useFormUtils'; +import useIsFirstRender from '../../hooks/useIsFirstRender'; +import useProtectedState from '../../hooks/useProtectedState'; const ManageManifestPanel: FC = () => { + const isFirstRender = useIsFirstRender(); + const confirmDialogRef = useRef({}); - const formDialogRef = useRef({}); + const addManifestFormDialogRef = useRef({}); + const editManifestFormDialogRef = useRef( + {}, + ); const messageGroupRef = useRef({}); const [confirmDialogProps] = useConfirmDialogProps(); - const [formDialogProps, setFormDialogProps] = useConfirmDialogProps(); const [isEditManifests, setIsEditManifests] = useState(false); + const [isLoadingManifestDetail, setIsLoadingManifestDetail] = + useProtectedState(true); + const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] = + useState(true); + const [manifestDetail, setManifestDetail] = useProtectedState< + APIManifestDetail | undefined + >(undefined); + const [manifestTemplate, setManifestTemplate] = useProtectedState< + APIManifestTemplate | undefined + >(undefined); const { data: manifestOverviews, isLoading: isLoadingManifestOverviews } = periodicFetch(`${API_BASE_URL}/manifest`, { @@ -55,13 +74,79 @@ const ManageManifestPanel: FC = () => { ); const { isFormInvalid } = formUtils; - const addAnvilManifestFormDialogProps = useMemo( - () => ({ + const addManifestFormDialogProps = useMemo(() => { + let domain: string | undefined; + let prefix: string | undefined; + let sequence: number | undefined; + let fences: APIManifestTemplateFenceList | undefined; + let upses: APIManifestTemplateUpsList | undefined; + + if (manifestTemplate) { + ({ domain, fences, prefix, sequence, upses } = manifestTemplate); + } + + return { actionProceedText: 'Add', - content: , - titleText: 'Add a Anvil! manifest', - }), - [formUtils], + content: ( + + ), + titleText: 'Add an install manifest', + }; + }, [formUtils, manifestTemplate]); + + const editManifestFormDialogProps = useMemo(() => { + let fences: APIManifestTemplateFenceList | undefined; + let manifestName: string | undefined; + let upses: APIManifestTemplateUpsList | undefined; + + if (manifestTemplate) { + ({ fences, upses } = manifestTemplate); + } + + if (manifestDetail) { + ({ name: manifestName } = manifestDetail); + } + + return { + actionProceedText: 'Edit', + content: ( + + ), + loading: isLoadingManifestDetail, + titleText: `Update install manifest ${manifestName}`, + }; + }, [formUtils, isLoadingManifestDetail, manifestDetail, manifestTemplate]); + + const getManifestDetail = useCallback( + (manifestUuid: string, finallyAppend?: () => void) => { + setIsLoadingManifestDetail(true); + + api + .get(`manifest/${manifestUuid}`) + .then(({ data }) => { + data.uuid = manifestUuid; + + setManifestDetail(data); + }) + .catch((error) => { + handleAPIError(error); + }) + .finally(() => { + setIsLoadingManifestDetail(false); + finallyAppend?.call(null); + }); + }, + [setIsLoadingManifestDetail, setManifestDetail], ); const listElement = useMemo( @@ -74,12 +159,19 @@ const ManageManifestPanel: FC = () => { listEmpty="No manifest(s) registered." listItems={manifestOverviews} onAdd={() => { - setFormDialogProps(addAnvilManifestFormDialogProps); - formDialogRef.current.setOpen?.call(null, true); + addManifestFormDialogRef.current.setOpen?.call(null, true); }} onEdit={() => { setIsEditManifests((previous) => !previous); }} + onItemClick={({ manifestName, manifestUUID }) => { + setManifestDetail({ + name: manifestName, + uuid: manifestUUID, + } as APIManifestDetail); + editManifestFormDialogRef.current.setOpen?.call(null, true); + getManifestDetail(manifestUUID); + }} renderListItem={(manifestUUID, { manifestName }) => ( @@ -90,19 +182,33 @@ const ManageManifestPanel: FC = () => { )} /> ), - [ - addAnvilManifestFormDialogProps, - isEditManifests, - manifestOverviews, - setFormDialogProps, - ], + [getManifestDetail, isEditManifests, manifestOverviews, setManifestDetail], ); const panelContent = useMemo( - () => (isLoadingManifestOverviews ? : listElement), - [isLoadingManifestOverviews, listElement], + () => + isLoadingManifestTemplate || isLoadingManifestOverviews ? ( + + ) : ( + listElement + ), + [isLoadingManifestOverviews, isLoadingManifestTemplate, listElement], ); + if (isFirstRender) { + api + .get('/manifest/template') + .then(({ data }) => { + setManifestTemplate(data); + }) + .catch((error) => { + handleAPIError(error); + }) + .finally(() => { + setIsLoadingManifestTemplate(false); + }); + } + return ( <> @@ -112,14 +218,15 @@ const ManageManifestPanel: FC = () => { {panelContent} + diff --git a/striker-ui/types/APIManifest.d.ts b/striker-ui/types/APIManifest.d.ts index 0022f5aa..6ef80562 100644 --- a/striker-ui/types/APIManifest.d.ts +++ b/striker-ui/types/APIManifest.d.ts @@ -7,6 +7,13 @@ type APIManifestOverviewList = { [manifestUUID: string]: APIManifestOverview; }; +type APIManifestDetail = ManifestAnId & { + hostConfig: ManifestHostConfig; + name: string; + networkConfig: ManifestNetworkConfig; + uuid?: string; +}; + type APIManifestTemplateFence = { fenceName: string; fenceUUID: string; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 80dd3535..0acc3dd8 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -1,16 +1,9 @@ -type AnvilIdInputGroupOptionalProps = { - previous?: { - anvilIdPrefix?: string; - anvilIdDomain?: string; - anvilIdSequence?: number; - }; +type ManifestAnId = { + domain: string; + prefix: string; + sequence: number; }; -type AnvilIdInputGroupProps = - AnvilIdInputGroupOptionalProps & { - formUtils: FormUtils; - }; - type ManifestNetwork = { networkGateway?: string; networkMinIp: string; @@ -23,6 +16,14 @@ type ManifestNetworkList = { [networkId: string]: ManifestNetwork; }; +type ManifestNetworkConfig = { + dnsCsv: string; + /** Max Transmission Unit (MTU); unit: bytes */ + mtu: number; + networks: ManifestNetworkList; + ntpCsv: string; +}; + type ManifestHostFenceList = { [fenceId: string]: { fenceName: string; @@ -57,6 +58,19 @@ type ManifestHostList = { [hostId: string]: ManifestHost; }; +type ManifestHostConfig = { + hosts: ManifestHostList; +}; + +type AnvilIdInputGroupOptionalProps = { + previous?: Partial; +}; + +type AnvilIdInputGroupProps = + AnvilIdInputGroupOptionalProps & { + formUtils: FormUtils; + }; + type AnvilNetworkEventHandlerPreviousArgs = { networkId: string; } & Pick; @@ -112,13 +126,7 @@ type AnvilHostInputGroupProps = }; type AnvilNetworkConfigInputGroupOptionalProps = { - previous?: { - dnsCsv?: string; - /** Max Transmission Unit (MTU); unit: bytes */ - mtu?: number; - networks?: ManifestNetworkList; - ntpCsv?: string; - }; + previous?: Partial; }; type AnvilNetworkConfigInputGroupProps = @@ -133,9 +141,7 @@ type AnvilNetworkConfigInputGroupProps = type AnvilHostConfigInputGroupOptionalProps = { knownFences?: APIManifestTemplateFenceList; knownUpses?: APIManifestTemplateUpsList; - previous?: { - hosts?: ManifestHostList; - }; + previous?: Partial; }; type AnvilHostConfigInputGroupProps = @@ -148,10 +154,9 @@ type AddManifestInputGroupOptionalProps = Pick< AnvilHostConfigInputGroupOptionalProps, 'knownFences' | 'knownUpses' > & { - previous?: { - anId?: AnvilIdInputGroupOptionalProps['previous']; - networkConfig?: AnvilNetworkConfigInputGroupOptionalProps['previous']; - hostConfig?: AnvilHostConfigInputGroupOptionalProps['previous']; + previous?: Partial & { + hostConfig?: Partial; + networkConfig?: Partial; }; }; @@ -159,3 +164,6 @@ type AddManifestInputGroupProps = AddManifestInputGroupOptionalProps & { formUtils: FormUtils; }; + +type EditManifestInputGroupProps = + AddManifestInputGroupProps; From f72a348fb527bdf7055d317e6fe1a0f2713e881c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 27 Mar 2023 22:02:36 -0400 Subject: [PATCH 42/86] fix(striker-ui): add play preset to IconButton --- striker-ui/components/IconButton/IconButton.tsx | 6 ++++++ striker-ui/types/IconButton.d.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/striker-ui/components/IconButton/IconButton.tsx b/striker-ui/components/IconButton/IconButton.tsx index 1e289aad..9d73871c 100644 --- a/striker-ui/components/IconButton/IconButton.tsx +++ b/striker-ui/components/IconButton/IconButton.tsx @@ -3,6 +3,7 @@ import { Close as MUICloseIcon, Done as MUIDoneIcon, Edit as MUIEditIcon, + PlayCircle as MUIPlayCircleIcon, Visibility as MUIVisibilityIcon, VisibilityOff as MUIVisibilityOffIcon, } from '@mui/icons-material'; @@ -55,6 +56,10 @@ const MAP_TO_EDIT_ICON: IconButtonMapToStateIconBundle = { true: { iconType: MUIDoneIcon, iconProps: { sx: { color: BLUE } } }, }; +const MAP_TO_PLAY_ICON: IconButtonMapToStateIconBundle = { + none: { iconType: MUIPlayCircleIcon }, +}; + const MAP_TO_VISIBILITY_ICON: IconButtonMapToStateIconBundle = { false: { iconType: MUIVisibilityIcon }, true: { iconType: MUIVisibilityOffIcon }, @@ -67,6 +72,7 @@ const MAP_TO_MAP_PRESET: Record< add: MAP_TO_ADD_ICON, close: MAP_TO_CLOSE_ICON, edit: MAP_TO_EDIT_ICON, + play: MAP_TO_PLAY_ICON, visibility: MAP_TO_VISIBILITY_ICON, }; diff --git a/striker-ui/types/IconButton.d.ts b/striker-ui/types/IconButton.d.ts index 93c46f53..208fae3a 100644 --- a/striker-ui/types/IconButton.d.ts +++ b/striker-ui/types/IconButton.d.ts @@ -4,6 +4,7 @@ type IconButtonPresetMapToStateIconBundle = | 'add' | 'close' | 'edit' + | 'play' | 'visibility'; type IconButtonStateIconBundle = { From f7dea00ae6c79c1c4d8bb70fa2d9270f8a427fd3 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 27 Mar 2023 23:06:58 -0400 Subject: [PATCH 43/86] fix(striker-ui-api): include host name in manifest detail --- .../request_handlers/manifest/getManifestDetail.ts | 12 +++++++++++- striker-ui-api/src/types/APIManifest.d.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts index 4291394c..fb858e2e 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -132,7 +132,16 @@ export const getManifestDetail: RequestHandler = (request, response) => { .reduce( ( previous, - [hostId, { fence = {}, ipmi_ip: ipmiIp, network, ups = {} }], + [ + hostId, + { + fence = {}, + ipmi_ip: ipmiIp, + name: hostName, + network, + ups = {}, + }, + ], ) => { const { name: hostType, number: hostNumber } = getEntityParts(hostId); @@ -160,6 +169,7 @@ export const getManifestDetail: RequestHandler = (request, response) => { }, {}, ), + hostName, hostNumber, hostType, ipmiIp, diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index fc1e5693..f997d72b 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -48,6 +48,7 @@ type ManifestDetailUpsList = { type ManifestDetailHostList = { [hostId: string]: { fences: ManifestDetailFenceList; + hostName: string; hostNumber: number; hostType: string; ipmiIp: string; From 2ca1277b3f628f597839441e96c4bcdad7c2e865 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 27 Mar 2023 23:15:56 -0400 Subject: [PATCH 44/86] fix(striker-ui-api): only include node(s) in manifest detail --- .../src/lib/request_handlers/manifest/getManifestDetail.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts index fb858e2e..0da929a9 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts @@ -148,6 +148,11 @@ export const getManifestDetail: RequestHandler = (request, response) => { stdout(`host=${hostType},n=${hostNumber}`); + // Only include node-type host(s). + if (hostType !== 'node') { + return previous; + } + previous[hostId] = { fences: Object.entries(fence) .sort(handleSortEntries) From c0189cba8dfa3ef0170080e12526c44957771f29 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 01:05:02 -0400 Subject: [PATCH 45/86] fix(striker-ui): add run manifest form --- .../ManageManifest/ManageManifestPanel.tsx | 159 +++++-- .../ManageManifest/RunManifestInputGroup.tsx | 404 ++++++++++++++++++ striker-ui/types/ManageManifest.d.ts | 8 + 3 files changed, 532 insertions(+), 39 deletions(-) create mode 100644 striker-ui/components/ManageManifest/RunManifestInputGroup.tsx diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 14b38f76..54f8056b 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -1,4 +1,3 @@ -import { PlayCircle } from '@mui/icons-material'; import { FC, useCallback, useMemo, useRef, useState } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; @@ -25,6 +24,11 @@ import List from '../List'; import { MessageGroupForwardedRefContent } from '../MessageGroup'; import { Panel, PanelHeader } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; +import RunManifestInputGroup, { + INPUT_ID_AN_CONFIRM_PASSWORD, + INPUT_ID_AN_DESCRIPTION, + INPUT_ID_AN_PASSWORD, +} from './RunManifestInputGroup'; import Spinner from '../Spinner'; import { BodyText, HeaderText } from '../Text'; import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; @@ -40,11 +44,17 @@ const ManageManifestPanel: FC = () => { const editManifestFormDialogRef = useRef( {}, ); + const runManifestFormDialogRef = useRef({}); const messageGroupRef = useRef({}); const [confirmDialogProps] = useConfirmDialogProps(); + const [hostOverviews, setHostOverviews] = useProtectedState< + APIHostOverviewList | undefined + >(undefined); const [isEditManifests, setIsEditManifests] = useState(false); + const [isLoadingHostOverviews, setIsLoadingHostOverviews] = + useState(true); const [isLoadingManifestDetail, setIsLoadingManifestDetail] = useProtectedState(true); const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] = @@ -74,58 +84,94 @@ const ManageManifestPanel: FC = () => { ); const { isFormInvalid } = formUtils; - const addManifestFormDialogProps = useMemo(() => { - let domain: string | undefined; - let prefix: string | undefined; - let sequence: number | undefined; - let fences: APIManifestTemplateFenceList | undefined; - let upses: APIManifestTemplateUpsList | undefined; + const runFormUtils = useFormUtils( + [ + INPUT_ID_AN_CONFIRM_PASSWORD, + INPUT_ID_AN_DESCRIPTION, + INPUT_ID_AN_PASSWORD, + ], + messageGroupRef, + ); + const { isFormInvalid: isRunFormInvalid } = runFormUtils; - if (manifestTemplate) { - ({ domain, fences, prefix, sequence, upses } = manifestTemplate); - } + const { + domain, + name: anName, + prefix, + sequence, + } = useMemo>( + () => manifestDetail ?? {}, + [manifestDetail], + ); + const { fences: knownFences, upses: knownUpses } = useMemo< + Partial + >(() => manifestTemplate ?? {}, [manifestTemplate]); - return { + const addManifestFormDialogProps = useMemo( + () => ({ actionProceedText: 'Add', content: ( ), titleText: 'Add an install manifest', - }; - }, [formUtils, manifestTemplate]); - - const editManifestFormDialogProps = useMemo(() => { - let fences: APIManifestTemplateFenceList | undefined; - let manifestName: string | undefined; - let upses: APIManifestTemplateUpsList | undefined; - - if (manifestTemplate) { - ({ fences, upses } = manifestTemplate); - } - - if (manifestDetail) { - ({ name: manifestName } = manifestDetail); - } + }), + [domain, formUtils, knownFences, knownUpses, prefix, sequence], + ); - return { + const editManifestFormDialogProps = useMemo( + () => ({ actionProceedText: 'Edit', content: ( ), loading: isLoadingManifestDetail, - titleText: `Update install manifest ${manifestName}`, - }; - }, [formUtils, isLoadingManifestDetail, manifestDetail, manifestTemplate]); + titleText: `Update install manifest ${anName}`, + }), + [ + formUtils, + isLoadingManifestDetail, + knownFences, + knownUpses, + anName, + manifestDetail, + ], + ); + + const runManifestFormDialogProps = useMemo( + () => ({ + actionProceedText: 'Run', + content: ( + + ), + loading: isLoadingManifestDetail, + titleText: `Run install manifest ${anName}`, + }), + [ + anName, + hostOverviews, + isLoadingManifestDetail, + knownFences, + knownUpses, + manifestDetail, + runFormUtils, + ], + ); const getManifestDetail = useCallback( (manifestUuid: string, finallyAppend?: () => void) => { @@ -174,9 +220,19 @@ const ManageManifestPanel: FC = () => { }} renderListItem={(manifestUUID, { manifestName }) => ( - - - + { + setManifestDetail({ + name: manifestName, + uuid: manifestUUID, + } as APIManifestDetail); + runManifestFormDialogRef.current.setOpen?.call(null, true); + getManifestDetail(manifestUUID); + }} + variant="normal" + /> {manifestName} )} @@ -187,12 +243,19 @@ const ManageManifestPanel: FC = () => { const panelContent = useMemo( () => - isLoadingManifestTemplate || isLoadingManifestOverviews ? ( + isLoadingHostOverviews || + isLoadingManifestTemplate || + isLoadingManifestOverviews ? ( ) : ( listElement ), - [isLoadingManifestOverviews, isLoadingManifestTemplate, listElement], + [ + isLoadingHostOverviews, + isLoadingManifestOverviews, + isLoadingManifestTemplate, + listElement, + ], ); if (isFirstRender) { @@ -207,6 +270,18 @@ const ManageManifestPanel: FC = () => { .finally(() => { setIsLoadingManifestTemplate(false); }); + + api + .get('/host', { params: { types: 'node' } }) + .then(({ data }) => { + setHostOverviews(data); + }) + .catch((apiError) => { + handleAPIError(apiError); + }) + .finally(() => { + setIsLoadingHostOverviews(false); + }); } return ( @@ -229,6 +304,12 @@ const ManageManifestPanel: FC = () => { ref={editManifestFormDialogRef} scrollContent /> + ); diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx new file mode 100644 index 00000000..0ce03f0d --- /dev/null +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -0,0 +1,404 @@ +import { styled } from '@mui/material'; +import { ReactElement, useMemo } from 'react'; +import { buildPeacefulStringTestBatch } from '../../lib/test_input'; + +import FlexBox from '../FlexBox'; +import Grid from '../Grid'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import SelectWithLabel from '../SelectWithLabel'; +import { BodyText, MonoText } from '../Text'; + +const INPUT_ID_PREFIX_RUN_MANIFEST = 'run-manifest-input'; +const INPUT_ID_PREFIX_HOST = `${INPUT_ID_PREFIX_RUN_MANIFEST}-host`; + +const INPUT_ID_AN_DESCRIPTION = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-description`; +const INPUT_ID_AN_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-password`; +const INPUT_ID_AN_CONFIRM_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-confirm-password`; + +const INPUT_LABEL_AN_DESCRIPTION = 'Description'; +const INPUT_LABEL_AN_PASSWORD = 'Password'; +const INPUT_LABEL_AN_CONFIRM_PASSWORD = 'Confirm password'; + +const MANIFEST_PARAM_NONE = '--'; + +const EndMono = styled(MonoText)({ + justifyContent: 'end', +}); + +const RunManifestInputGroup = ({ + formUtils: { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + setMsgSetter, + }, + knownFences = {}, + knownHosts = {}, + knownUpses = {}, + previous: { domain: anDomain, hostConfig = {}, networkConfig = {} } = {}, +}: RunManifestInputGroupProps): ReactElement => { + const { hosts: initHostList = {} } = hostConfig; + const { + dnsCsv, + mtu, + networks: initNetworkList = {}, + ntpCsv = MANIFEST_PARAM_NONE, + } = networkConfig; + + const hostListEntries = useMemo( + () => Object.entries(initHostList), + [initHostList], + ); + const knownFenceListEntries = useMemo( + () => Object.entries(knownFences), + [knownFences], + ); + const knownHostListEntries = useMemo( + () => Object.entries(knownHosts), + [knownHosts], + ); + const knownUpsListEntries = useMemo( + () => Object.entries(knownUpses), + [knownUpses], + ); + const networkListEntries = useMemo( + () => Object.entries(initNetworkList), + [initNetworkList], + ); + + const hostOptionList = useMemo( + () => + knownHostListEntries.map(([, { hostName, hostUUID }]) => ({ + displayValue: hostName, + value: hostUUID, + })), + [knownHostListEntries], + ); + + const { + headers: hostHeaderRow, + hosts: hostSelectRow, + hostNames: hostNewNameRow, + } = useMemo( + () => + hostListEntries.reduce<{ + headers: GridLayout; + hosts: GridLayout; + hostNames: GridLayout; + }>( + (previous, [hostId, { hostName, hostNumber, hostType }]) => { + const { headers, hosts, hostNames } = previous; + + const prettyId = `${hostType}${hostNumber}`; + + headers[`run-manifest-column-header-cell-${hostId}`] = { + children: {prettyId}, + }; + + const inputId = `${INPUT_ID_PREFIX_HOST}-${hostId}`; + const inputLabel = `${prettyId} host`; + + setMsgSetter(inputId); + + hosts[`run-manifest-host-cell-${hostId}`] = { + children: ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + inputLabel, + () => { + msgSetters[inputId](); + }, + { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, + (message) => { + msgSetters[inputId]({ children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(inputId)} + required + /> + ), + }; + + hostNames[`run-manifest-new-host-name-cell-${hostId}`] = { + children: ( + + {hostName}.{anDomain} + + ), + }; + + return previous; + }, + { + headers: { + 'run-manifest-column-header-cell-offset': {}, + }, + hosts: { + 'run-manifest-host-cell-header': { + children: Uses host, + }, + }, + hostNames: { + 'run-manifest-new-host-name-cell-header': { + children: New hostname, + }, + }, + }, + ), + [ + anDomain, + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + hostListEntries, + hostOptionList, + msgSetters, + setMsgSetter, + ], + ); + + const { + gateway: defaultGatewayGridLayout, + hostNetworks: hostNetworkRowList, + } = useMemo( + () => + networkListEntries.reduce<{ + gateway: GridLayout; + hostNetworks: GridLayout; + }>( + ( + previous, + [networkId, { networkGateway, networkNumber, networkType }], + ) => { + const { gateway, hostNetworks } = previous; + + const idPrefix = `run-manifest-host-network-cell-${networkId}`; + + const networkShortName = `${networkType.toUpperCase()}${networkNumber}`; + + hostNetworks[`${idPrefix}-header`] = { + children: {networkShortName}, + }; + + hostListEntries.forEach(([hostId, { networks = {} }]) => { + const { + [networkId]: { networkIp: ip = MANIFEST_PARAM_NONE } = {}, + } = networks; + + hostNetworks[`${idPrefix}-${hostId}-ip`] = { + children: {ip}, + }; + }); + + const cellId = 'run-manifest-gateway-cell'; + + if (networkGateway && !gateway[cellId]) { + gateway[cellId] = { + children: {networkGateway}, + }; + } + + return previous; + }, + { + gateway: { + 'run-manifest-gateway-cell-header': { + children: Gateway, + }, + }, + hostNetworks: {}, + }, + ), + [hostListEntries, networkListEntries], + ); + + const hostFenceRowList = useMemo( + () => + knownFenceListEntries.reduce( + (previous, [fenceUuid, { fenceName }]) => { + const idPrefix = `run-manifest-fence-cell-${fenceUuid}`; + + previous[`${idPrefix}-header`] = { + children: Port on {fenceName}, + }; + + hostListEntries.forEach(([hostId, { fences = {} }]) => { + const { [fenceName]: { fencePort = MANIFEST_PARAM_NONE } = {} } = + fences; + + previous[`${idPrefix}-${hostId}-port`] = { + children: {fencePort}, + }; + }); + + return previous; + }, + {}, + ), + [hostListEntries, knownFenceListEntries], + ); + + const hostUpsRowList = useMemo( + () => + knownUpsListEntries.reduce( + (previous, [upsUuid, { upsName }]) => { + const idPrefix = `run-manifest-ups-cell-${upsUuid}`; + + previous[`${idPrefix}-header`] = { + children: Uses {upsName}, + }; + + hostListEntries.forEach(([hostId, { upses = {} }]) => { + const { [upsName]: { isUsed = false } = {} } = upses; + + previous[`${idPrefix}-${hostId}-is-used`] = { + children: {isUsed ? 'yes' : 'no'}, + }; + }); + + return previous; + }, + {}, + ), + [hostListEntries, knownUpsListEntries], + ); + + return ( + + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_AN_DESCRIPTION, + () => { + msgSetters[INPUT_ID_AN_DESCRIPTION](); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_AN_DESCRIPTION, + ), + }, + (message) => { + msgSetters[INPUT_ID_AN_DESCRIPTION]({ children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_AN_DESCRIPTION, + )} + required + /> + ), + sm: 2, + }, + 'anvil-password-input-cell': { + children: ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_AN_PASSWORD, + () => { + msgSetters[INPUT_ID_AN_PASSWORD](); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_AN_PASSWORD), + }, + (message) => { + msgSetters[INPUT_ID_AN_PASSWORD]({ children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + INPUT_ID_AN_PASSWORD, + )} + required + /> + ), + }, + 'anvil-confirm-password-input-cell': { + children: ( + + } + required + /> + ), + }, + }} + spacing="1em" + /> + + DNS, + }, + 'run-manifest-dns-csv-cell': { + children: {dnsCsv}, + }, + 'run-manifest-ntp-csv-cell-header': { + children: NTP, + }, + 'run-manifest-ntp-csv-cell': { + children: {ntpCsv}, + }, + 'run-manifest-mtu-cell-header': { + children: MTU, + }, + 'run-manifest-mtu-cell': { + children: {mtu}, + }, + }} + spacing="0.4em" + /> + + ); +}; + +export { + INPUT_ID_AN_CONFIRM_PASSWORD, + INPUT_ID_AN_DESCRIPTION, + INPUT_ID_AN_PASSWORD, +}; + +export default RunManifestInputGroup; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 0acc3dd8..d07c8490 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -48,6 +48,7 @@ type ManifestHostUpsList = { type ManifestHost = { fences?: ManifestHostFenceList; + hostName: string; hostNumber: number; hostType: string; networks?: ManifestHostNetworkList; @@ -167,3 +168,10 @@ type AddManifestInputGroupProps = type EditManifestInputGroupProps = AddManifestInputGroupProps; + +type RunManifestInputGroupOptionalProps = { + knownHosts?: APIHostOverviewList; +}; + +type RunManifestInputGroupProps = + RunManifestInputGroupOptionalProps & AddManifestInputGroupProps; From 2e26e3ed40f8db9768eca569321f45aba07561cd Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 14:53:53 -0400 Subject: [PATCH 46/86] fix(striker-ui): change fence port valid type --- .../ManageManifest/AnvilHostInputGroup.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx index 91903303..bcc40248 100644 --- a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx @@ -10,7 +10,7 @@ import { InnerPanel, InnerPanelBody, InnerPanelHeader } from '../Panels'; import SwitchWithLabel from '../SwitchWithLabel'; import { buildIPAddressTestBatch, - buildNumberTestBatch, + buildPeacefulStringTestBatch, } from '../../lib/test_input'; import { BodyText } from '../Text'; @@ -80,16 +80,14 @@ const AnvilHostInputGroup = ({ value={fencePort} /> } - inputTestBatch={buildNumberTestBatch( + inputTestBatch={buildPeacefulStringTestBatch( inputLabel, () => { msgSetters[inputId](); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, (message) => { - msgSetters[inputId]({ - children: message, - }); + msgSetters[inputId]({ children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} @@ -141,9 +139,7 @@ const AnvilHostInputGroup = ({ }, { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, (message) => { - msgSetters[inputId]({ - children: message, - }); + msgSetters[inputId]({ children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} From 5897353d7ba2b18c619f4870bb77c7c44450d3be Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 14:57:43 -0400 Subject: [PATCH 47/86] fix(striker-ui): add message area to manage manifest forms --- .../ManageManifest/ManageManifestPanel.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 54f8056b..3a1d983c 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -21,7 +21,7 @@ import FormDialog from '../FormDialog'; import handleAPIError from '../../lib/handleAPIError'; import IconButton from '../IconButton'; import List from '../List'; -import { MessageGroupForwardedRefContent } from '../MessageGroup'; +import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup'; import { Panel, PanelHeader } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import RunManifestInputGroup, { @@ -258,6 +258,17 @@ const ManageManifestPanel: FC = () => { ], ); + const messageArea = useMemo( + () => ( + + ), + [], + ); + if (isFirstRender) { api .get('/manifest/template') @@ -295,18 +306,21 @@ const ManageManifestPanel: FC = () => { From e73f67d50eb677b807903fae4a48fee61abcbfe6 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 16:55:30 -0400 Subject: [PATCH 48/86] fix(striker-ui): rename anvil->an (anvil or anvil node) --- .../ManageManifest/AddManifestInputGroup.tsx | 40 ++--- ...utGroup.tsx => AnHostConfigInputGroup.tsx} | 20 ++- ...ostInputGroup.tsx => AnHostInputGroup.tsx} | 24 +-- .../ManageManifest/AnIdInputGroup.tsx | 141 +++++++++++++++ ...roup.tsx => AnNetworkConfigInputGroup.tsx} | 168 +++++++++--------- ...InputGroup.tsx => AnNetworkInputGroup.tsx} | 6 +- .../ManageManifest/AnvilIdInputGroup.tsx | 152 ---------------- .../ManageManifest/EditManifestInputGroup.tsx | 28 +-- .../ManageManifest/ManageManifestPanel.tsx | 40 ++--- .../ManageManifest/RunManifestInputGroup.tsx | 65 +++---- striker-ui/types/ManageManifest.d.ts | 46 ++--- 11 files changed, 358 insertions(+), 372 deletions(-) rename striker-ui/components/ManageManifest/{AnvilHostConfigInputGroup.tsx => AnHostConfigInputGroup.tsx} (82%) rename striker-ui/components/ManageManifest/{AnvilHostInputGroup.tsx => AnHostInputGroup.tsx} (88%) create mode 100644 striker-ui/components/ManageManifest/AnIdInputGroup.tsx rename striker-ui/components/ManageManifest/{AnvilNetworkConfigInputGroup.tsx => AnNetworkConfigInputGroup.tsx} (64%) rename striker-ui/components/ManageManifest/{AnvilNetworkInputGroup.tsx => AnNetworkInputGroup.tsx} (97%) delete mode 100644 striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx diff --git a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx index 7f4c68c5..9cad0b51 100644 --- a/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AddManifestInputGroup.tsx @@ -1,16 +1,16 @@ import { ReactElement, useMemo, useState } from 'react'; -import AnvilHostConfigInputGroup from './AnvilHostConfigInputGroup'; -import AnvilIdInputGroup, { - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, -} from './AnvilIdInputGroup'; -import AnvilNetworkConfigInputGroup, { - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, -} from './AnvilNetworkConfigInputGroup'; +import AnHostConfigInputGroup from './AnHostConfigInputGroup'; +import AnIdInputGroup, { + INPUT_ID_AI_DOMAIN, + INPUT_ID_AI_PREFIX, + INPUT_ID_AI_SEQUENCE, +} from './AnIdInputGroup'; +import AnNetworkConfigInputGroup, { + INPUT_ID_ANC_DNS, + INPUT_ID_ANC_MTU, + INPUT_ID_ANC_NTP, +} from './AnNetworkConfigInputGroup'; import FlexBox from '../FlexBox'; const DEFAULT_NETWORK_LIST: ManifestNetworkList = { @@ -37,12 +37,12 @@ const DEFAULT_NETWORK_LIST: ManifestNetworkList = { const AddManifestInputGroup = < M extends { [K in - | typeof INPUT_ID_ANVIL_ID_DOMAIN - | typeof INPUT_ID_ANVIL_ID_PREFIX - | typeof INPUT_ID_ANVIL_ID_SEQUENCE - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + | typeof INPUT_ID_AI_DOMAIN + | typeof INPUT_ID_AI_PREFIX + | typeof INPUT_ID_AI_SEQUENCE + | typeof INPUT_ID_ANC_DNS + | typeof INPUT_ID_ANC_MTU + | typeof INPUT_ID_ANC_NTP]: string; }, >({ formUtils, @@ -67,14 +67,14 @@ const AddManifestInputGroup = < return ( - - + - ({ +const AnHostConfigInputGroup = ({ formUtils, knownFences = {}, knownUpses = {}, networkListEntries, previous: { hosts: previousHostList = DEFAULT_HOST_LIST } = {}, -}: AnvilHostConfigInputGroupProps): ReactElement => { +}: AnHostConfigInputGroupProps): ReactElement => { const hostListEntries = useMemo( () => Object.entries(previousHostList), [previousHostList], @@ -86,12 +88,12 @@ const AnvilHostConfigInputGroup = ({ {}, ); - const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_ANVIL_HOST_CONFIG}-${hostId}`; + const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_AHC}-${hostId}`; const hostLabel = `${hostType} ${hostNumber}`; previous[cellId] = { children: ( - ({ ); }; -export default AnvilHostConfigInputGroup; +export default AnHostConfigInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx similarity index 88% rename from striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx rename to striker-ui/components/ManageManifest/AnHostInputGroup.tsx index bcc40248..ade7425a 100644 --- a/striker-ui/components/ManageManifest/AnvilHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -14,13 +14,13 @@ import { } from '../../lib/test_input'; import { BodyText } from '../Text'; -const INPUT_ID_PREFIX_ANVIL_HOST = 'anvil-host-input'; +const INPUT_ID_PREFIX_AN_HOST = 'an-host-input'; -const INPUT_CELL_ID_PREFIX_ANVIL_HOST = `${INPUT_ID_PREFIX_ANVIL_HOST}-cell`; +const INPUT_CELL_ID_PREFIX_AN_HOST = `${INPUT_ID_PREFIX_AN_HOST}-cell`; const GRID_SPACING = '1em'; -const AnvilHostInputGroup = ({ +const AnHostInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, @@ -33,7 +33,7 @@ const AnvilHostInputGroup = ({ networks: networkList = {}, upses: upsList = {}, } = {}, -}: AnvilHostInputGroupProps): ReactElement => { +}: AnHostInputGroupProps): ReactElement => { const fenceListEntries = useMemo( () => Object.entries(fenceList), [fenceList], @@ -63,9 +63,9 @@ const AnvilHostInputGroup = ({ (previous, [fenceId, { fenceName, fencePort }]) => { const idPostfix = `${fenceId}-port`; - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; const inputLabel = `Port on ${fenceName}`; setMsgSetter(inputId); @@ -115,9 +115,9 @@ const AnvilHostInputGroup = ({ (previous, [networkId, { networkIp, networkNumber, networkType }]) => { const idPostfix = `${networkId}-ip`; - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; setMsgSetter(inputId); @@ -167,9 +167,9 @@ const AnvilHostInputGroup = ({ (previous, [upsId, { isUsed, upsName }]) => { const idPostfix = `${upsId}-power-host`; - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; - const inputId = `${INPUT_ID_PREFIX_ANVIL_HOST}-${idPostfix}`; + const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; previous[cellId] = { children: ( @@ -238,6 +238,6 @@ const AnvilHostInputGroup = ({ ); }; -export { INPUT_ID_PREFIX_ANVIL_HOST }; +export { INPUT_ID_PREFIX_AN_HOST }; -export default AnvilHostInputGroup; +export default AnHostInputGroup; diff --git a/striker-ui/components/ManageManifest/AnIdInputGroup.tsx b/striker-ui/components/ManageManifest/AnIdInputGroup.tsx new file mode 100644 index 00000000..8bbb4dfb --- /dev/null +++ b/striker-ui/components/ManageManifest/AnIdInputGroup.tsx @@ -0,0 +1,141 @@ +import { ReactElement } from 'react'; + +import Grid from '../Grid'; +import InputWithRef from '../InputWithRef'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { + buildNumberTestBatch, + buildPeacefulStringTestBatch, +} from '../../lib/test_input'; + +const INPUT_ID_PREFIX_AN_ID = 'an-id-input'; + +const INPUT_ID_AI_DOMAIN = `${INPUT_ID_PREFIX_AN_ID}-domain`; +const INPUT_ID_AI_PREFIX = `${INPUT_ID_PREFIX_AN_ID}-prefix`; +const INPUT_ID_AI_SEQUENCE = `${INPUT_ID_PREFIX_AN_ID}-sequence`; + +const INPUT_LABEL_AI_DOMAIN = 'Domain name'; +const INPUT_LABEL_AI_PREFIX = 'Prefix'; +const INPUT_LABEL_AI_SEQUENCE = 'Sequence'; + +const AnIdInputGroup = < + M extends { + [K in + | typeof INPUT_ID_AI_DOMAIN + | typeof INPUT_ID_AI_PREFIX + | typeof INPUT_ID_AI_SEQUENCE]: string; + }, +>({ + formUtils: { + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + msgSetters, + }, + previous: { + domain: previousDomain, + prefix: previousPrefix, + sequence: previousSequence, + } = {}, +}: AnIdInputGroupProps): ReactElement => ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_AI_PREFIX, + () => { + msgSetters[INPUT_ID_AI_PREFIX](); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_AI_PREFIX), + }, + (message) => { + msgSetters[INPUT_ID_AI_PREFIX]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_PREFIX)} + required + /> + ), + }, + 'an-id-input-cell-domain': { + children: ( + + } + inputTestBatch={buildPeacefulStringTestBatch( + INPUT_LABEL_AI_DOMAIN, + () => { + msgSetters[INPUT_ID_AI_DOMAIN](); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_AI_DOMAIN), + }, + (message) => { + msgSetters[INPUT_ID_AI_DOMAIN]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_DOMAIN)} + required + /> + ), + }, + 'an-id-input-cell-sequence': { + children: ( + + } + inputTestBatch={buildNumberTestBatch( + INPUT_LABEL_AI_SEQUENCE, + () => { + msgSetters[INPUT_ID_AI_SEQUENCE](); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_AI_SEQUENCE), + }, + (message) => { + msgSetters[INPUT_ID_AI_SEQUENCE]({ + children: message, + }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_SEQUENCE)} + required + valueType="number" + /> + ), + }, + }} + spacing="1em" + /> +); + +export { INPUT_ID_AI_DOMAIN, INPUT_ID_AI_PREFIX, INPUT_ID_AI_SEQUENCE }; + +export default AnIdInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx similarity index 64% rename from striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx rename to striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index b5554e16..28d598c8 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; -import AnvilNetworkInputGroup from './AnvilNetworkInputGroup'; +import AnNetworkInputGroup from './AnNetworkInputGroup'; import buildObjectStateSetterCallback from '../../lib/buildObjectStateSetterCallback'; import Grid from '../Grid'; import IconButton from '../IconButton'; @@ -11,31 +11,31 @@ import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import { buildNumberTestBatch } from '../../lib/test_input'; -const INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG = 'anvil-network-config-input'; +const INPUT_ID_PREFIX_AN_NETWORK_CONFIG = 'an-network-config-input'; -const INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-cell`; +const INPUT_CELL_ID_PREFIX_ANC = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-cell`; -const INPUT_ID_ANVIL_NETWORK_CONFIG_DNS = 'anvil-network-config-input-dns'; -const INPUT_ID_ANVIL_NETWORK_CONFIG_MTU = 'anvil-network-config-input-mtu'; -const INPUT_ID_ANVIL_NETWORK_CONFIG_NTP = 'anvil-network-config-input-ntp'; +const INPUT_ID_ANC_DNS = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-dns`; +const INPUT_ID_ANC_MTU = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-mtu`; +const INPUT_ID_ANC_NTP = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-ntp`; -const INPUT_LABEL_ANVIL_NETWORK_CONFIG_DNS = 'DNS'; -const INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU = 'MTU'; -const INPUT_LABEL_ANVIL_NETWORK_CONFIG_NTP = 'NTP'; +const INPUT_LABEL_ANC_DNS = 'DNS'; +const INPUT_LABEL_ANC_MTU = 'MTU'; +const INPUT_LABEL_ANC_NTP = 'NTP'; -const DEFAULT_DNS_CSV = '8.8.8.8, 8.8.4.4'; +const DEFAULT_DNS_CSV = '8.8.8.8,8.8.4.4'; const NETWORK_TYPE_ENTRIES = Object.entries(NETWORK_TYPES); const assertIfn = (type: string) => type === 'ifn'; const assertMn = (type: string) => type === 'mn'; -const AnvilNetworkConfigInputGroup = < +const AnNetworkConfigInputGroup = < M extends MapToInputTestID & { [K in - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + | typeof INPUT_ID_ANC_DNS + | typeof INPUT_ID_ANC_MTU + | typeof INPUT_ID_ANC_NTP]: string; }, >({ formUtils, @@ -46,7 +46,7 @@ const AnvilNetworkConfigInputGroup = < ntpCsv: previousNtpCsv, } = {}, setNetworkList, -}: AnvilNetworkConfigInputGroupProps): ReactElement => { +}: AnNetworkConfigInputGroupProps): ReactElement => { const { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, @@ -120,63 +120,62 @@ const AnvilNetworkConfigInputGroup = < [setNetworkList], ); - const handleNetworkTypeChange = - useCallback( - ( - { networkId: targetId, networkType: previousType }, - { target: { value } }, - ) => { - const newType = String(value); + const handleNetworkTypeChange = useCallback( + ( + { networkId: targetId, networkType: previousType }, + { target: { value } }, + ) => { + const newType = String(value); - let isIdMatch = false; - let newTypeNumber = 0; + let isIdMatch = false; + let newTypeNumber = 0; - const newList = networkListEntries.reduce( - (previous, [networkId, networkValue]) => { - const { networkNumber: initnn, networkType: initnt } = networkValue; + const newList = networkListEntries.reduce( + (previous, [networkId, networkValue]) => { + const { networkNumber: initnn, networkType: initnt } = networkValue; - let networkNumber = initnn; - let networkType = initnt; + let networkNumber = initnn; + let networkType = initnt; - if (networkId === targetId) { - isIdMatch = true; + if (networkId === targetId) { + isIdMatch = true; - networkType = newType; - } + networkType = newType; + } - const isTypeMatch = networkType === newType; + const isTypeMatch = networkType === newType; + + if (isTypeMatch) { + newTypeNumber += 1; + } + if (isIdMatch) { if (isTypeMatch) { - newTypeNumber += 1; + networkNumber = newTypeNumber; + } else if (networkType === previousType) { + networkNumber -= 1; } - if (isIdMatch) { - if (isTypeMatch) { - networkNumber = newTypeNumber; - } else if (networkType === previousType) { - networkNumber -= 1; - } - - previous[networkId] = { - ...networkValue, - networkNumber, - networkType, - }; - } else { - previous[networkId] = networkValue; - } + previous[networkId] = { + ...networkValue, + networkNumber, + networkType, + }; + } else { + previous[networkId] = networkValue; + } - return previous; - }, - {}, - ); + return previous; + }, + {}, + ); - setNetworkList(newList); - }, - [networkListEntries, setNetworkList], - ); + setNetworkList(newList); + }, + [networkListEntries, setNetworkList], + ); - const handleNetworkRemove = useCallback( + const handleNetworkRemove = useCallback( ({ networkId: rmId, networkType: rmType }) => { let isIdMatch = false; let networkNumber = 0; @@ -224,11 +223,11 @@ const AnvilNetworkConfigInputGroup = < }, ], ) => { - const cellId = `${INPUT_CELL_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; + const cellId = `${INPUT_CELL_ID_PREFIX_ANC}-${networkId}`; - const idPrefix = `anvil-network-${networkId}`; + const idPrefix = `an-network-${networkId}`; - const inputIdPrefix = `${INPUT_ID_PREFIX_ANVIL_NETWORK_CONFIG}-${networkId}`; + const inputIdPrefix = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-${networkId}`; const inputGatewayId = `${inputIdPrefix}-gateway`; const inputMinIpId = `${inputIdPrefix}-min-ip`; const inputNetworkTypeId = `${inputIdPrefix}-network-type`; @@ -241,7 +240,7 @@ const AnvilNetworkConfigInputGroup = < previous[cellId] = { children: ( - } @@ -317,49 +316,46 @@ const AnvilNetworkConfigInputGroup = < /> ), }, - 'anvil-network-config-input-cell-ntp': { + 'an-network-config-input-cell-ntp': { children: ( } /> ), }, - 'anvil-network-config-input-cell-mtu': { + 'an-network-config-input-cell-mtu': { children: ( } inputTestBatch={buildNumberTestBatch( - INPUT_LABEL_ANVIL_NETWORK_CONFIG_MTU, + INPUT_LABEL_ANC_MTU, () => { - msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU](); + msgSetters[INPUT_ID_ANC_MTU](); }, { - onFinishBatch: buildFinishInputTestBatchFunction( - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - ), + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_ANC_MTU), }, (message) => { - msgSetters[INPUT_ID_ANVIL_NETWORK_CONFIG_MTU]({ + msgSetters[INPUT_ID_ANC_MTU]({ children: message, }); }, )} - onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_ANC_MTU)} valueType="number" /> ), @@ -370,10 +366,6 @@ const AnvilNetworkConfigInputGroup = < ); }; -export { - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, -}; +export { INPUT_ID_ANC_DNS, INPUT_ID_ANC_MTU, INPUT_ID_ANC_NTP }; -export default AnvilNetworkConfigInputGroup; +export default AnNetworkConfigInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx similarity index 97% rename from striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx rename to striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index ad437bcb..fd1725e3 100644 --- a/striker-ui/components/ManageManifest/AnvilNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -10,7 +10,7 @@ import { InnerPanel, InnerPanelBody, InnerPanelHeader } from '../Panels'; import SelectWithLabel from '../SelectWithLabel'; import { buildIPAddressTestBatch } from '../../lib/test_input'; -const AnvilNetworkInputGroup = ({ +const AnNetworkInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, @@ -39,7 +39,7 @@ const AnvilNetworkInputGroup = ({ readonlyNetworkName: isReadonlyNetworkName, showCloseButton: isShowCloseButton, showGateway: isShowGateway, -}: AnvilNetworkInputGroupProps): ReactElement => { +}: AnNetworkInputGroupProps): ReactElement => { const networkName = useMemo( () => `${NETWORK_TYPES[networkType]} ${networkNumber}`, [networkNumber, networkType], @@ -217,4 +217,4 @@ const AnvilNetworkInputGroup = ({ ); }; -export default AnvilNetworkInputGroup; +export default AnNetworkInputGroup; diff --git a/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx b/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx deleted file mode 100644 index 3457db5c..00000000 --- a/striker-ui/components/ManageManifest/AnvilIdInputGroup.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { ReactElement } from 'react'; - -import Grid from '../Grid'; -import InputWithRef from '../InputWithRef'; -import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; -import { - buildNumberTestBatch, - buildPeacefulStringTestBatch, -} from '../../lib/test_input'; - -const INPUT_ID_ANVIL_ID_DOMAIN = 'anvil-id-input-domain'; -const INPUT_ID_ANVIL_ID_PREFIX = 'anvil-id-input-prefix'; -const INPUT_ID_ANVIL_ID_SEQUENCE = 'anvil-id-input-sequence'; - -const INPUT_LABEL_ANVIL_ID_DOMAIN = 'Domain name'; -const INPUT_LABEL_ANVIL_ID_PREFIX = 'Anvil! prefix'; -const INPUT_LABEL_ANVIL_ID_SEQUENCE = 'Anvil! sequence'; - -const AnvilIdInputGroup = < - M extends { - [K in - | typeof INPUT_ID_ANVIL_ID_DOMAIN - | typeof INPUT_ID_ANVIL_ID_PREFIX - | typeof INPUT_ID_ANVIL_ID_SEQUENCE]: string; - }, ->({ - formUtils: { - buildFinishInputTestBatchFunction, - buildInputFirstRenderFunction, - msgSetters, - }, - previous: { - domain: previousDomain, - prefix: previousPrefix, - sequence: previousSequence, - } = {}, -}: AnvilIdInputGroupProps): ReactElement => ( - - } - inputTestBatch={buildPeacefulStringTestBatch( - INPUT_LABEL_ANVIL_ID_PREFIX, - () => { - msgSetters[INPUT_ID_ANVIL_ID_PREFIX](); - }, - { - onFinishBatch: buildFinishInputTestBatchFunction( - INPUT_ID_ANVIL_ID_PREFIX, - ), - }, - (message) => { - msgSetters[INPUT_ID_ANVIL_ID_PREFIX]({ - children: message, - }); - }, - )} - onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_ANVIL_ID_PREFIX, - )} - required - /> - ), - }, - 'anvil-id-input-cell-domain': { - children: ( - - } - inputTestBatch={buildPeacefulStringTestBatch( - INPUT_LABEL_ANVIL_ID_DOMAIN, - () => { - msgSetters[INPUT_ID_ANVIL_ID_DOMAIN](); - }, - { - onFinishBatch: buildFinishInputTestBatchFunction( - INPUT_ID_ANVIL_ID_DOMAIN, - ), - }, - (message) => { - msgSetters[INPUT_ID_ANVIL_ID_DOMAIN]({ - children: message, - }); - }, - )} - onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_ANVIL_ID_DOMAIN, - )} - required - /> - ), - }, - 'anvil-id-input-cell-sequence': { - children: ( - - } - inputTestBatch={buildNumberTestBatch( - INPUT_LABEL_ANVIL_ID_SEQUENCE, - () => { - msgSetters[INPUT_ID_ANVIL_ID_SEQUENCE](); - }, - { - onFinishBatch: buildFinishInputTestBatchFunction( - INPUT_ID_ANVIL_ID_SEQUENCE, - ), - }, - (message) => { - msgSetters[INPUT_ID_ANVIL_ID_SEQUENCE]({ - children: message, - }); - }, - )} - onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_ANVIL_ID_SEQUENCE, - )} - required - valueType="number" - /> - ), - }, - }} - spacing="1em" - /> -); - -export { - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, -}; - -export default AnvilIdInputGroup; diff --git a/striker-ui/components/ManageManifest/EditManifestInputGroup.tsx b/striker-ui/components/ManageManifest/EditManifestInputGroup.tsx index c5870411..94ab3472 100644 --- a/striker-ui/components/ManageManifest/EditManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/EditManifestInputGroup.tsx @@ -1,26 +1,26 @@ import { ReactElement } from 'react'; import { - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, -} from './AnvilIdInputGroup'; + INPUT_ID_AI_DOMAIN, + INPUT_ID_AI_PREFIX, + INPUT_ID_AI_SEQUENCE, +} from './AnIdInputGroup'; import { - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, -} from './AnvilNetworkConfigInputGroup'; + INPUT_ID_ANC_DNS, + INPUT_ID_ANC_MTU, + INPUT_ID_ANC_NTP, +} from './AnNetworkConfigInputGroup'; import AddManifestInputGroup from './AddManifestInputGroup'; const EditManifestInputGroup = < M extends { [K in - | typeof INPUT_ID_ANVIL_ID_DOMAIN - | typeof INPUT_ID_ANVIL_ID_PREFIX - | typeof INPUT_ID_ANVIL_ID_SEQUENCE - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_DNS - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_MTU - | typeof INPUT_ID_ANVIL_NETWORK_CONFIG_NTP]: string; + | typeof INPUT_ID_AI_DOMAIN + | typeof INPUT_ID_AI_PREFIX + | typeof INPUT_ID_AI_SEQUENCE + | typeof INPUT_ID_ANC_DNS + | typeof INPUT_ID_ANC_MTU + | typeof INPUT_ID_ANC_NTP]: string; }, >({ formUtils, diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 3a1d983c..7f0f5c7e 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -4,15 +4,15 @@ import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import AddManifestInputGroup from './AddManifestInputGroup'; import { - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, -} from './AnvilIdInputGroup'; + INPUT_ID_AI_DOMAIN, + INPUT_ID_AI_PREFIX, + INPUT_ID_AI_SEQUENCE, +} from './AnIdInputGroup'; import { - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, -} from './AnvilNetworkConfigInputGroup'; + INPUT_ID_ANC_DNS, + INPUT_ID_ANC_MTU, + INPUT_ID_ANC_NTP, +} from './AnNetworkConfigInputGroup'; import api from '../../lib/api'; import ConfirmDialog from '../ConfirmDialog'; import EditManifestInputGroup from './EditManifestInputGroup'; @@ -25,9 +25,9 @@ import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup'; import { Panel, PanelHeader } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import RunManifestInputGroup, { - INPUT_ID_AN_CONFIRM_PASSWORD, - INPUT_ID_AN_DESCRIPTION, - INPUT_ID_AN_PASSWORD, + INPUT_ID_RM_AN_CONFIRM_PASSWORD, + INPUT_ID_RM_AN_DESCRIPTION, + INPUT_ID_RM_AN_PASSWORD, } from './RunManifestInputGroup'; import Spinner from '../Spinner'; import { BodyText, HeaderText } from '../Text'; @@ -73,12 +73,12 @@ const ManageManifestPanel: FC = () => { const formUtils = useFormUtils( [ - INPUT_ID_ANVIL_ID_DOMAIN, - INPUT_ID_ANVIL_ID_PREFIX, - INPUT_ID_ANVIL_ID_SEQUENCE, - INPUT_ID_ANVIL_NETWORK_CONFIG_DNS, - INPUT_ID_ANVIL_NETWORK_CONFIG_MTU, - INPUT_ID_ANVIL_NETWORK_CONFIG_NTP, + INPUT_ID_AI_DOMAIN, + INPUT_ID_AI_PREFIX, + INPUT_ID_AI_SEQUENCE, + INPUT_ID_ANC_DNS, + INPUT_ID_ANC_MTU, + INPUT_ID_ANC_NTP, ], messageGroupRef, ); @@ -86,9 +86,9 @@ const ManageManifestPanel: FC = () => { const runFormUtils = useFormUtils( [ - INPUT_ID_AN_CONFIRM_PASSWORD, - INPUT_ID_AN_DESCRIPTION, - INPUT_ID_AN_PASSWORD, + INPUT_ID_RM_AN_CONFIRM_PASSWORD, + INPUT_ID_RM_AN_DESCRIPTION, + INPUT_ID_RM_AN_PASSWORD, ], messageGroupRef, ); diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index 0ce03f0d..dcb25b06 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -10,15 +10,15 @@ import SelectWithLabel from '../SelectWithLabel'; import { BodyText, MonoText } from '../Text'; const INPUT_ID_PREFIX_RUN_MANIFEST = 'run-manifest-input'; -const INPUT_ID_PREFIX_HOST = `${INPUT_ID_PREFIX_RUN_MANIFEST}-host`; +const INPUT_ID_PREFIX_RM_HOST = `${INPUT_ID_PREFIX_RUN_MANIFEST}-host`; -const INPUT_ID_AN_DESCRIPTION = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-description`; -const INPUT_ID_AN_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-password`; -const INPUT_ID_AN_CONFIRM_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-confirm-password`; +const INPUT_ID_RM_AN_DESCRIPTION = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-description`; +const INPUT_ID_RM_AN_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-password`; +const INPUT_ID_RM_AN_CONFIRM_PASSWORD = `${INPUT_ID_PREFIX_RUN_MANIFEST}-an-confirm-password`; -const INPUT_LABEL_AN_DESCRIPTION = 'Description'; -const INPUT_LABEL_AN_PASSWORD = 'Password'; -const INPUT_LABEL_AN_CONFIRM_PASSWORD = 'Confirm password'; +const INPUT_LABEL_RM_AN_DESCRIPTION = 'Description'; +const INPUT_LABEL_RM_AN_PASSWORD = 'Password'; +const INPUT_LABEL_RM_AN_CONFIRM_PASSWORD = 'Confirm password'; const MANIFEST_PARAM_NONE = '--'; @@ -96,7 +96,7 @@ const RunManifestInputGroup = ({ children: {prettyId}, }; - const inputId = `${INPUT_ID_PREFIX_HOST}-${hostId}`; + const inputId = `${INPUT_ID_PREFIX_RM_HOST}-${hostId}`; const inputLabel = `${prettyId} host`; setMsgSetter(inputId); @@ -276,73 +276,76 @@ const RunManifestInputGroup = ({ } inputTestBatch={buildPeacefulStringTestBatch( - INPUT_LABEL_AN_DESCRIPTION, + INPUT_LABEL_RM_AN_DESCRIPTION, () => { - msgSetters[INPUT_ID_AN_DESCRIPTION](); + msgSetters[INPUT_ID_RM_AN_DESCRIPTION](); }, { onFinishBatch: buildFinishInputTestBatchFunction( - INPUT_ID_AN_DESCRIPTION, + INPUT_ID_RM_AN_DESCRIPTION, ), }, (message) => { - msgSetters[INPUT_ID_AN_DESCRIPTION]({ children: message }); + msgSetters[INPUT_ID_RM_AN_DESCRIPTION]({ + children: message, + }); }, )} onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_AN_DESCRIPTION, + INPUT_ID_RM_AN_DESCRIPTION, )} required /> ), sm: 2, }, - 'anvil-password-input-cell': { + 'run-manifest-input-cell-an-password': { children: ( } inputTestBatch={buildPeacefulStringTestBatch( - INPUT_LABEL_AN_PASSWORD, + INPUT_LABEL_RM_AN_PASSWORD, () => { - msgSetters[INPUT_ID_AN_PASSWORD](); + msgSetters[INPUT_ID_RM_AN_PASSWORD](); }, { - onFinishBatch: - buildFinishInputTestBatchFunction(INPUT_ID_AN_PASSWORD), + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_RM_AN_PASSWORD, + ), }, (message) => { - msgSetters[INPUT_ID_AN_PASSWORD]({ children: message }); + msgSetters[INPUT_ID_RM_AN_PASSWORD]({ children: message }); }, )} onFirstRender={buildInputFirstRenderFunction( - INPUT_ID_AN_PASSWORD, + INPUT_ID_RM_AN_PASSWORD, )} required /> ), }, - 'anvil-confirm-password-input-cell': { + 'run-manifest-input-cell-an-confirm-password': { children: ( } required @@ -396,9 +399,9 @@ const RunManifestInputGroup = ({ }; export { - INPUT_ID_AN_CONFIRM_PASSWORD, - INPUT_ID_AN_DESCRIPTION, - INPUT_ID_AN_PASSWORD, + INPUT_ID_RM_AN_CONFIRM_PASSWORD, + INPUT_ID_RM_AN_DESCRIPTION, + INPUT_ID_RM_AN_PASSWORD, }; export default RunManifestInputGroup; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index d07c8490..c5dac67f 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -63,36 +63,36 @@ type ManifestHostConfig = { hosts: ManifestHostList; }; -type AnvilIdInputGroupOptionalProps = { +type AnIdInputGroupOptionalProps = { previous?: Partial; }; -type AnvilIdInputGroupProps = - AnvilIdInputGroupOptionalProps & { +type AnIdInputGroupProps = + AnIdInputGroupOptionalProps & { formUtils: FormUtils; }; -type AnvilNetworkEventHandlerPreviousArgs = { +type AnNetworkEventHandlerPreviousArgs = { networkId: string; } & Pick; -type AnvilNetworkCloseEventHandler = ( - args: AnvilNetworkEventHandlerPreviousArgs, +type AnNetworkCloseEventHandler = ( + args: AnNetworkEventHandlerPreviousArgs, ...handlerArgs: Parameters ) => ReturnType; -type AnvilNetworkTypeChangeEventHandler = ( - args: AnvilNetworkEventHandlerPreviousArgs, +type AnNetworkTypeChangeEventHandler = ( + args: AnNetworkEventHandlerPreviousArgs, ...handlerArgs: Parameters ) => ReturnType; -type AnvilNetworkInputGroupOptionalProps = { +type AnNetworkInputGroupOptionalProps = { inputGatewayId?: string; inputGatewayLabel?: string; inputMinIpLabel?: string; inputSubnetMaskLabel?: string; - onClose?: AnvilNetworkCloseEventHandler; - onNetworkTypeChange?: AnvilNetworkTypeChangeEventHandler; + onClose?: AnNetworkCloseEventHandler; + onNetworkTypeChange?: AnNetworkTypeChangeEventHandler; previous?: { gateway?: string; minIp?: string; @@ -103,8 +103,8 @@ type AnvilNetworkInputGroupOptionalProps = { showGateway?: boolean; }; -type AnvilNetworkInputGroupProps = - AnvilNetworkInputGroupOptionalProps & { +type AnNetworkInputGroupProps = + AnNetworkInputGroupOptionalProps & { formUtils: FormUtils; idPrefix: string; inputMinIpId: string; @@ -116,22 +116,22 @@ type AnvilNetworkInputGroupProps = networkTypeOptions: SelectItem[]; }; -type AnvilHostInputGroupOptionalProps = { +type AnHostInputGroupOptionalProps = { previous?: Pick; }; -type AnvilHostInputGroupProps = - AnvilHostInputGroupOptionalProps & { +type AnHostInputGroupProps = + AnHostInputGroupOptionalProps & { formUtils: FormUtils; hostLabel: string; }; -type AnvilNetworkConfigInputGroupOptionalProps = { +type AnNetworkConfigInputGroupOptionalProps = { previous?: Partial; }; -type AnvilNetworkConfigInputGroupProps = - AnvilNetworkConfigInputGroupOptionalProps & { +type AnNetworkConfigInputGroupProps = + AnNetworkConfigInputGroupOptionalProps & { formUtils: FormUtils; networkListEntries: Array<[string, ManifestNetwork]>; setNetworkList: import('react').Dispatch< @@ -139,20 +139,20 @@ type AnvilNetworkConfigInputGroupProps = >; }; -type AnvilHostConfigInputGroupOptionalProps = { +type AnHostConfigInputGroupOptionalProps = { knownFences?: APIManifestTemplateFenceList; knownUpses?: APIManifestTemplateUpsList; previous?: Partial; }; -type AnvilHostConfigInputGroupProps = - AnvilHostConfigInputGroupOptionalProps & { +type AnHostConfigInputGroupProps = + AnHostConfigInputGroupOptionalProps & { formUtils: FormUtils; networkListEntries: Array<[string, ManifestNetwork]>; }; type AddManifestInputGroupOptionalProps = Pick< - AnvilHostConfigInputGroupOptionalProps, + AnHostConfigInputGroupOptionalProps, 'knownFences' | 'knownUpses' > & { previous?: Partial & { From 586204122e9b31afc0064c62dada41126c04568a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 17:15:21 -0400 Subject: [PATCH 49/86] fix(striker-ui): hide passwords in run manifest form --- .../components/ManageManifest/RunManifestInputGroup.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index dcb25b06..c90f11c6 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -1,12 +1,14 @@ import { styled } from '@mui/material'; import { ReactElement, useMemo } from 'react'; -import { buildPeacefulStringTestBatch } from '../../lib/test_input'; + +import INPUT_TYPES from '../../lib/consts/INPUT_TYPES'; import FlexBox from '../FlexBox'; import Grid from '../Grid'; import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import SelectWithLabel from '../SelectWithLabel'; +import { buildPeacefulStringTestBatch } from '../../lib/test_input'; import { BodyText, MonoText } from '../Text'; const INPUT_ID_PREFIX_RUN_MANIFEST = 'run-manifest-input'; @@ -316,6 +318,7 @@ const RunManifestInputGroup = ({ } inputTestBatch={buildPeacefulStringTestBatch( @@ -346,6 +349,7 @@ const RunManifestInputGroup = ({ } required From 195348c6c7f0104c987635c80d091c1520d673ed Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 22:48:32 -0400 Subject: [PATCH 50/86] fix(striker-ui): export input id builder in run manifest input group --- .../components/ManageManifest/RunManifestInputGroup.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index c90f11c6..1714c7d4 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -28,6 +28,9 @@ const EndMono = styled(MonoText)({ justifyContent: 'end', }); +const buildInputIdRMHost = (hostId: string): string => + `${INPUT_ID_PREFIX_RM_HOST}-${hostId}`; + const RunManifestInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, @@ -98,7 +101,7 @@ const RunManifestInputGroup = ({ children: {prettyId}, }; - const inputId = `${INPUT_ID_PREFIX_RM_HOST}-${hostId}`; + const inputId = buildInputIdRMHost(hostId); const inputLabel = `${prettyId} host`; setMsgSetter(inputId); @@ -406,6 +409,7 @@ export { INPUT_ID_RM_AN_CONFIRM_PASSWORD, INPUT_ID_RM_AN_DESCRIPTION, INPUT_ID_RM_AN_PASSWORD, + buildInputIdRMHost, }; export default RunManifestInputGroup; From dfd07bb7f591860e4936e5ba88a4466fc67e66af Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 23:14:55 -0400 Subject: [PATCH 51/86] fix(striker-ui): export input id builders in host input group --- .../ManageManifest/AnHostInputGroup.tsx | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index ade7425a..b4be0baa 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -16,10 +16,19 @@ import { BodyText } from '../Text'; const INPUT_ID_PREFIX_AN_HOST = 'an-host-input'; -const INPUT_CELL_ID_PREFIX_AN_HOST = `${INPUT_ID_PREFIX_AN_HOST}-cell`; +const INPUT_CELL_ID_PREFIX_AH = `${INPUT_ID_PREFIX_AN_HOST}-cell`; const GRID_SPACING = '1em'; +const buildInputIdAHFencePort = (fenceId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${fenceId}-port`; + +const buildInputIdAHNetworkIp = (networkId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${networkId}-ip`; + +const buildInputIdAHUpsPowerHost = (upsId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${upsId}-power-host`; + const AnHostInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, @@ -61,11 +70,9 @@ const AnHostInputGroup = ({ () => fenceListEntries.reduce( (previous, [fenceId, { fenceName, fencePort }]) => { - const idPostfix = `${fenceId}-port`; - - const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${fenceId}-port`; - const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; + const inputId = buildInputIdAHFencePort(fenceId); const inputLabel = `Port on ${fenceName}`; setMsgSetter(inputId); @@ -113,11 +120,9 @@ const AnHostInputGroup = ({ () => networkListEntries.reduce( (previous, [networkId, { networkIp, networkNumber, networkType }]) => { - const idPostfix = `${networkId}-ip`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${networkId}-ip`; - const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; - - const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; + const inputId = buildInputIdAHNetworkIp(networkIp); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; setMsgSetter(inputId); @@ -165,11 +170,10 @@ const AnHostInputGroup = ({ () => upsListEntries.reduce( (previous, [upsId, { isUsed, upsName }]) => { - const idPostfix = `${upsId}-power-host`; - - const cellId = `${INPUT_CELL_ID_PREFIX_AN_HOST}-${idPostfix}`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${upsId}-power-host`; - const inputId = `${INPUT_ID_PREFIX_AN_HOST}-${idPostfix}`; + const inputId = buildInputIdAHUpsPowerHost(upsId); + const inputLabel = `Uses ${upsName}`; previous[cellId] = { children: ( @@ -178,7 +182,7 @@ const AnHostInputGroup = ({ } @@ -238,6 +242,10 @@ const AnHostInputGroup = ({ ); }; -export { INPUT_ID_PREFIX_AN_HOST }; +export { + buildInputIdAHFencePort, + buildInputIdAHNetworkIp, + buildInputIdAHUpsPowerHost, +}; export default AnHostInputGroup; From 8110a3daafee84e56d5d2343a5d44c0aab1fd482 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 23:22:11 -0400 Subject: [PATCH 52/86] fix(striker-ui): export input id builders for an-network inputs --- .../AnNetworkConfigInputGroup.tsx | 13 ----- .../ManageManifest/AnNetworkInputGroup.tsx | 58 +++++++++++++++---- striker-ui/types/ManageManifest.d.ts | 5 -- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index 28d598c8..28cdfd8a 100644 --- a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -225,14 +225,6 @@ const AnNetworkConfigInputGroup = < ) => { const cellId = `${INPUT_CELL_ID_PREFIX_ANC}-${networkId}`; - const idPrefix = `an-network-${networkId}`; - - const inputIdPrefix = `${INPUT_ID_PREFIX_AN_NETWORK_CONFIG}-${networkId}`; - const inputGatewayId = `${inputIdPrefix}-gateway`; - const inputMinIpId = `${inputIdPrefix}-min-ip`; - const inputNetworkTypeId = `${inputIdPrefix}-network-type`; - const inputSubnetMaskId = `${inputIdPrefix}-subnet-mask`; - const isFirstNetwork = networkNumber === 1; const isIfn = assertIfn(networkType); const isMn = assertMn(networkType); @@ -242,11 +234,6 @@ const AnNetworkConfigInputGroup = < children: ( + `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}-gateway`; + +const buildInputIdANMinIp = (networkId: string): string => + `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}-min-ip`; + +const buildInputIdANNetworkType = (networkId: string): string => + `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}-network-type`; + +const buildInputIdANSubnetMask = (networkId: string): string => + `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}-subnet-mask`; + const AnNetworkInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, @@ -17,13 +33,8 @@ const AnNetworkInputGroup = ({ msgSetters, setMsgSetter, }, - idPrefix, - inputGatewayId, inputGatewayLabel = 'Gateway', - inputMinIpId, inputMinIpLabel = 'IP address', - inputNetworkTypeId, - inputSubnetMaskId, inputSubnetMaskLabel = 'Subnet mask', networkId, networkNumber, @@ -46,13 +57,33 @@ const AnNetworkInputGroup = ({ ); const inputCellGatewayId = useMemo( - () => `${idPrefix}-input-cell-gateway`, - [idPrefix], + () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-gateway`, + [networkId], + ); + const inputCellIpId = useMemo( + () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-ip`, + [networkId], ); - const inputCellIpId = useMemo(() => `${idPrefix}-input-cell-ip`, [idPrefix]); const inputCellSubnetMaskId = useMemo( - () => `${idPrefix}-input-cell-subnet-mask`, - [idPrefix], + () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-subnet-mask`, + [networkId], + ); + + const inputGatewayId = useMemo( + () => buildInputIdANGateway(networkId), + [networkId], + ); + const inputMinIpId = useMemo( + () => buildInputIdANMinIp(networkId), + [networkId], + ); + const inputNetworkTypeId = useMemo( + () => buildInputIdANNetworkType(networkId), + [networkId], + ); + const inputSubnetMaskId = useMemo( + () => buildInputIdANSubnetMask(networkId), + [networkId], ); const inputCellGatewayDisplay = useMemo( @@ -217,4 +248,11 @@ const AnNetworkInputGroup = ({ ); }; +export { + buildInputIdANGateway, + buildInputIdANMinIp, + buildInputIdANNetworkType, + buildInputIdANSubnetMask, +}; + export default AnNetworkInputGroup; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index c5dac67f..06eedbdf 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -87,7 +87,6 @@ type AnNetworkTypeChangeEventHandler = ( ) => ReturnType; type AnNetworkInputGroupOptionalProps = { - inputGatewayId?: string; inputGatewayLabel?: string; inputMinIpLabel?: string; inputSubnetMaskLabel?: string; @@ -106,10 +105,6 @@ type AnNetworkInputGroupOptionalProps = { type AnNetworkInputGroupProps = AnNetworkInputGroupOptionalProps & { formUtils: FormUtils; - idPrefix: string; - inputMinIpId: string; - inputNetworkTypeId: string; - inputSubnetMaskId: string; networkId: string; networkNumber: number; networkType: string; From 515d3b3b9ebbd8b57948e794454dee61e03621c7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 28 Mar 2023 23:43:34 -0400 Subject: [PATCH 53/86] fix(striker-ui): include host id in an-host input ids --- .../ManageManifest/AnHostConfigInputGroup.tsx | 1 + .../ManageManifest/AnHostInputGroup.tsx | 23 +++++++++++-------- striker-ui/types/ManageManifest.d.ts | 1 + 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx index ec7fddd1..1254d54f 100644 --- a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx @@ -95,6 +95,7 @@ const AnHostConfigInputGroup = ({ children: ( diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index b4be0baa..8366edf8 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -20,14 +20,14 @@ const INPUT_CELL_ID_PREFIX_AH = `${INPUT_ID_PREFIX_AN_HOST}-cell`; const GRID_SPACING = '1em'; -const buildInputIdAHFencePort = (fenceId: string): string => - `${INPUT_ID_PREFIX_AN_HOST}-${fenceId}-port`; +const buildInputIdAHFencePort = (hostId: string, fenceId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-${fenceId}-port`; -const buildInputIdAHNetworkIp = (networkId: string): string => - `${INPUT_ID_PREFIX_AN_HOST}-${networkId}-ip`; +const buildInputIdAHNetworkIp = (hostId: string, networkId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-${networkId}-ip`; -const buildInputIdAHUpsPowerHost = (upsId: string): string => - `${INPUT_ID_PREFIX_AN_HOST}-${upsId}-power-host`; +const buildInputIdAHUpsPowerHost = (hostId: string, upsId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-${upsId}-power-host`; const AnHostInputGroup = ({ formUtils: { @@ -36,6 +36,7 @@ const AnHostInputGroup = ({ msgSetters, setMsgSetter, }, + hostId, hostLabel, previous: { fences: fenceList = {}, @@ -72,7 +73,7 @@ const AnHostInputGroup = ({ (previous, [fenceId, { fenceName, fencePort }]) => { const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${fenceId}-port`; - const inputId = buildInputIdAHFencePort(fenceId); + const inputId = buildInputIdAHFencePort(hostId, fenceId); const inputLabel = `Port on ${fenceName}`; setMsgSetter(inputId); @@ -111,6 +112,7 @@ const AnHostInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, fenceListEntries, + hostId, msgSetters, setMsgSetter, ], @@ -122,7 +124,7 @@ const AnHostInputGroup = ({ (previous, [networkId, { networkIp, networkNumber, networkType }]) => { const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${networkId}-ip`; - const inputId = buildInputIdAHNetworkIp(networkIp); + const inputId = buildInputIdAHNetworkIp(hostId, networkIp); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; setMsgSetter(inputId); @@ -159,6 +161,7 @@ const AnHostInputGroup = ({ ), [ networkListEntries, + hostId, setMsgSetter, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, @@ -172,7 +175,7 @@ const AnHostInputGroup = ({ (previous, [upsId, { isUsed, upsName }]) => { const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${upsId}-power-host`; - const inputId = buildInputIdAHUpsPowerHost(upsId); + const inputId = buildInputIdAHUpsPowerHost(hostId, upsId); const inputLabel = `Uses ${upsName}`; previous[cellId] = { @@ -195,7 +198,7 @@ const AnHostInputGroup = ({ }, {}, ), - [upsListEntries], + [hostId, upsListEntries], ); return ( diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 06eedbdf..186ef7c7 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -118,6 +118,7 @@ type AnHostInputGroupOptionalProps = { type AnHostInputGroupProps = AnHostInputGroupOptionalProps & { formUtils: FormUtils; + hostId: string; hostLabel: string; }; From cd14783c089c9f94349334f3b3157667d86ca84a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 29 Mar 2023 16:59:35 -0400 Subject: [PATCH 54/86] fix(striker-ui): correct label in SwitchWithLabel --- .../ManageFence/CommonFenceInputGroup.tsx | 1 - striker-ui/components/SwitchWithLabel.tsx | 61 ++++++++++--------- striker-ui/types/SwitchWithLabel.d.ts | 2 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/striker-ui/components/ManageFence/CommonFenceInputGroup.tsx b/striker-ui/components/ManageFence/CommonFenceInputGroup.tsx index 9c70146e..cccfee64 100644 --- a/striker-ui/components/ManageFence/CommonFenceInputGroup.tsx +++ b/striker-ui/components/ManageFence/CommonFenceInputGroup.tsx @@ -24,7 +24,6 @@ const MAP_TO_INPUT_BUILDER: MapToInputBuilder = { input={ = ({ checked: isChecked, - flexBoxProps: { sx: flexBoxSx, ...restFlexBoxProps } = {}, + formControlLabelProps, id: switchId, label, name: switchName, onChange, switchProps, }) => { - const combinedFlexBoxSx = useMemo>( - () => ({ - '& > :first-child': { - flexGrow: 1, - }, - - ...flexBoxSx, - }), - [flexBoxSx], - ); - - const labelElement = useMemo( + const labelElement = useMemo( () => typeof label === 'string' ? ( - + {label} ) : ( - label + <>{label} ), [label], ); return ( - - {labelElement} - - + + } + label={labelElement} + labelPlacement="start" + {...formControlLabelProps} + /> ); }; diff --git a/striker-ui/types/SwitchWithLabel.d.ts b/striker-ui/types/SwitchWithLabel.d.ts index 4015119c..4e4448bf 100644 --- a/striker-ui/types/SwitchWithLabel.d.ts +++ b/striker-ui/types/SwitchWithLabel.d.ts @@ -1,5 +1,5 @@ type SwitchWithLabelOptionalProps = { - flexBoxProps?: import('../components/FlexBox').FlexBoxProps; + formControlLabelProps?: import('@mui/material').FormControlLabelProps; switchProps?: import('@mui/material').SwitchProps; }; From c20100ab741db6dd91de21c5f56ddd927652aa5f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 29 Mar 2023 23:52:10 -0400 Subject: [PATCH 55/86] fix(striker-ui): enable dataset in SwitchWithLabel --- striker-ui/components/SwitchWithLabel.tsx | 41 ++++++++++++++--------- striker-ui/types/SwitchWithLabel.d.ts | 1 + 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/striker-ui/components/SwitchWithLabel.tsx b/striker-ui/components/SwitchWithLabel.tsx index a1b9ac21..7a26ddea 100644 --- a/striker-ui/components/SwitchWithLabel.tsx +++ b/striker-ui/components/SwitchWithLabel.tsx @@ -16,6 +16,7 @@ const SwitchFormControlLabel = styled(MUIFormControlLabel)({ }); const SwitchWithLabel: FC = ({ + baseInputProps, checked: isChecked, formControlLabelProps, id: switchId, @@ -37,22 +38,30 @@ const SwitchWithLabel: FC = ({ ); return ( - - } - label={labelElement} - labelPlacement="start" - {...formControlLabelProps} - /> + <> + + } + label={labelElement} + labelPlacement="start" + {...formControlLabelProps} + /> + + ); }; diff --git a/striker-ui/types/SwitchWithLabel.d.ts b/striker-ui/types/SwitchWithLabel.d.ts index 4e4448bf..27026d61 100644 --- a/striker-ui/types/SwitchWithLabel.d.ts +++ b/striker-ui/types/SwitchWithLabel.d.ts @@ -1,4 +1,5 @@ type SwitchWithLabelOptionalProps = { + baseInputProps?: import('@mui/material').InputBaseComponentProps; formControlLabelProps?: import('@mui/material').FormControlLabelProps; switchProps?: import('@mui/material').SwitchProps; }; From d402258b8acf2bc27061d31ea13a21dabb0f82e1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 29 Mar 2023 23:54:07 -0400 Subject: [PATCH 56/86] fix(striker-ui): expose base input props in OutlinedInputWithLabel --- striker-ui/components/OutlinedInputWithLabel.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/striker-ui/components/OutlinedInputWithLabel.tsx b/striker-ui/components/OutlinedInputWithLabel.tsx index 7ded6bfe..3ee5adfb 100644 --- a/striker-ui/components/OutlinedInputWithLabel.tsx +++ b/striker-ui/components/OutlinedInputWithLabel.tsx @@ -6,6 +6,7 @@ import { IconButtonProps as MUIIconButtonProps, iconButtonClasses as muiIconButtonClasses, InputAdornment as MUIInputAdornment, + InputBaseComponentProps as MUIInputBaseComponentProps, } from '@mui/material'; import { FC, useCallback, useMemo, useState } from 'react'; @@ -31,6 +32,7 @@ type OutlinedInputWithLabelOptionalPropsWithDefault = { }; type OutlinedInputWithLabelOptionalPropsWithoutDefault = { + baseInputProps?: MUIInputBaseComponentProps; onHelp?: MUIIconButtonProps['onClick']; onHelpAppend?: MUIIconButtonProps['onClick']; type?: string; @@ -50,6 +52,7 @@ type OutlinedInputWithLabelProps = Pick< const OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS: Required & OutlinedInputWithLabelOptionalPropsWithoutDefault = { + baseInputProps: undefined, fillRow: false, formControlProps: {}, helpMessageBoxProps: {}, @@ -65,6 +68,7 @@ const OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS: Required = ({ + baseInputProps, fillRow: isFillRow = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.fillRow, formControlProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.formControlProps, helpMessageBoxProps = OUTLINED_INPUT_WITH_LABEL_DEFAULT_PROPS.helpMessageBoxProps, @@ -171,6 +175,7 @@ const OutlinedInputWithLabel: FC = ({ } fullWidth={formControlProps.fullWidth} id={id} + inputProps={baseInputProps} label={label} name={name} onBlur={onBlur} From 239138a8b149740e08538a4800b3f1ac55772342 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 00:01:27 -0400 Subject: [PATCH 57/86] fix(striker-ui): get data from inputs in manage manifest forms --- .../ManageManifest/AnHostConfigInputGroup.tsx | 6 +- .../ManageManifest/AnHostInputGroup.tsx | 171 ++++++++++++++---- .../ManageManifest/AnNetworkInputGroup.tsx | 79 ++++++++ .../ManageManifest/ManageManifestPanel.tsx | 144 ++++++++++++++- striker-ui/types/APIManifest.d.ts | 14 ++ striker-ui/types/ConfirmDialog.d.ts | 5 +- striker-ui/types/ManageManifest.d.ts | 15 +- 7 files changed, 379 insertions(+), 55 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx index 1254d54f..46e35075 100644 --- a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx @@ -10,12 +10,10 @@ const INPUT_GROUP_CELL_ID_PREFIX_AHC = `${INPUT_GROUP_ID_PREFIX_AHC}-cell`; const DEFAULT_HOST_LIST: ManifestHostList = { node1: { - hostName: '', hostNumber: 1, hostType: 'node', }, node2: { - hostName: '', hostNumber: 2, hostType: 'node', }, @@ -89,14 +87,14 @@ const AnHostConfigInputGroup = ({ ); const cellId = `${INPUT_GROUP_CELL_ID_PREFIX_AHC}-${hostId}`; - const hostLabel = `${hostType} ${hostNumber}`; previous[cellId] = { children: ( ), diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index 8366edf8..ce26627b 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -18,6 +18,82 @@ const INPUT_ID_PREFIX_AN_HOST = 'an-host-input'; const INPUT_CELL_ID_PREFIX_AH = `${INPUT_ID_PREFIX_AN_HOST}-cell`; +const MAP_TO_AH_INPUT_HANDLER: MapToManifestFormInputHandler = { + fence: (container, input) => { + const { + dataset: { hostId = '', fenceId = '', fenceName = '' }, + value: fencePort, + } = input; + const { + hostConfig: { + hosts: { [hostId]: host }, + }, + } = container; + const { fences = {} } = host; + + fences[fenceId] = { + fenceName, + fencePort, + }; + host.fences = fences; + }, + host: (container, input) => { + const { + dataset: { hostId = '', hostNumber: rawHostNumber = '', hostType = '' }, + } = input; + const hostNumber = Number.parseInt(rawHostNumber, 10); + + container.hostConfig.hosts[hostId] = { + hostNumber, + hostType, + }; + }, + network: (container, input) => { + const { + dataset: { + hostId = '', + networkId = '', + networkNumber: rawNetworkNumber = '', + networkType = '', + }, + value: networkIp, + } = input; + const { + hostConfig: { + hosts: { [hostId]: host }, + }, + } = container; + const { networks = {} } = host; + const networkNumber = Number.parseInt(rawNetworkNumber, 10); + + networks[networkId] = { + networkIp, + networkNumber, + networkType, + }; + host.networks = networks; + }, + ups: (container, input) => { + const { + checked: isUsed, + dataset: { hostId = '', upsId = '', upsName = '' }, + } = input; + const { + hostConfig: { + hosts: { [hostId]: host }, + }, + } = container; + const { upses = {} } = host; + + upses[upsId] = { + isUsed, + upsName, + }; + host.upses = upses; + }, +}; + +const GRID_COLUMNS = { xs: 1, sm: 2, md: 3 }; const GRID_SPACING = '1em'; const buildInputIdAHFencePort = (hostId: string, fenceId: string): string => @@ -37,12 +113,15 @@ const AnHostInputGroup = ({ setMsgSetter, }, hostId, - hostLabel, + hostNumber, + hostType, previous: { fences: fenceList = {}, networks: networkList = {}, upses: upsList = {}, } = {}, + // Props that depend on others. + hostLabel = `${hostType} ${hostNumber}`, }: AnHostInputGroupProps): ReactElement => { const fenceListEntries = useMemo( () => Object.entries(fenceList), @@ -54,17 +133,14 @@ const AnHostInputGroup = ({ ); const upsListEntries = useMemo(() => Object.entries(upsList), [upsList]); - const isShowFenceListGrid = useMemo( - () => Boolean(fenceListEntries.length), - [fenceListEntries.length], - ); const isShowUpsListGrid = useMemo( () => Boolean(upsListEntries.length), [upsListEntries.length], ); - const isShowFenceAndUpsListGrid = useMemo( - () => isShowFenceListGrid || isShowUpsListGrid, - [isShowFenceListGrid, isShowUpsListGrid], + + const inputIdAHHost = useMemo( + () => `${INPUT_ID_PREFIX_AN_HOST}-${hostId}`, + [hostId], ); const fenceListGridLayout = useMemo( @@ -83,6 +159,12 @@ const AnHostInputGroup = ({ ({ (previous, [networkId, { networkIp, networkNumber, networkType }]) => { const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${networkId}-ip`; - const inputId = buildInputIdAHNetworkIp(hostId, networkIp); + const inputId = buildInputIdAHNetworkIp(hostId, networkId); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; setMsgSetter(inputId); @@ -134,6 +216,13 @@ const AnHostInputGroup = ({ ({ } valueType="boolean" @@ -201,44 +295,43 @@ const AnHostInputGroup = ({ [hostId, upsListEntries], ); + const upsListGrid = useMemo( + () => + isShowUpsListGrid && ( + + ), + [isShowUpsListGrid, upsListGridLayout], + ); + return ( {hostLabel} + - {isShowFenceAndUpsListGrid && ( - - ), - }, - 'an-host-ups-input-group': { - children: ( - - ), - }, - }} - spacing={GRID_SPACING} - /> - )} + {upsListGrid} @@ -246,6 +339,8 @@ const AnHostInputGroup = ({ }; export { + INPUT_ID_PREFIX_AN_HOST, + MAP_TO_AH_INPUT_HANDLER, buildInputIdAHFencePort, buildInputIdAHNetworkIp, buildInputIdAHUpsPowerHost, diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index af9bd7a9..c86c0c14 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -14,6 +14,56 @@ const INPUT_ID_PREFIX_AN_NETWORK = 'an-network-input'; const INPUT_CELL_ID_PREFIX_AN = `${INPUT_ID_PREFIX_AN_NETWORK}-cell`; +const MAP_TO_AN_INPUT_HANDLER: MapToManifestFormInputHandler = { + gateway: (container, input) => { + const { + dataset: { networkId = '' }, + value, + } = input; + const { + networkConfig: { networks }, + } = container; + + networks[networkId].networkGateway = value; + }, + minip: (container, input) => { + const { + dataset: { networkId = '' }, + value, + } = input; + const { + networkConfig: { networks }, + } = container; + + networks[networkId].networkMinIp = value; + }, + network: (container, input) => { + const { + dataset: { networkId = '', networkNumber: rawNn = '', networkType = '' }, + } = input; + const { + networkConfig: { networks }, + } = container; + const networkNumber = Number.parseInt(rawNn, 10); + + networks[networkId] = { + networkNumber, + networkType, + } as ManifestNetwork; + }, + subnetmask: (container, input) => { + const { + dataset: { networkId = '' }, + value, + } = input; + const { + networkConfig: { networks }, + } = container; + + networks[networkId].networkSubnetMask = value; + }, +}; + const buildInputIdANGateway = (networkId: string): string => `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}-gateway`; @@ -69,6 +119,11 @@ const AnNetworkInputGroup = ({ [networkId], ); + const inputIdANNetwork = useMemo( + () => `${INPUT_ID_PREFIX_AN_NETWORK}-${networkId}`, + [networkId], + ); + const inputGatewayId = useMemo( () => buildInputIdANGateway(networkId), [networkId], @@ -121,6 +176,10 @@ const AnNetworkInputGroup = ({ ({ isShowGateway, inputGatewayId, setMsgSetter, + networkId, inputGatewayLabel, previousGateway, networkName, @@ -190,6 +250,15 @@ const AnNetworkInputGroup = ({ {closeButtonElement} + ({ ({ ({ }; export { + INPUT_ID_PREFIX_AN_NETWORK, + MAP_TO_AN_INPUT_HANDLER, buildInputIdANGateway, buildInputIdANMinIp, buildInputIdANNetworkType, diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 7f0f5c7e..6513565c 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -8,6 +8,14 @@ import { INPUT_ID_AI_PREFIX, INPUT_ID_AI_SEQUENCE, } from './AnIdInputGroup'; +import { + INPUT_ID_PREFIX_AN_HOST, + MAP_TO_AH_INPUT_HANDLER, +} from './AnHostInputGroup'; +import { + INPUT_ID_PREFIX_AN_NETWORK, + MAP_TO_AN_INPUT_HANDLER, +} from './AnNetworkInputGroup'; import { INPUT_ID_ANC_DNS, INPUT_ID_ANC_MTU, @@ -25,6 +33,7 @@ import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup'; import { Panel, PanelHeader } from '../Panels'; import periodicFetch from '../../lib/fetchers/periodicFetch'; import RunManifestInputGroup, { + buildInputIdRMHost, INPUT_ID_RM_AN_CONFIRM_PASSWORD, INPUT_ID_RM_AN_DESCRIPTION, INPUT_ID_RM_AN_PASSWORD, @@ -36,6 +45,99 @@ import useFormUtils from '../../hooks/useFormUtils'; import useIsFirstRender from '../../hooks/useIsFirstRender'; import useProtectedState from '../../hooks/useProtectedState'; +const getFormData = ( + ...[{ target }]: DivFormEventHandlerParameters +): APIBuildManifestRequestBody => { + const { elements } = target as HTMLFormElement; + + const { value: domain } = elements.namedItem( + INPUT_ID_AI_DOMAIN, + ) as HTMLInputElement; + const { value: prefix } = elements.namedItem( + INPUT_ID_AI_PREFIX, + ) as HTMLInputElement; + const { value: rawSequence } = elements.namedItem( + INPUT_ID_AI_SEQUENCE, + ) as HTMLInputElement; + const { value: dnsCsv } = elements.namedItem( + INPUT_ID_ANC_DNS, + ) as HTMLInputElement; + const { value: rawMtu } = elements.namedItem( + INPUT_ID_ANC_MTU, + ) as HTMLInputElement; + const { value: ntpCsv } = elements.namedItem( + INPUT_ID_ANC_NTP, + ) as HTMLInputElement; + + const mtu = Number.parseInt(rawMtu, 10); + const sequence = Number.parseInt(rawSequence, 10); + + return Object.values(elements).reduce( + (previous, element) => { + const { id: inputId } = element; + + if (RegExp(`^${INPUT_ID_PREFIX_AN_HOST}`).test(inputId)) { + const input = element as HTMLInputElement; + + const { + dataset: { handler: key = '' }, + } = input; + + MAP_TO_AH_INPUT_HANDLER[key]?.call(null, previous, input); + } else if (RegExp(`^${INPUT_ID_PREFIX_AN_NETWORK}`).test(inputId)) { + const input = element as HTMLInputElement; + + const { + dataset: { handler: key = '' }, + } = input; + + MAP_TO_AN_INPUT_HANDLER[key]?.call(null, previous, input); + } + + return previous; + }, + { + domain, + hostConfig: { hosts: {} }, + networkConfig: { + dnsCsv, + mtu, + networks: {}, + ntpCsv, + }, + prefix, + sequence, + }, + ); +}; + +const getRunFormData = ( + mdetailHosts: ManifestHostList, + ...[{ target }]: DivFormEventHandlerParameters +): APIRunManifestRequestBody => { + const { elements } = target as HTMLFormElement; + + const { value: description } = elements.namedItem( + INPUT_ID_RM_AN_DESCRIPTION, + ) as HTMLInputElement; + const { value: password } = elements.namedItem( + INPUT_ID_RM_AN_PASSWORD, + ) as HTMLInputElement; + + const hosts = Object.entries(mdetailHosts).reduce< + APIRunManifestRequestBody['hosts'] + >((previous, [hostId, { hostNumber, hostType }]) => { + const inputId = buildInputIdRMHost(hostId); + const { value: hostUuid } = elements.namedItem(inputId) as HTMLInputElement; + + previous[hostId] = { hostNumber, hostType, hostUuid }; + + return previous; + }, {}); + + return { description, hosts, password }; +}; + const ManageManifestPanel: FC = () => { const isFirstRender = useIsFirstRender(); @@ -95,10 +197,11 @@ const ManageManifestPanel: FC = () => { const { isFormInvalid: isRunFormInvalid } = runFormUtils; const { - domain, - name: anName, - prefix, - sequence, + domain: mdetailDomain, + hostConfig: { hosts: mdetailHosts = {} } = {}, + name: mdetailName, + prefix: mdetailPrefix, + sequence: mdetailSequence, } = useMemo>( () => manifestDetail ?? {}, [manifestDetail], @@ -115,12 +218,26 @@ const ManageManifestPanel: FC = () => { formUtils={formUtils} knownFences={knownFences} knownUpses={knownUpses} - previous={{ domain, prefix, sequence }} + previous={{ + domain: mdetailDomain, + prefix: mdetailPrefix, + sequence: mdetailSequence, + }} /> ), + onSubmitAppend: (...args) => { + getFormData(...args); + }, titleText: 'Add an install manifest', }), - [domain, formUtils, knownFences, knownUpses, prefix, sequence], + [ + mdetailDomain, + formUtils, + knownFences, + knownUpses, + mdetailPrefix, + mdetailSequence, + ], ); const editManifestFormDialogProps = useMemo( @@ -134,15 +251,18 @@ const ManageManifestPanel: FC = () => { previous={manifestDetail} /> ), + onSubmitAppend: (...args) => { + getFormData(...args); + }, loading: isLoadingManifestDetail, - titleText: `Update install manifest ${anName}`, + titleText: `Update install manifest ${mdetailName}`, }), [ formUtils, isLoadingManifestDetail, knownFences, knownUpses, - anName, + mdetailName, manifestDetail, ], ); @@ -160,11 +280,15 @@ const ManageManifestPanel: FC = () => { /> ), loading: isLoadingManifestDetail, - titleText: `Run install manifest ${anName}`, + onSubmitAppend: (...args) => { + getRunFormData(mdetailHosts, ...args); + }, + titleText: `Run install manifest ${mdetailName}`, }), [ - anName, + mdetailName, hostOverviews, + mdetailHosts, isLoadingManifestDetail, knownFences, knownUpses, diff --git a/striker-ui/types/APIManifest.d.ts b/striker-ui/types/APIManifest.d.ts index 6ef80562..ac704b50 100644 --- a/striker-ui/types/APIManifest.d.ts +++ b/striker-ui/types/APIManifest.d.ts @@ -39,3 +39,17 @@ type APIManifestTemplate = { sequence: number; upses: APIManifestTemplateUpsList; }; + +type APIBuildManifestRequestBody = Omit; + +type APIRunManifestRequestBody = { + description: string; + hosts: { + [hostId: string]: { + hostNumber: number; + hostType: string; + hostUuid: string; + }; + }; + password: string; +}; diff --git a/striker-ui/types/ConfirmDialog.d.ts b/striker-ui/types/ConfirmDialog.d.ts index 9b85da17..f7e55de3 100644 --- a/striker-ui/types/ConfirmDialog.d.ts +++ b/striker-ui/types/ConfirmDialog.d.ts @@ -1,3 +1,6 @@ +type DivFormEventHandler = import('react').FormEventHandler; +type DivFormEventHandlerParameters = Parameters; + type ConfirmDialogOptionalProps = { actionCancelText?: string; closeOnProceed?: boolean; @@ -10,7 +13,7 @@ type ConfirmDialogOptionalProps = { onActionAppend?: ContainedButtonProps['onClick']; onProceedAppend?: ContainedButtonProps['onClick']; onCancelAppend?: ContainedButtonProps['onClick']; - onSubmitAppend?: import('react').FormEventHandler; + onSubmitAppend?: DivFormEventHandler; openInitially?: boolean; preActionArea?: import('react').ReactNode; proceedButtonProps?: ContainedButtonProps; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 186ef7c7..30e03cc6 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -48,7 +48,7 @@ type ManifestHostUpsList = { type ManifestHost = { fences?: ManifestHostFenceList; - hostName: string; + hostName?: string; hostNumber: number; hostType: string; networks?: ManifestHostNetworkList; @@ -63,6 +63,15 @@ type ManifestHostConfig = { hosts: ManifestHostList; }; +type ManifestFormInputHandler = ( + container: APIBuildManifestRequestBody, + input: HTMLInputElement, +) => void; + +type MapToManifestFormInputHandler = Record; + +/** ---------- Component types ---------- */ + type AnIdInputGroupOptionalProps = { previous?: Partial; }; @@ -112,6 +121,7 @@ type AnNetworkInputGroupProps = }; type AnHostInputGroupOptionalProps = { + hostLabel?: string; previous?: Pick; }; @@ -119,7 +129,8 @@ type AnHostInputGroupProps = AnHostInputGroupOptionalProps & { formUtils: FormUtils; hostId: string; - hostLabel: string; + hostNumber: number; + hostType: string; }; type AnNetworkConfigInputGroupOptionalProps = { From 4557e27e1b80185ad72935f2448c3eaf9742e653 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:06:06 -0400 Subject: [PATCH 58/86] fix(striker-ui): add function to set form validity by regex --- striker-ui/hooks/useFormUtils.ts | 23 ++++++++++++++++++++++- striker-ui/types/FormUtils.d.ts | 3 ++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index f866bf1d..0839b5de 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -16,12 +16,32 @@ const useFormUtils = < ): FormUtils => { const [formValidity, setFormValidity] = useState>({}); - const setValidity = useCallback((key: keyof M, value: boolean) => { + const setValidity = useCallback((key: keyof M, value?: boolean) => { setFormValidity( buildObjectStateSetterCallback>(key, value), ); }, []); + const setValidityRe = useCallback((re: RegExp, value?: boolean) => { + setFormValidity((previous) => { + const result: FormValidity = {}; + + Object.keys(previous).forEach((key) => { + const id = key as keyof M; + + if (re.test(key)) { + if (value !== undefined) { + result[id] = value; + } + } else { + result[id] = previous[id]; + } + }); + + return result; + }); + }, []); + const buildFinishInputTestBatchFunction = useCallback( (key: keyof M) => (result: boolean) => { setValidity(key, result); @@ -66,6 +86,7 @@ const useFormUtils = < setFormValidity, setMsgSetter, setValidity, + setValidityRe, }; }; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 5a665490..5af8aac0 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -28,5 +28,6 @@ type FormUtils = { setter?: MessageSetterFunction, isOverwrite?: boolean, ) => void; - setValidity: (key: keyof M, value: boolean) => void; + setValidity: (key: keyof M, value?: boolean) => void; + setValidityRe: (re: RegExp, value?: boolean) => void; }; From de5ae58c7e5aff15925ecf55a926e3b3defae479 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:27:10 -0400 Subject: [PATCH 59/86] refactor(striker-ui): rename MessageSetterFunction->MessageSetter --- striker-ui/hooks/useFormUtils.ts | 2 +- striker-ui/lib/buildMapToMessageSetter.ts | 4 ++-- striker-ui/types/FormUtils.d.ts | 2 +- striker-ui/types/MapToMessageSetter.d.ts | 2 +- striker-ui/types/MessageSetterFunction.d.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 0839b5de..8e40f965 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -68,7 +68,7 @@ const useFormUtils = < ); const setMsgSetter = useCallback( - (id: keyof M, setter?: MessageSetterFunction, isOverwrite?: boolean) => { + (id: keyof M, setter?: MessageSetter, isOverwrite?: boolean) => { if (!msgSetters[id] || isOverwrite) { msgSetters[id] = setter ?? buildMessageSetter(String(id), messageGroupRef); diff --git a/striker-ui/lib/buildMapToMessageSetter.ts b/striker-ui/lib/buildMapToMessageSetter.ts index 0b5f9035..750f46fe 100644 --- a/striker-ui/lib/buildMapToMessageSetter.ts +++ b/striker-ui/lib/buildMapToMessageSetter.ts @@ -7,8 +7,8 @@ const buildMessageSetter = ( messageGroupRef: MutableRefObject, container?: MapToMessageSetter, key: string = id, -): MessageSetterFunction => { - const setter: MessageSetterFunction = (message?) => { +): MessageSetter => { + const setter: MessageSetter = (message?) => { messageGroupRef.current.setMessage?.call(null, id, message); }; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 5af8aac0..439c45a8 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -25,7 +25,7 @@ type FormUtils = { >; setMsgSetter: ( id: keyof M, - setter?: MessageSetterFunction, + setter?: MessageSetter, isOverwrite?: boolean, ) => void; setValidity: (key: keyof M, value?: boolean) => void; diff --git a/striker-ui/types/MapToMessageSetter.d.ts b/striker-ui/types/MapToMessageSetter.d.ts index 537f2644..cf781b81 100644 --- a/striker-ui/types/MapToMessageSetter.d.ts +++ b/striker-ui/types/MapToMessageSetter.d.ts @@ -1,5 +1,5 @@ type MapToMessageSetter = { - [MessageSetterID in keyof T]: MessageSetterFunction; + [MessageSetterID in keyof T]: MessageSetter; }; type InputIds = ReadonlyArray | MapToInputTestID; diff --git a/striker-ui/types/MessageSetterFunction.d.ts b/striker-ui/types/MessageSetterFunction.d.ts index 40c5da0e..7d04f76c 100644 --- a/striker-ui/types/MessageSetterFunction.d.ts +++ b/striker-ui/types/MessageSetterFunction.d.ts @@ -1,3 +1,3 @@ -type MessageSetterFunction = ( +type MessageSetter = ( message?: import('../components/MessageBox').Message, ) => void; From 23ddce9cec5ef47efc5876a297e732ee44eb3e5b Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:34:55 -0400 Subject: [PATCH 60/86] fix(striker-ui): remove unused form validity flags in manifest network config --- .../components/ManageManifest/AnNetworkConfigInputGroup.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index 28cdfd8a..827972e0 100644 --- a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -51,6 +51,7 @@ const AnNetworkConfigInputGroup = < buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, + setValidityRe, } = formUtils; const getNetworkNumber = useCallback( @@ -184,6 +185,8 @@ const AnNetworkConfigInputGroup = < (previous, [networkId, networkValue]) => { if (networkId === rmId) { isIdMatch = true; + + setValidityRe(RegExp(rmId)); } else { const { networkType } = networkValue; @@ -203,7 +206,7 @@ const AnNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries, setNetworkList], + [networkListEntries, setNetworkList, setValidityRe], ); const networksGridLayout = useMemo(() => { From 47e822421dfa28ff665711d8d61c1b8b70fd6b6a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:47:25 -0400 Subject: [PATCH 61/86] fix(striker-ui): remove unneeded dependency in InputWithRef --- striker-ui/components/InputWithRef.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/striker-ui/components/InputWithRef.tsx b/striker-ui/components/InputWithRef.tsx index 86ebbdca..0d738dc3 100644 --- a/striker-ui/components/InputWithRef.tsx +++ b/striker-ui/components/InputWithRef.tsx @@ -184,7 +184,7 @@ const InputWithRef = forwardRef( onFirstRender?.call(null, { isValid }); } - }, [input.props.id, inputValue, isFirstRender, onFirstRender, testInput]); + }, [inputValue, isFirstRender, onFirstRender, testInput]); useImperativeHandle( ref, From 4db9d8b321dc23836967534517c936a45e0e9a78 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:48:58 -0400 Subject: [PATCH 62/86] refactor(striker-ui): rename id variables in manifest network input group --- .../ManageManifest/AnNetworkInputGroup.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index c86c0c14..83c0c576 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -106,15 +106,15 @@ const AnNetworkInputGroup = ({ [networkNumber, networkType], ); - const inputCellGatewayId = useMemo( + const inputCellIdGateway = useMemo( () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-gateway`, [networkId], ); - const inputCellIpId = useMemo( + const inputCellIdIp = useMemo( () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-ip`, [networkId], ); - const inputCellSubnetMaskId = useMemo( + const inputCellIdSubnetMask = useMemo( () => `${INPUT_CELL_ID_PREFIX_AN}-${networkId}-subnet-mask`, [networkId], ); @@ -124,19 +124,19 @@ const AnNetworkInputGroup = ({ [networkId], ); - const inputGatewayId = useMemo( + const inputIdGateway = useMemo( () => buildInputIdANGateway(networkId), [networkId], ); - const inputMinIpId = useMemo( + const inputIdMinIp = useMemo( () => buildInputIdANMinIp(networkId), [networkId], ); - const inputNetworkTypeId = useMemo( + const inputIdNetworkType = useMemo( () => buildInputIdANNetworkType(networkId), [networkId], ); - const inputSubnetMaskId = useMemo( + const inputIdSubnetMask = useMemo( () => buildInputIdANSubnetMask(networkId), [networkId], ); @@ -169,8 +169,8 @@ const AnNetworkInputGroup = ({ const inputGatewayElement = useMemo(() => { let result: ReactNode; - if (isShowGateway && inputGatewayId) { - setMsgSetter(inputGatewayId); + if (isShowGateway && inputIdGateway) { + setMsgSetter(inputIdGateway); result = ( ({ 'data-handler': 'gateway', 'data-network-id': networkId, }} - id={inputGatewayId} + id={inputIdGateway} label={inputGatewayLabel} value={previousGateway} /> @@ -188,18 +188,18 @@ const AnNetworkInputGroup = ({ inputTestBatch={buildIPAddressTestBatch( `${networkName} ${inputGatewayLabel}`, () => { - msgSetters[inputGatewayId](); + msgSetters[inputIdGateway](); }, { - onFinishBatch: buildFinishInputTestBatchFunction(inputGatewayId), + onFinishBatch: buildFinishInputTestBatchFunction(inputIdGateway), }, (message) => { - msgSetters[inputGatewayId]({ + msgSetters[inputIdGateway]({ children: message, }); }, )} - onFirstRender={buildInputFirstRenderFunction(inputGatewayId)} + onFirstRender={buildInputFirstRenderFunction(inputIdGateway)} required={isShowGateway} /> ); @@ -208,7 +208,7 @@ const AnNetworkInputGroup = ({ return result; }, [ isShowGateway, - inputGatewayId, + inputIdGateway, setMsgSetter, networkId, inputGatewayLabel, @@ -220,9 +220,9 @@ const AnNetworkInputGroup = ({ ]); useEffect(() => { - setMsgSetter(inputMinIpId); - setMsgSetter(inputSubnetMaskId); - }, [inputMinIpId, inputSubnetMaskId, setMsgSetter]); + setMsgSetter(inputIdMinIp); + setMsgSetter(inputIdSubnetMask); + }, [inputIdMinIp, inputIdSubnetMask, setMsgSetter]); return ( @@ -230,7 +230,7 @@ const AnNetworkInputGroup = ({ { onNetworkTypeChange?.call( @@ -261,7 +261,7 @@ const AnNetworkInputGroup = ({ /> ({ 'data-handler': 'minip', 'data-network-id': networkId, }} - id={inputMinIpId} + id={inputIdMinIp} label={inputMinIpLabel} value={previousIpAddress} /> @@ -278,24 +278,24 @@ const AnNetworkInputGroup = ({ inputTestBatch={buildIPAddressTestBatch( `${networkName} ${inputMinIpLabel}`, () => { - msgSetters[inputMinIpId](); + msgSetters[inputIdMinIp](); }, { onFinishBatch: - buildFinishInputTestBatchFunction(inputMinIpId), + buildFinishInputTestBatchFunction(inputIdMinIp), }, (message) => { - msgSetters[inputMinIpId]({ + msgSetters[inputIdMinIp]({ children: message, }); }, )} - onFirstRender={buildInputFirstRenderFunction(inputMinIpId)} + onFirstRender={buildInputFirstRenderFunction(inputIdMinIp)} required /> ), }, - [inputCellSubnetMaskId]: { + [inputCellIdSubnetMask]: { children: ( ({ 'data-handler': 'subnetmask', 'data-network-id': networkId, }} - id={inputSubnetMaskId} + id={inputIdSubnetMask} label={inputSubnetMaskLabel} value={previousSubnetMask} /> @@ -313,7 +313,7 @@ const AnNetworkInputGroup = ({ /> ), }, - [inputCellGatewayId]: { + [inputCellIdGateway]: { children: inputGatewayElement, display: inputCellGatewayDisplay, }, From b99ac243abb788ac5668c5f578ba5a1648406af2 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 30 Mar 2023 17:50:08 -0400 Subject: [PATCH 63/86] fix(striker-ui): make grid keys unique in manifest host input group --- striker-ui/components/ManageManifest/AnHostInputGroup.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index ce26627b..4b425085 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -147,7 +147,7 @@ const AnHostInputGroup = ({ () => fenceListEntries.reduce( (previous, [fenceId, { fenceName, fencePort }]) => { - const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${fenceId}-port`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${hostId}-${fenceId}-port`; const inputId = buildInputIdAHFencePort(hostId, fenceId); const inputLabel = `Port on ${fenceName}`; @@ -204,7 +204,7 @@ const AnHostInputGroup = ({ () => networkListEntries.reduce( (previous, [networkId, { networkIp, networkNumber, networkType }]) => { - const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${networkId}-ip`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${hostId}-${networkId}-ip`; const inputId = buildInputIdAHNetworkIp(hostId, networkId); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; @@ -262,7 +262,7 @@ const AnHostInputGroup = ({ () => upsListEntries.reduce( (previous, [upsId, { isUsed, upsName }]) => { - const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${upsId}-power-host`; + const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${hostId}-${upsId}-power-host`; const inputId = buildInputIdAHUpsPowerHost(hostId, upsId); const inputLabel = `Uses ${upsName}`; From 23ea09ed3545aa0eda08d5d14d5f7f6ac65f8b35 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 31 Mar 2023 01:10:30 -0400 Subject: [PATCH 64/86] fix(striker-ui): avoid unneeded recreation of message setters --- .../ManageManifest/AnHostInputGroup.tsx | 7 --- .../ManageManifest/AnNetworkInputGroup.tsx | 11 +--- .../ManageManifest/RunManifestInputGroup.tsx | 4 -- striker-ui/hooks/useFormUtils.ts | 58 +++++++++++++------ .../lib/buildObjectStateSetterCallback.ts | 41 +++++++++++-- .../types/BuildObjectStateSetterCallback.d.ts | 23 ++++++++ striker-ui/types/FormUtils.d.ts | 2 +- 7 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 striker-ui/types/BuildObjectStateSetterCallback.d.ts diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index 4b425085..70dd2a8d 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -110,7 +110,6 @@ const AnHostInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, hostId, hostNumber, @@ -152,8 +151,6 @@ const AnHostInputGroup = ({ const inputId = buildInputIdAHFencePort(hostId, fenceId); const inputLabel = `Port on ${fenceName}`; - setMsgSetter(inputId); - previous[cellId] = { children: ( ({ fenceListEntries, hostId, msgSetters, - setMsgSetter, ], ); @@ -209,8 +205,6 @@ const AnHostInputGroup = ({ const inputId = buildInputIdAHNetworkIp(hostId, networkId); const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; - setMsgSetter(inputId); - previous[cellId] = { children: ( ({ [ networkListEntries, hostId, - setMsgSetter, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index 83c0c576..6f34c7f8 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -1,4 +1,4 @@ -import { ReactElement, ReactNode, useEffect, useMemo } from 'react'; +import { ReactElement, ReactNode, useMemo } from 'react'; import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; @@ -81,7 +81,6 @@ const AnNetworkInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, inputGatewayLabel = 'Gateway', inputMinIpLabel = 'IP address', @@ -170,8 +169,6 @@ const AnNetworkInputGroup = ({ let result: ReactNode; if (isShowGateway && inputIdGateway) { - setMsgSetter(inputIdGateway); - result = ( ({ }, [ isShowGateway, inputIdGateway, - setMsgSetter, networkId, inputGatewayLabel, previousGateway, @@ -219,11 +215,6 @@ const AnNetworkInputGroup = ({ msgSetters, ]); - useEffect(() => { - setMsgSetter(inputIdMinIp); - setMsgSetter(inputIdSubnetMask); - }, [inputIdMinIp, inputIdSubnetMask, setMsgSetter]); - return ( diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index 1714c7d4..04e45a41 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -36,7 +36,6 @@ const RunManifestInputGroup = ({ buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, msgSetters, - setMsgSetter, }, knownFences = {}, knownHosts = {}, @@ -104,8 +103,6 @@ const RunManifestInputGroup = ({ const inputId = buildInputIdRMHost(hostId); const inputLabel = `${prettyId} host`; - setMsgSetter(inputId); - hosts[`run-manifest-host-cell-${hostId}`] = { children: ( ({ hostListEntries, hostOptionList, msgSetters, - setMsgSetter, ], ); diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 8e40f965..2e5dff96 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -3,7 +3,9 @@ import { MutableRefObject, useCallback, useMemo, useState } from 'react'; import buildMapToMessageSetter, { buildMessageSetter, } from '../lib/buildMapToMessageSetter'; -import buildObjectStateSetterCallback from '../lib/buildObjectStateSetterCallback'; +import buildObjectStateSetterCallback, { + buildProtectedObjectStateSetterCallback, +} from '../lib/buildObjectStateSetterCallback'; import { MessageGroupForwardedRefContent } from '../components/MessageGroup'; const useFormUtils = < @@ -15,6 +17,9 @@ const useFormUtils = < messageGroupRef: MutableRefObject, ): FormUtils => { const [formValidity, setFormValidity] = useState>({}); + const [msgSetterList, setMsgSetterList] = useState>( + () => buildMapToMessageSetter(ids, messageGroupRef), + ); const setValidity = useCallback((key: keyof M, value?: boolean) => { setFormValidity( @@ -42,6 +47,37 @@ const useFormUtils = < }); }, []); + const setMsgSetter = useCallback( + ( + id: keyof M, + setter?: MessageSetter, + { + isOverwrite, + isUseFallback = true, + }: { isOverwrite?: boolean; isUseFallback?: boolean } = {}, + ) => { + const fallbackSetter: ObjectStatePropSetter> = ( + result, + key, + value = buildMessageSetter(String(id), messageGroupRef), + ) => { + result[key] = value; + }; + + setMsgSetterList( + buildProtectedObjectStateSetterCallback>( + id, + setter, + { + isOverwrite, + set: isUseFallback ? fallbackSetter : undefined, + }, + ), + ); + }, + [messageGroupRef], + ); + const buildFinishInputTestBatchFunction = useCallback( (key: keyof M) => (result: boolean) => { setValidity(key, result); @@ -52,9 +88,10 @@ const useFormUtils = < const buildInputFirstRenderFunction = useCallback( (key: keyof M) => ({ isValid }: InputFirstRenderFunctionArgs) => { + setMsgSetter(key); setValidity(key, isValid); }, - [setValidity], + [setMsgSetter, setValidity], ); const isFormInvalid = useMemo( @@ -62,27 +99,12 @@ const useFormUtils = < [formValidity], ); - const msgSetters = useMemo( - () => buildMapToMessageSetter(ids, messageGroupRef), - [ids, messageGroupRef], - ); - - const setMsgSetter = useCallback( - (id: keyof M, setter?: MessageSetter, isOverwrite?: boolean) => { - if (!msgSetters[id] || isOverwrite) { - msgSetters[id] = - setter ?? buildMessageSetter(String(id), messageGroupRef); - } - }, - [messageGroupRef, msgSetters], - ); - return { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, formValidity, isFormInvalid, - msgSetters, + msgSetters: msgSetterList, setFormValidity, setMsgSetter, setValidity, diff --git a/striker-ui/lib/buildObjectStateSetterCallback.ts b/striker-ui/lib/buildObjectStateSetterCallback.ts index 98cecfcf..85aca14f 100644 --- a/striker-ui/lib/buildObjectStateSetterCallback.ts +++ b/striker-ui/lib/buildObjectStateSetterCallback.ts @@ -1,13 +1,46 @@ +/** + * Checks whether specified `key` is unset in given object. Always returns + * `true` when overwrite is allowed. + */ +const checkUnset = ( + obj: S, + key: keyof S, + { isOverwrite = false }: { isOverwrite?: boolean } = {}, +): boolean => !(key in obj) || isOverwrite; + const buildObjectStateSetterCallback = - >(key: keyof S, value?: S[keyof S]) => - ({ [key]: toReplace, ...restPrevious }: S): S => { + ( + key: keyof S, + value?: S[keyof S], + { + guard, + set = (o, k, v) => { + if (v !== undefined) { + o[k] = v; + } + }, + }: BuildObjectStateSetterCallbackOptions = {}, + ): BuildObjectStateSetterCallbackReturnType => + (previous: S): S => { + const { [key]: toReplace, ...restPrevious } = previous; const result = { ...restPrevious } as S; - if (value !== undefined) { - result[key] = value; + if (guard?.call(null, previous, key, value)) { + set(result, key, value); } return result; }; +export const buildProtectedObjectStateSetterCallback = ( + key: keyof S, + value?: S[keyof S], + { + isOverwrite, + guard = (o, k) => checkUnset(o, k, { isOverwrite }), + set, + }: BuildObjectStateSetterCallbackOptions = {}, +): BuildObjectStateSetterCallbackReturnType => + buildObjectStateSetterCallback(key, value, { isOverwrite, guard, set }); + export default buildObjectStateSetterCallback; diff --git a/striker-ui/types/BuildObjectStateSetterCallback.d.ts b/striker-ui/types/BuildObjectStateSetterCallback.d.ts new file mode 100644 index 00000000..dd2aa60f --- /dev/null +++ b/striker-ui/types/BuildObjectStateSetterCallback.d.ts @@ -0,0 +1,23 @@ +type BaseObject = Record; + +type ObjectStatePropGuard = ( + previous: S, + key: keyof S, + value?: S[keyof S], +) => boolean; + +type ObjectStatePropSetter = ( + result: S, + key: keyof S, + value?: S[keyof S], +) => void; + +type BuildObjectStateSetterCallbackOptions = { + guard?: ObjectStatePropGuard; + isOverwrite?: boolean; + set?: ObjectStatePropSetter; +}; + +type BuildObjectStateSetterCallbackReturnType = ( + previous: S, +) => S; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 439c45a8..53f8382e 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -26,7 +26,7 @@ type FormUtils = { setMsgSetter: ( id: keyof M, setter?: MessageSetter, - isOverwrite?: boolean, + options?: { isOverwrite?: boolean }, ) => void; setValidity: (key: keyof M, value?: boolean) => void; setValidityRe: (re: RegExp, value?: boolean) => void; From fa2f68574ecc61a49f9c56696389f2248a052c39 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 31 Mar 2023 22:35:37 -0400 Subject: [PATCH 65/86] fix(striker-ui): add regexp object state setter callback --- .../lib/buildObjectStateSetterCallback.ts | 44 +++++++++++++++---- .../types/BuildObjectStateSetterCallback.d.ts | 1 + 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/striker-ui/lib/buildObjectStateSetterCallback.ts b/striker-ui/lib/buildObjectStateSetterCallback.ts index 85aca14f..40547c6d 100644 --- a/striker-ui/lib/buildObjectStateSetterCallback.ts +++ b/striker-ui/lib/buildObjectStateSetterCallback.ts @@ -8,25 +8,29 @@ const checkUnset = ( { isOverwrite = false }: { isOverwrite?: boolean } = {}, ): boolean => !(key in obj) || isOverwrite; +const defaultObjectStatePropSetter = ( + ...[, result, key, value]: Parameters> +): ReturnType> => { + if (value !== undefined) { + result[key] = value; + } +}; + const buildObjectStateSetterCallback = ( key: keyof S, value?: S[keyof S], { - guard, - set = (o, k, v) => { - if (v !== undefined) { - o[k] = v; - } - }, + guard = () => true, + set = defaultObjectStatePropSetter, }: BuildObjectStateSetterCallbackOptions = {}, ): BuildObjectStateSetterCallbackReturnType => (previous: S): S => { const { [key]: toReplace, ...restPrevious } = previous; const result = { ...restPrevious } as S; - if (guard?.call(null, previous, key, value)) { - set(result, key, value); + if (guard(previous, key, value)) { + set(previous, result, key, value); } return result; @@ -43,4 +47,28 @@ export const buildProtectedObjectStateSetterCallback = ( ): BuildObjectStateSetterCallbackReturnType => buildObjectStateSetterCallback(key, value, { isOverwrite, guard, set }); +export const buildRegExpObjectStateSetterCallback = + ( + re: RegExp, + value?: S[keyof S], + { + set = defaultObjectStatePropSetter, + }: Pick, 'set'> = {}, + ) => + (previous: S): S => { + const result: S = {} as S; + + Object.keys(previous).forEach((key) => { + const k = key as keyof S; + + if (re.test(key)) { + set(previous, result, k, value); + } else { + result[k] = previous[k]; + } + }); + + return result; + }; + export default buildObjectStateSetterCallback; diff --git a/striker-ui/types/BuildObjectStateSetterCallback.d.ts b/striker-ui/types/BuildObjectStateSetterCallback.d.ts index dd2aa60f..bdd142bc 100644 --- a/striker-ui/types/BuildObjectStateSetterCallback.d.ts +++ b/striker-ui/types/BuildObjectStateSetterCallback.d.ts @@ -7,6 +7,7 @@ type ObjectStatePropGuard = ( ) => boolean; type ObjectStatePropSetter = ( + previous: S, result: S, key: keyof S, value?: S[keyof S], From d22b11bb901cabd8180baedfce6f55717b72cbbe Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 31 Mar 2023 22:36:58 -0400 Subject: [PATCH 66/86] fix(striker-ui): simplify form message setting by removing message setter list --- .../ManageManifest/AnHostInputGroup.tsx | 14 +-- .../ManageManifest/AnIdInputGroup.tsx | 20 ++--- .../AnNetworkConfigInputGroup.tsx | 14 ++- .../ManageManifest/AnNetworkInputGroup.tsx | 16 ++-- .../ManageManifest/RunManifestInputGroup.tsx | 16 ++-- .../ManageUps/CommonUpsInputGroup.tsx | 14 ++- striker-ui/hooks/useFormUtils.ts | 89 ++++++++----------- striker-ui/types/FormUtils.d.ts | 10 +-- 8 files changed, 78 insertions(+), 115 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index 70dd2a8d..d0156ebb 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -109,7 +109,7 @@ const AnHostInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, }, hostId, hostNumber, @@ -170,11 +170,11 @@ const AnHostInputGroup = ({ inputTestBatch={buildPeacefulStringTestBatch( inputLabel, () => { - msgSetters[inputId](); + setMessage(inputId); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, (message) => { - msgSetters[inputId]({ children: message }); + setMessage(inputId, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} @@ -192,7 +192,7 @@ const AnHostInputGroup = ({ buildInputFirstRenderFunction, fenceListEntries, hostId, - msgSetters, + setMessage, ], ); @@ -225,11 +225,11 @@ const AnHostInputGroup = ({ inputTestBatch={buildIPAddressTestBatch( inputLabel, () => { - msgSetters[inputId](); + setMessage(inputId); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, (message) => { - msgSetters[inputId]({ children: message }); + setMessage(inputId, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} @@ -247,7 +247,7 @@ const AnHostInputGroup = ({ hostId, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, ], ); diff --git a/striker-ui/components/ManageManifest/AnIdInputGroup.tsx b/striker-ui/components/ManageManifest/AnIdInputGroup.tsx index 8bbb4dfb..04f78ad0 100644 --- a/striker-ui/components/ManageManifest/AnIdInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnIdInputGroup.tsx @@ -29,7 +29,7 @@ const AnIdInputGroup = < formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, }, previous: { domain: previousDomain, @@ -53,16 +53,14 @@ const AnIdInputGroup = < inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_AI_PREFIX, () => { - msgSetters[INPUT_ID_AI_PREFIX](); + setMessage(INPUT_ID_AI_PREFIX); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_AI_PREFIX), }, (message) => { - msgSetters[INPUT_ID_AI_PREFIX]({ - children: message, - }); + setMessage(INPUT_ID_AI_PREFIX, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_PREFIX)} @@ -83,16 +81,14 @@ const AnIdInputGroup = < inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_AI_DOMAIN, () => { - msgSetters[INPUT_ID_AI_DOMAIN](); + setMessage(INPUT_ID_AI_DOMAIN); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_AI_DOMAIN), }, (message) => { - msgSetters[INPUT_ID_AI_DOMAIN]({ - children: message, - }); + setMessage(INPUT_ID_AI_DOMAIN, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_DOMAIN)} @@ -113,16 +109,14 @@ const AnIdInputGroup = < inputTestBatch={buildNumberTestBatch( INPUT_LABEL_AI_SEQUENCE, () => { - msgSetters[INPUT_ID_AI_SEQUENCE](); + setMessage(INPUT_ID_AI_SEQUENCE); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_AI_SEQUENCE), }, (message) => { - msgSetters[INPUT_ID_AI_SEQUENCE]({ - children: message, - }); + setMessage(INPUT_ID_AI_SEQUENCE, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_AI_SEQUENCE)} diff --git a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index 827972e0..5c7cb32a 100644 --- a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -50,8 +50,8 @@ const AnNetworkConfigInputGroup = < const { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, - setValidityRe, + setMessage, + unsetKeyRe, } = formUtils; const getNetworkNumber = useCallback( @@ -186,7 +186,7 @@ const AnNetworkConfigInputGroup = < if (networkId === rmId) { isIdMatch = true; - setValidityRe(RegExp(rmId)); + unsetKeyRe(RegExp(rmId)); } else { const { networkType } = networkValue; @@ -206,7 +206,7 @@ const AnNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries, setNetworkList, setValidityRe], + [networkListEntries, setNetworkList, unsetKeyRe], ); const networksGridLayout = useMemo(() => { @@ -333,16 +333,14 @@ const AnNetworkConfigInputGroup = < inputTestBatch={buildNumberTestBatch( INPUT_LABEL_ANC_MTU, () => { - msgSetters[INPUT_ID_ANC_MTU](); + setMessage(INPUT_ID_ANC_MTU); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_ANC_MTU), }, (message) => { - msgSetters[INPUT_ID_ANC_MTU]({ - children: message, - }); + setMessage(INPUT_ID_ANC_MTU, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_ANC_MTU)} diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index 6f34c7f8..67568753 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -80,7 +80,7 @@ const AnNetworkInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, }, inputGatewayLabel = 'Gateway', inputMinIpLabel = 'IP address', @@ -185,15 +185,13 @@ const AnNetworkInputGroup = ({ inputTestBatch={buildIPAddressTestBatch( `${networkName} ${inputGatewayLabel}`, () => { - msgSetters[inputIdGateway](); + setMessage(inputIdGateway); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputIdGateway), }, (message) => { - msgSetters[inputIdGateway]({ - children: message, - }); + setMessage(inputIdGateway, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputIdGateway)} @@ -212,7 +210,7 @@ const AnNetworkInputGroup = ({ networkName, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, ]); return ( @@ -269,16 +267,14 @@ const AnNetworkInputGroup = ({ inputTestBatch={buildIPAddressTestBatch( `${networkName} ${inputMinIpLabel}`, () => { - msgSetters[inputIdMinIp](); + setMessage(inputIdMinIp); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputIdMinIp), }, (message) => { - msgSetters[inputIdMinIp]({ - children: message, - }); + setMessage(inputIdMinIp, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputIdMinIp)} diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index 04e45a41..69296dc9 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -35,7 +35,7 @@ const RunManifestInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, }, knownFences = {}, knownHosts = {}, @@ -117,11 +117,11 @@ const RunManifestInputGroup = ({ inputTestBatch={buildPeacefulStringTestBatch( inputLabel, () => { - msgSetters[inputId](); + setMessage(inputId); }, { onFinishBatch: buildFinishInputTestBatchFunction(inputId) }, (message) => { - msgSetters[inputId]({ children: message }); + setMessage(inputId, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} @@ -162,7 +162,7 @@ const RunManifestInputGroup = ({ buildInputFirstRenderFunction, hostListEntries, hostOptionList, - msgSetters, + setMessage, ], ); @@ -289,7 +289,7 @@ const RunManifestInputGroup = ({ inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_RM_AN_DESCRIPTION, () => { - msgSetters[INPUT_ID_RM_AN_DESCRIPTION](); + setMessage(INPUT_ID_RM_AN_DESCRIPTION); }, { onFinishBatch: buildFinishInputTestBatchFunction( @@ -297,7 +297,7 @@ const RunManifestInputGroup = ({ ), }, (message) => { - msgSetters[INPUT_ID_RM_AN_DESCRIPTION]({ + setMessage(INPUT_ID_RM_AN_DESCRIPTION, { children: message, }); }, @@ -323,7 +323,7 @@ const RunManifestInputGroup = ({ inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_RM_AN_PASSWORD, () => { - msgSetters[INPUT_ID_RM_AN_PASSWORD](); + setMessage(INPUT_ID_RM_AN_PASSWORD); }, { onFinishBatch: buildFinishInputTestBatchFunction( @@ -331,7 +331,7 @@ const RunManifestInputGroup = ({ ), }, (message) => { - msgSetters[INPUT_ID_RM_AN_PASSWORD]({ children: message }); + setMessage(INPUT_ID_RM_AN_PASSWORD, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction( diff --git a/striker-ui/components/ManageUps/CommonUpsInputGroup.tsx b/striker-ui/components/ManageUps/CommonUpsInputGroup.tsx index 7e744061..ff0d1cf7 100644 --- a/striker-ui/components/ManageUps/CommonUpsInputGroup.tsx +++ b/striker-ui/components/ManageUps/CommonUpsInputGroup.tsx @@ -22,7 +22,7 @@ const CommonUpsInputGroup = < formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, - msgSetters, + setMessage, }, previous: { upsIPAddress: previousIpAddress, upsName: previousUpsName } = {}, }: CommonUpsInputGroupProps): ReactElement => ( @@ -42,16 +42,14 @@ const CommonUpsInputGroup = < inputTestBatch={buildPeacefulStringTestBatch( INPUT_LABEL_UPS_NAME, () => { - msgSetters[INPUT_ID_UPS_NAME](); + setMessage(INPUT_ID_UPS_NAME); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_UPS_NAME), }, (message) => { - msgSetters[INPUT_ID_UPS_NAME]({ - children: message, - }); + setMessage(INPUT_ID_UPS_NAME, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_UPS_NAME)} @@ -72,16 +70,14 @@ const CommonUpsInputGroup = < inputTestBatch={buildIPAddressTestBatch( INPUT_LABEL_UPS_IP, () => { - msgSetters[INPUT_ID_UPS_IP](); + setMessage(INPUT_ID_UPS_IP); }, { onFinishBatch: buildFinishInputTestBatchFunction(INPUT_ID_UPS_IP), }, (message) => { - msgSetters[INPUT_ID_UPS_IP]({ - children: message, - }); + setMessage(INPUT_ID_UPS_IP, { children: message }); }, )} onFirstRender={buildInputFirstRenderFunction(INPUT_ID_UPS_IP)} diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 2e5dff96..0aeafd73 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -1,11 +1,9 @@ import { MutableRefObject, useCallback, useMemo, useState } from 'react'; -import buildMapToMessageSetter, { - buildMessageSetter, -} from '../lib/buildMapToMessageSetter'; import buildObjectStateSetterCallback, { - buildProtectedObjectStateSetterCallback, + buildRegExpObjectStateSetterCallback, } from '../lib/buildObjectStateSetterCallback'; +import { Message } from '../components/MessageBox'; import { MessageGroupForwardedRefContent } from '../components/MessageGroup'; const useFormUtils = < @@ -17,8 +15,19 @@ const useFormUtils = < messageGroupRef: MutableRefObject, ): FormUtils => { const [formValidity, setFormValidity] = useState>({}); - const [msgSetterList, setMsgSetterList] = useState>( - () => buildMapToMessageSetter(ids, messageGroupRef), + + const setMessage = useCallback( + (key: keyof M, message?: Message) => { + messageGroupRef.current.setMessage?.call(null, String(key), message); + }, + [messageGroupRef], + ); + + const setMessageRe = useCallback( + (re: RegExp, message?: Message) => { + messageGroupRef.current.setMessageRe?.call(null, re, message); + }, + [messageGroupRef], ); const setValidity = useCallback((key: keyof M, value?: boolean) => { @@ -28,54 +37,25 @@ const useFormUtils = < }, []); const setValidityRe = useCallback((re: RegExp, value?: boolean) => { - setFormValidity((previous) => { - const result: FormValidity = {}; - - Object.keys(previous).forEach((key) => { - const id = key as keyof M; - - if (re.test(key)) { - if (value !== undefined) { - result[id] = value; - } - } else { - result[id] = previous[id]; - } - }); - - return result; - }); + setFormValidity( + buildRegExpObjectStateSetterCallback>(re, value), + ); }, []); - const setMsgSetter = useCallback( - ( - id: keyof M, - setter?: MessageSetter, - { - isOverwrite, - isUseFallback = true, - }: { isOverwrite?: boolean; isUseFallback?: boolean } = {}, - ) => { - const fallbackSetter: ObjectStatePropSetter> = ( - result, - key, - value = buildMessageSetter(String(id), messageGroupRef), - ) => { - result[key] = value; - }; + const unsetKey = useCallback( + (key: keyof M) => { + setMessage(key); + setValidity(key); + }, + [setMessage, setValidity], + ); - setMsgSetterList( - buildProtectedObjectStateSetterCallback>( - id, - setter, - { - isOverwrite, - set: isUseFallback ? fallbackSetter : undefined, - }, - ), - ); + const unsetKeyRe = useCallback( + (re: RegExp) => { + setMessageRe(re); + setValidityRe(re); }, - [messageGroupRef], + [setMessageRe, setValidityRe], ); const buildFinishInputTestBatchFunction = useCallback( @@ -88,10 +68,9 @@ const useFormUtils = < const buildInputFirstRenderFunction = useCallback( (key: keyof M) => ({ isValid }: InputFirstRenderFunctionArgs) => { - setMsgSetter(key); setValidity(key, isValid); }, - [setMsgSetter, setValidity], + [setValidity], ); const isFormInvalid = useMemo( @@ -104,11 +83,13 @@ const useFormUtils = < buildInputFirstRenderFunction, formValidity, isFormInvalid, - msgSetters: msgSetterList, setFormValidity, - setMsgSetter, + setMessage, + setMessageRe, setValidity, setValidityRe, + unsetKey, + unsetKeyRe, }; }; diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 53f8382e..a33da4d0 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -19,15 +19,13 @@ type FormUtils = { buildInputFirstRenderFunction: InputFirstRenderFunctionBuilder; formValidity: FormValidity; isFormInvalid: boolean; - msgSetters: MapToMessageSetter; setFormValidity: import('react').Dispatch< import('react').SetStateAction> >; - setMsgSetter: ( - id: keyof M, - setter?: MessageSetter, - options?: { isOverwrite?: boolean }, - ) => void; + setMessage: (key: keyof M, message?: Message) => void; + setMessageRe: (re: RegExp, message?: Message) => void; setValidity: (key: keyof M, value?: boolean) => void; setValidityRe: (re: RegExp, value?: boolean) => void; + unsetKey: (key: keyof M) => void; + unsetKeyRe: (re: RegExp) => void; }; From b00afbe7cfae3938bc77c30390c4b40f5c2ebbd1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 1 Apr 2023 02:03:35 -0400 Subject: [PATCH 67/86] fix(striker-ui): avoid depend on outdated message list in MessageGroup setters --- striker-ui/components/MessageGroup.tsx | 47 ++++++++++++++------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/striker-ui/components/MessageGroup.tsx b/striker-ui/components/MessageGroup.tsx index 1ada5852..1bfda7ad 100644 --- a/striker-ui/components/MessageGroup.tsx +++ b/striker-ui/components/MessageGroup.tsx @@ -62,20 +62,22 @@ const MessageGroup = forwardRef< (key: string, message?: Message) => { let length = 0; - const { [key]: unused, ...rest } = messages; - const result: Messages = rest; + setMessages((previous) => { + const { [key]: unused, ...rest } = previous; + const result: Messages = rest; - if (message) { - result[key] = message; - } + if (message) { + result[key] = message; + } - length = Object.keys(result).length; + length = Object.keys(result).length; - onSet?.call(null, length); + return result; + }); - setMessages(result); + onSet?.call(null, length); }, - [messages, onSet], + [onSet], ); const setMessageRe = useCallback( (re: RegExp, message?: Message) => { @@ -87,22 +89,25 @@ const MessageGroup = forwardRef< length += 1; } : undefined; - const result: Messages = {}; - - Object.keys(messages).forEach((key: string) => { - if (re.test(key)) { - assignMessage?.call(null, result, key); - } else { - result[key] = messages[key]; - length += 1; - } + + setMessages((previous) => { + const result: Messages = {}; + + Object.keys(previous).forEach((key: string) => { + if (re.test(key)) { + assignMessage?.call(null, result, key); + } else { + result[key] = previous[key]; + length += 1; + } + }); + + return result; }); onSet?.call(null, length); - - setMessages(result); }, - [messages, onSet], + [onSet], ); const messageElements = useMemo(() => { From f6b7a2df5a26aee5854e3b8a8214acf0431e8af1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 1 Apr 2023 02:05:47 -0400 Subject: [PATCH 68/86] fix(striker-ui): add input unmount function builder to form utils --- striker-ui/hooks/useFormUtils.ts | 12 ++++++++++-- striker-ui/types/FormUtils.d.ts | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/striker-ui/hooks/useFormUtils.ts b/striker-ui/hooks/useFormUtils.ts index 0aeafd73..f0ae1ea3 100644 --- a/striker-ui/hooks/useFormUtils.ts +++ b/striker-ui/hooks/useFormUtils.ts @@ -18,14 +18,14 @@ const useFormUtils = < const setMessage = useCallback( (key: keyof M, message?: Message) => { - messageGroupRef.current.setMessage?.call(null, String(key), message); + messageGroupRef?.current?.setMessage?.call(null, String(key), message); }, [messageGroupRef], ); const setMessageRe = useCallback( (re: RegExp, message?: Message) => { - messageGroupRef.current.setMessageRe?.call(null, re, message); + messageGroupRef?.current?.setMessageRe?.call(null, re, message); }, [messageGroupRef], ); @@ -73,6 +73,13 @@ const useFormUtils = < [setValidity], ); + const buildInputUnmountFunction = useCallback( + (key: keyof M) => () => { + unsetKey(key); + }, + [unsetKey], + ); + const isFormInvalid = useMemo( () => Object.values(formValidity).some((isInputValid) => !isInputValid), [formValidity], @@ -81,6 +88,7 @@ const useFormUtils = < return { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, + buildInputUnmountFunction, formValidity, isFormInvalid, setFormValidity, diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index a33da4d0..5eed5d1b 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -14,9 +14,16 @@ type InputFirstRenderFunctionBuilder = ( key: keyof M, ) => InputFirstRenderFunction; +type InputUnmountFunction = () => void; + +type InputUnmountFunctionBuilder = ( + key: keyof M, +) => InputUnmountFunction; + type FormUtils = { buildFinishInputTestBatchFunction: InputTestBatchFinishCallbackBuilder; buildInputFirstRenderFunction: InputFirstRenderFunctionBuilder; + buildInputUnmountFunction: InputUnmountFunctionBuilder; formValidity: FormValidity; isFormInvalid: boolean; setFormValidity: import('react').Dispatch< From bc25f1a2adbf50ef1636bc1711df9a3dc02666ef Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 1 Apr 2023 02:07:25 -0400 Subject: [PATCH 69/86] fix(striker-ui): add unmount event and simplify mount/first render event in InputWithRef --- striker-ui/components/InputWithRef.tsx | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/striker-ui/components/InputWithRef.tsx b/striker-ui/components/InputWithRef.tsx index 0d738dc3..7c0ad269 100644 --- a/striker-ui/components/InputWithRef.tsx +++ b/striker-ui/components/InputWithRef.tsx @@ -13,7 +13,6 @@ import { import createInputOnChangeHandler from '../lib/createInputOnChangeHandler'; import { createTestInputFunction } from '../lib/test_input'; -import useIsFirstRender from '../hooks/useIsFirstRender'; type InputWithRefOptionalPropsWithDefault< TypeName extends keyof MapToInputType, @@ -27,6 +26,7 @@ type InputWithRefOptionalPropsWithoutDefault< > = { inputTestBatch?: InputTestBatch; onFirstRender?: InputFirstRenderFunction; + onUnmount?: () => void; valueKey?: CreateInputOnChangeHandlerOptions['valueKey']; }; @@ -70,6 +70,7 @@ const InputWithRef = forwardRef( input, inputTestBatch, onFirstRender, + onUnmount, required: isRequired = INPUT_WITH_REF_DEFAULT_PROPS.required, valueKey, valueType = INPUT_WITH_REF_DEFAULT_PROPS.valueType as TypeName, @@ -97,8 +98,6 @@ const InputWithRef = forwardRef( ...restInitProps } = inputProps; - const isFirstRender = useIsFirstRender(); - const [inputValue, setInputValue] = useState(initValue); const [isChangedByUser, setIsChangedByUser] = useState(false); @@ -175,16 +174,18 @@ const InputWithRef = forwardRef( * render function completes. */ useEffect(() => { - if (isFirstRender) { - const isValid = - testInput?.call(null, { - inputs: { [INPUT_TEST_ID]: { value: inputValue } }, - isIgnoreOnCallbacks: true, - }) ?? false; - - onFirstRender?.call(null, { isValid }); - } - }, [inputValue, isFirstRender, onFirstRender, testInput]); + const isValid = + testInput?.call(null, { + inputs: { [INPUT_TEST_ID]: { value: inputValue } }, + isIgnoreOnCallbacks: true, + }) ?? false; + + onFirstRender?.call(null, { isValid }); + + return onUnmount; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useImperativeHandle( ref, From 4bbd8a32f36306eacdf8af426f16f7560e05c425 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 1 Apr 2023 02:08:27 -0400 Subject: [PATCH 70/86] fix(striker-ui): unset message and validity where necessary in manage manifest --- .../components/ManageManifest/AnHostInputGroup.tsx | 3 +++ .../ManageManifest/AnNetworkConfigInputGroup.tsx | 10 ++++------ .../components/ManageManifest/AnNetworkInputGroup.tsx | 4 ++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index d0156ebb..6a28c7d0 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -109,6 +109,7 @@ const AnHostInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, + buildInputUnmountFunction, setMessage, }, hostId, @@ -233,6 +234,7 @@ const AnHostInputGroup = ({ }, )} onFirstRender={buildInputFirstRenderFunction(inputId)} + onUnmount={buildInputUnmountFunction(inputId)} required /> ), @@ -247,6 +249,7 @@ const AnHostInputGroup = ({ hostId, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, + buildInputUnmountFunction, setMessage, ], ); diff --git a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index 5c7cb32a..7cdc808b 100644 --- a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -51,7 +51,7 @@ const AnNetworkConfigInputGroup = < buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, setMessage, - unsetKeyRe, + setMessageRe, } = formUtils; const getNetworkNumber = useCallback( @@ -140,8 +140,8 @@ const AnNetworkConfigInputGroup = < if (networkId === targetId) { isIdMatch = true; - networkType = newType; + setMessageRe(RegExp(networkId)); } const isTypeMatch = networkType === newType; @@ -173,7 +173,7 @@ const AnNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries, setNetworkList], + [networkListEntries, setMessageRe, setNetworkList], ); const handleNetworkRemove = useCallback( @@ -185,8 +185,6 @@ const AnNetworkConfigInputGroup = < (previous, [networkId, networkValue]) => { if (networkId === rmId) { isIdMatch = true; - - unsetKeyRe(RegExp(rmId)); } else { const { networkType } = networkValue; @@ -206,7 +204,7 @@ const AnNetworkConfigInputGroup = < setNetworkList(newList); }, - [networkListEntries, setNetworkList, unsetKeyRe], + [networkListEntries, setNetworkList], ); const networksGridLayout = useMemo(() => { diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index 67568753..7ca2973d 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -80,6 +80,7 @@ const AnNetworkInputGroup = ({ formUtils: { buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, + buildInputUnmountFunction, setMessage, }, inputGatewayLabel = 'Gateway', @@ -195,6 +196,7 @@ const AnNetworkInputGroup = ({ }, )} onFirstRender={buildInputFirstRenderFunction(inputIdGateway)} + onUnmount={buildInputUnmountFunction(inputIdGateway)} required={isShowGateway} /> ); @@ -210,6 +212,7 @@ const AnNetworkInputGroup = ({ networkName, buildFinishInputTestBatchFunction, buildInputFirstRenderFunction, + buildInputUnmountFunction, setMessage, ]); @@ -278,6 +281,7 @@ const AnNetworkInputGroup = ({ }, )} onFirstRender={buildInputFirstRenderFunction(inputIdMinIp)} + onUnmount={buildInputUnmountFunction(inputIdMinIp)} required /> ), From 88077810bc804bb97300eb6f0d150a07b501e13f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 12:58:08 -0400 Subject: [PATCH 71/86] fix(striker-ui-api): remove computed props from host entry of manifest execution --- striker-ui-api/src/types/APIManifest.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/striker-ui-api/src/types/APIManifest.d.ts b/striker-ui-api/src/types/APIManifest.d.ts index f997d72b..4d9efbc3 100644 --- a/striker-ui-api/src/types/APIManifest.d.ts +++ b/striker-ui-api/src/types/APIManifest.d.ts @@ -74,10 +74,7 @@ type ManifestDetail = { }; type ManifestExecutionHost = { - anName?: string; - anUuid?: string; hostId?: string; - hostName?: string; hostNumber: number; hostType: string; hostUuid: string; From 7eb629c747c2d5dbdc604035c12856870132b92e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 13:09:53 -0400 Subject: [PATCH 72/86] fix(striker-ui): add manage manifest tab to manage element page --- striker-ui/pages/manage-element/index.tsx | 56 ++++++++++++++++------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/striker-ui/pages/manage-element/index.tsx b/striker-ui/pages/manage-element/index.tsx index 54fdcb3a..d05f5275 100644 --- a/striker-ui/pages/manage-element/index.tsx +++ b/striker-ui/pages/manage-element/index.tsx @@ -8,6 +8,7 @@ import Grid from '../../components/Grid'; import handleAPIError from '../../lib/handleAPIError'; import Header from '../../components/Header'; import ManageFencePanel from '../../components/ManageFence'; +import ManageManifestPanel from '../../components/ManageManifest'; import ManageUpsPanel from '../../components/ManageUps'; import { Panel } from '../../components/Panels'; import PrepareHostForm from '../../components/PrepareHostForm'; @@ -20,12 +21,18 @@ import useIsFirstRender from '../../hooks/useIsFirstRender'; import useProtect from '../../hooks/useProtect'; import useProtectedState from '../../hooks/useProtectedState'; +const TAB_ID_PREPARE_HOST = 'prepare-host'; +const TAB_ID_PREPARE_NETWORK = 'prepare-network'; +const TAB_ID_MANAGE_FENCE = 'manage-fence'; +const TAB_ID_MANAGE_UPS = 'manage-ups'; +const TAB_ID_MANAGE_MANIFEST = 'manage-manifest'; + const MAP_TO_PAGE_TITLE: Record = { - 'prepare-host': 'Prepare Host', - 'prepare-network': 'Prepare Network', - 'manage-fence': 'Manage Fence Devices', - 'manage-ups': 'Manage UPSes', - 'manage-manifest': 'Manage Manifests', + [TAB_ID_PREPARE_HOST]: 'Prepare Host', + [TAB_ID_PREPARE_NETWORK]: 'Prepare Network', + [TAB_ID_MANAGE_FENCE]: 'Manage Fence Devices', + [TAB_ID_MANAGE_UPS]: 'Manage UPSes', + [TAB_ID_MANAGE_MANIFEST]: 'Manage Manifests', }; const PAGE_TITLE_LOADING = 'Loading'; const STEP_CONTENT_GRID_COLUMNS = { md: 8, sm: 6, xs: 1 }; @@ -70,7 +77,7 @@ const PrepareNetworkTabContent: FC = () => { > {hostOverviewPairs.map(([hostUUID, { shortHostName }]) => ( @@ -144,6 +151,19 @@ const ManageUpsTabContent: FC = () => ( /> ); +const ManageManifestContent: FC = () => ( + , + ...STEP_CONTENT_GRID_CENTER_COLUMN, + }, + }} + /> +); + const ManageElement: FC = () => { const { isReady, @@ -156,11 +176,11 @@ const ManageElement: FC = () => { useEffect(() => { if (isReady) { let step = getQueryParam(rawStep, { - fallbackValue: 'prepare-host', + fallbackValue: TAB_ID_PREPARE_HOST, }); if (!MAP_TO_PAGE_TITLE[step]) { - step = 'prepare-host'; + step = TAB_ID_PREPARE_HOST; } if (pageTitle === PAGE_TITLE_LOADING) { @@ -188,24 +208,28 @@ const ManageElement: FC = () => { orientation={{ xs: 'vertical', sm: 'horizontal' }} value={pageTabId} > - - - - + + + + + - + - + - + - + + + + ); }; From fea0657119cc1b4714a9a5dcaa02c8033351c54a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 14:14:08 -0400 Subject: [PATCH 73/86] fix(striker-ui): call finish batch callback when input optional --- striker-ui/lib/test_input/testInput.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/striker-ui/lib/test_input/testInput.ts b/striker-ui/lib/test_input/testInput.ts index 835d40f6..9091ff84 100644 --- a/striker-ui/lib/test_input/testInput.ts +++ b/striker-ui/lib/test_input/testInput.ts @@ -101,15 +101,17 @@ const testInput: TestInputFunction = ({ displayMin = orSet(dDisplayMin, String(min)), } = testsToRun[id]; - if (!value && isOptional) { - return true; - } - const { cbFinishBatch, setTestCallbacks } = evalIsIgnoreOnCallbacks({ isIgnoreOnCallbacks, onFinishBatch, }); + if (!value && isOptional) { + cbFinishBatch?.call(null, true, id); + + return true; + } + const runTest: (test: InputTest) => boolean = ({ onFailure, onSuccess = dOnSuccess, From 16ac5a4e6d1f2b0aad7d80ec042eb749d88fc0c5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 14:26:55 -0400 Subject: [PATCH 74/86] fix(striker-ui): add IPv4 CSV input test to network config in manage manifest --- .../AnNetworkConfigInputGroup.tsx | 33 ++++++++++++++++++- .../lib/test_input/buildIpCsvTestBatch.tsx | 25 ++++++++++++++ striker-ui/lib/test_input/index.ts | 2 ++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 striker-ui/lib/test_input/buildIpCsvTestBatch.tsx diff --git a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx index 7cdc808b..4faee782 100644 --- a/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkConfigInputGroup.tsx @@ -9,7 +9,10 @@ import Grid from '../Grid'; import IconButton from '../IconButton'; import InputWithRef from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; -import { buildNumberTestBatch } from '../../lib/test_input'; +import { + buildIpCsvTestBatch, + buildNumberTestBatch, +} from '../../lib/test_input'; const INPUT_ID_PREFIX_AN_NETWORK_CONFIG = 'an-network-config-input'; @@ -300,6 +303,20 @@ const AnNetworkConfigInputGroup = < value={previousDnsCsv} /> } + inputTestBatch={buildIpCsvTestBatch( + INPUT_LABEL_ANC_DNS, + () => { + setMessage(INPUT_ID_ANC_DNS); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_ANC_DNS), + }, + (message) => { + setMessage(INPUT_ID_ANC_DNS, { children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_ANC_DNS)} required /> ), @@ -314,6 +331,20 @@ const AnNetworkConfigInputGroup = < value={previousNtpCsv} /> } + inputTestBatch={buildIpCsvTestBatch( + INPUT_LABEL_ANC_NTP, + () => { + setMessage(INPUT_ID_ANC_NTP); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(INPUT_ID_ANC_NTP), + }, + (message) => { + setMessage(INPUT_ID_ANC_NTP, { children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction(INPUT_ID_ANC_NTP)} /> ), }, diff --git a/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx b/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx new file mode 100644 index 00000000..0aeec9d2 --- /dev/null +++ b/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx @@ -0,0 +1,25 @@ +import { REP_IPV4_CSV } from '../consts/REG_EXP_PATTERNS'; + +const buildIpCsvTestBatch: BuildInputTestBatchFunction = ( + inputName, + onSuccess, + { isRequired, onFinishBatch, ...defaults } = {}, + onIpCsvTestFailure, +) => ({ + defaults: { ...defaults, onSuccess }, + isRequired, + onFinishBatch, + tests: [ + { + onFailure: (...args) => { + onIpCsvTestFailure( + `${inputName} must be one or more valid IPv4 addresses separated by comma; without trailing comma.`, + ...args, + ); + }, + test: ({ value }) => REP_IPV4_CSV.test(value as string), + }, + ], +}); + +export default buildIpCsvTestBatch; diff --git a/striker-ui/lib/test_input/index.ts b/striker-ui/lib/test_input/index.ts index ca39f5f6..2d1ba8ee 100644 --- a/striker-ui/lib/test_input/index.ts +++ b/striker-ui/lib/test_input/index.ts @@ -1,5 +1,6 @@ import buildDomainTestBatch from './buildDomainTestBatch'; import buildIPAddressTestBatch from './buildIPAddressTestBatch'; +import buildIpCsvTestBatch from './buildIpCsvTestBatch'; import buildNumberTestBatch from './buildNumberTestBatch'; import buildPeacefulStringTestBatch from './buildPeacefulStringTestBatch'; import buildUUIDTestBatch from './buildUUIDTestBatch'; @@ -13,6 +14,7 @@ import testRange from './testRange'; export { buildDomainTestBatch, buildIPAddressTestBatch, + buildIpCsvTestBatch, buildNumberTestBatch, buildPeacefulStringTestBatch, buildUUIDTestBatch, From 79bcc3773b6b3c54b0c527577b50932298c39024 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 15:46:11 -0400 Subject: [PATCH 75/86] fix(striker-ui): remove unneeded not-blank test in input test batch builders --- striker-ui/lib/test_input/buildDomainTestBatch.tsx | 2 -- striker-ui/lib/test_input/buildIPAddressTestBatch.tsx | 3 --- striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx | 2 -- striker-ui/lib/test_input/buildUUIDTestBatch.tsx | 3 --- 4 files changed, 10 deletions(-) diff --git a/striker-ui/lib/test_input/buildDomainTestBatch.tsx b/striker-ui/lib/test_input/buildDomainTestBatch.tsx index dad7374a..ebf96c71 100644 --- a/striker-ui/lib/test_input/buildDomainTestBatch.tsx +++ b/striker-ui/lib/test_input/buildDomainTestBatch.tsx @@ -1,6 +1,5 @@ import { REP_DOMAIN } from '../consts/REG_EXP_PATTERNS'; -import testNotBlank from './testNotBlank'; import { InlineMonoText } from '../../components/Text'; const buildDomainTestBatch: BuildInputTestBatchFunction = ( @@ -27,7 +26,6 @@ const buildDomainTestBatch: BuildInputTestBatchFunction = ( test: ({ compare, value }) => (compare[0] as boolean) || REP_DOMAIN.test(value as string), }, - { test: testNotBlank }, ], }); diff --git a/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx b/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx index 418fb322..ed4b26e6 100644 --- a/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx +++ b/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx @@ -1,7 +1,5 @@ import { REP_IPV4 } from '../consts/REG_EXP_PATTERNS'; -import testNotBlank from './testNotBlank'; - const buildIPAddressTestBatch: BuildInputTestBatchFunction = ( inputName, onSuccess, @@ -21,7 +19,6 @@ const buildIPAddressTestBatch: BuildInputTestBatchFunction = ( }, test: ({ value }) => REP_IPV4.test(value as string), }, - { test: testNotBlank }, ], }); diff --git a/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx b/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx index 594f387e..27efbc1c 100644 --- a/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx +++ b/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx @@ -1,6 +1,5 @@ import { REP_PEACEFUL_STRING } from '../consts/REG_EXP_PATTERNS'; -import testNotBlank from './testNotBlank'; import { InlineMonoText } from '../../components/Text'; const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = ( @@ -31,7 +30,6 @@ const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = ( }, test: ({ value }) => REP_PEACEFUL_STRING.test(value as string), }, - { test: testNotBlank }, ], }); diff --git a/striker-ui/lib/test_input/buildUUIDTestBatch.tsx b/striker-ui/lib/test_input/buildUUIDTestBatch.tsx index d5173fbf..36cdc85f 100644 --- a/striker-ui/lib/test_input/buildUUIDTestBatch.tsx +++ b/striker-ui/lib/test_input/buildUUIDTestBatch.tsx @@ -1,7 +1,5 @@ import { REP_UUID } from '../consts/REG_EXP_PATTERNS'; -import testNotBlank from './testNotBlank'; - const buildUUIDTestBatch: BuildInputTestBatchFunction = ( inputName, onSuccess, @@ -18,7 +16,6 @@ const buildUUIDTestBatch: BuildInputTestBatchFunction = ( }, test: ({ value }) => REP_UUID.test(value as string), }, - { test: testNotBlank }, ], }); From 1e2aee47a309834639237654b5d0b58077e720ae Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 15:53:12 -0400 Subject: [PATCH 76/86] fix(striker-ui): populate add manifest form with template props --- .../ManageManifest/ManageManifestPanel.tsx | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index 6513565c..acae1939 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -196,19 +196,21 @@ const ManageManifestPanel: FC = () => { ); const { isFormInvalid: isRunFormInvalid } = runFormUtils; + const { hostConfig: { hosts: mdetailHosts = {} } = {}, name: mdetailName } = + useMemo>( + () => manifestDetail ?? {}, + [manifestDetail], + ); const { - domain: mdetailDomain, - hostConfig: { hosts: mdetailHosts = {} } = {}, - name: mdetailName, - prefix: mdetailPrefix, - sequence: mdetailSequence, - } = useMemo>( - () => manifestDetail ?? {}, - [manifestDetail], + domain: mtemplateDomain, + fences: knownFences, + prefix: mtemplatePrefix, + sequence: mtemplateSequence, + upses: knownUpses, + } = useMemo>( + () => manifestTemplate ?? {}, + [manifestTemplate], ); - const { fences: knownFences, upses: knownUpses } = useMemo< - Partial - >(() => manifestTemplate ?? {}, [manifestTemplate]); const addManifestFormDialogProps = useMemo( () => ({ @@ -219,9 +221,9 @@ const ManageManifestPanel: FC = () => { knownFences={knownFences} knownUpses={knownUpses} previous={{ - domain: mdetailDomain, - prefix: mdetailPrefix, - sequence: mdetailSequence, + domain: mtemplateDomain, + prefix: mtemplatePrefix, + sequence: mtemplateSequence, }} /> ), @@ -231,12 +233,12 @@ const ManageManifestPanel: FC = () => { titleText: 'Add an install manifest', }), [ - mdetailDomain, formUtils, knownFences, knownUpses, - mdetailPrefix, - mdetailSequence, + mtemplateDomain, + mtemplatePrefix, + mtemplateSequence, ], ); From 4837143c73791a11717bb1f4cae89ca1f6d08f45 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 16:07:32 -0400 Subject: [PATCH 77/86] fix(striker-ui): add input test to network subnet mask in manage manifest --- .../ManageManifest/AnNetworkInputGroup.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx index 7ca2973d..73eaba0a 100644 --- a/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnNetworkInputGroup.tsx @@ -300,6 +300,23 @@ const AnNetworkInputGroup = ({ value={previousSubnetMask} /> } + inputTestBatch={buildIPAddressTestBatch( + `${networkName} ${inputSubnetMaskLabel}`, + () => { + setMessage(inputIdSubnetMask); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(inputIdSubnetMask), + }, + (message) => { + setMessage(inputIdSubnetMask, { children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + inputIdSubnetMask, + )} + onUnmount={buildInputUnmountFunction(inputIdSubnetMask)} required /> ), From 499abdd7bd7f678895b7985212faca1fb2a09f6a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 16:13:20 -0400 Subject: [PATCH 78/86] fix(striker-ui): add host to input error message in manage manifest host inputs --- striker-ui/components/ManageManifest/AnHostInputGroup.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index 6a28c7d0..a1ee9aff 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -169,7 +169,7 @@ const AnHostInputGroup = ({ /> } inputTestBatch={buildPeacefulStringTestBatch( - inputLabel, + `${hostId} ${inputLabel}`, () => { setMessage(inputId); }, @@ -224,7 +224,7 @@ const AnHostInputGroup = ({ /> } inputTestBatch={buildIPAddressTestBatch( - inputLabel, + `${hostId} ${inputLabel}`, () => { setMessage(inputId); }, From d8c6c2bec99d2f613308fce2e8b6838dc6fb2155 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 19:26:13 -0400 Subject: [PATCH 79/86] fix(striker-ui-api): set host list from anvil data hash in /run-manifest --- .../src/lib/request_handlers/command/runManifest.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/command/runManifest.ts b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts index a445e934..6f98296a 100644 --- a/striker-ui-api/src/lib/request_handlers/command/runManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/command/runManifest.ts @@ -85,7 +85,11 @@ export const runManifest: RequestHandler< let rawSysData: AnvilDataSysHash | undefined; try { - ({ manifests: rawManifestListData, sys: rawSysData } = getAnvilData<{ + ({ + hosts: rawHostListData, + manifests: rawManifestListData, + sys: rawSysData, + } = getAnvilData<{ hosts?: AnvilDataHostListHash; manifests?: AnvilDataManifestListHash; sys?: AnvilDataSysHash; @@ -149,11 +153,10 @@ export const runManifest: RequestHandler< debug, file: __filename, job_command: SERVER_PATHS.usr.sbin['anvil-join-anvil'].self, - job_data: `as_machine=${hostId},manifest_uuid=${manifestUuid},anvil_uuid=`, + job_data: `as_machine=${hostId},manifest_uuid=${manifestUuid}`, job_description: 'job_0073', job_host_uuid: hostUuid, job_name: `join_anvil::${hostId}`, - job_progress: 0, job_title: 'job_0072', }); @@ -178,7 +181,7 @@ export const runManifest: RequestHandler< .stdout as [string]; joinAnJobs.forEach((jobParams) => { - jobParams.job_data += newAnUuid; + jobParams.job_data += `,anvil_uuid=${newAnUuid}`; job(jobParams); }); } catch (subError) { From 0bdc5dd44e1b2c7cd2e59a56404f71c016200a10 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 3 Apr 2023 23:25:23 -0400 Subject: [PATCH 80/86] fix(striker-ui): re-add not-blank tests to test batch builders --- striker-ui/lib/test_input/buildDomainTestBatch.tsx | 4 ++++ striker-ui/lib/test_input/buildIPAddressTestBatch.tsx | 7 ++++++- striker-ui/lib/test_input/buildIpCsvTestBatch.tsx | 10 +++++++++- .../lib/test_input/buildPeacefulStringTestBatch.tsx | 8 ++++++++ striker-ui/lib/test_input/buildUUIDTestBatch.tsx | 7 ++++++- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/striker-ui/lib/test_input/buildDomainTestBatch.tsx b/striker-ui/lib/test_input/buildDomainTestBatch.tsx index ebf96c71..9ed17475 100644 --- a/striker-ui/lib/test_input/buildDomainTestBatch.tsx +++ b/striker-ui/lib/test_input/buildDomainTestBatch.tsx @@ -1,5 +1,6 @@ import { REP_DOMAIN } from '../consts/REG_EXP_PATTERNS'; +import testNotBlank from './testNotBlank'; import { InlineMonoText } from '../../components/Text'; const buildDomainTestBatch: BuildInputTestBatchFunction = ( @@ -12,6 +13,9 @@ const buildDomainTestBatch: BuildInputTestBatchFunction = ( isRequired, onFinishBatch, tests: [ + { + test: testNotBlank, + }, { onFailure: (...args) => { onDomainTestFailure( diff --git a/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx b/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx index ed4b26e6..9d80b8be 100644 --- a/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx +++ b/striker-ui/lib/test_input/buildIPAddressTestBatch.tsx @@ -1,5 +1,7 @@ import { REP_IPV4 } from '../consts/REG_EXP_PATTERNS'; +import testNotBlank from './testNotBlank'; + const buildIPAddressTestBatch: BuildInputTestBatchFunction = ( inputName, onSuccess, @@ -10,10 +12,13 @@ const buildIPAddressTestBatch: BuildInputTestBatchFunction = ( isRequired, onFinishBatch, tests: [ + { + test: testNotBlank, + }, { onFailure: (...args) => { onIPv4TestFailure( - `${inputName} should be a valid IPv4 address.`, + <>{inputName} should be a valid IPv4 address., ...args, ); }, diff --git a/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx b/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx index 0aeec9d2..19cbb9cf 100644 --- a/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx +++ b/striker-ui/lib/test_input/buildIpCsvTestBatch.tsx @@ -1,5 +1,7 @@ import { REP_IPV4_CSV } from '../consts/REG_EXP_PATTERNS'; +import testNotBlank from './testNotBlank'; + const buildIpCsvTestBatch: BuildInputTestBatchFunction = ( inputName, onSuccess, @@ -10,10 +12,16 @@ const buildIpCsvTestBatch: BuildInputTestBatchFunction = ( isRequired, onFinishBatch, tests: [ + { + test: testNotBlank, + }, { onFailure: (...args) => { onIpCsvTestFailure( - `${inputName} must be one or more valid IPv4 addresses separated by comma; without trailing comma.`, + <> + {inputName} must be one or more valid IPv4 addresses separated by + comma(s); without trailing comma. + , ...args, ); }, diff --git a/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx b/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx index 27efbc1c..e05e0fa2 100644 --- a/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx +++ b/striker-ui/lib/test_input/buildPeacefulStringTestBatch.tsx @@ -1,5 +1,6 @@ import { REP_PEACEFUL_STRING } from '../consts/REG_EXP_PATTERNS'; +import testNotBlank from './testNotBlank'; import { InlineMonoText } from '../../components/Text'; const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = ( @@ -12,6 +13,13 @@ const buildPeacefulStringTestBatch: BuildInputTestBatchFunction = ( isRequired, onFinishBatch, tests: [ + { + /** + * Not-blank test ensures no unnecessary error message is provided when + * input is not (yet) filled. + */ + test: testNotBlank, + }, { onFailure: (...args) => { onTestPeacefulStringFailureAppend( diff --git a/striker-ui/lib/test_input/buildUUIDTestBatch.tsx b/striker-ui/lib/test_input/buildUUIDTestBatch.tsx index 36cdc85f..2779f113 100644 --- a/striker-ui/lib/test_input/buildUUIDTestBatch.tsx +++ b/striker-ui/lib/test_input/buildUUIDTestBatch.tsx @@ -1,5 +1,7 @@ import { REP_UUID } from '../consts/REG_EXP_PATTERNS'; +import testNotBlank from './testNotBlank'; + const buildUUIDTestBatch: BuildInputTestBatchFunction = ( inputName, onSuccess, @@ -10,9 +12,12 @@ const buildUUIDTestBatch: BuildInputTestBatchFunction = ( isRequired, onFinishBatch, tests: [ + { + test: testNotBlank, + }, { onFailure: (...args) => { - onUUIDTestFailure(`${inputName} must be a valid UUID.`, ...args); + onUUIDTestFailure(<>{inputName} must be a valid UUID., ...args); }, test: ({ value }) => REP_UUID.test(value as string), }, From 148ba5f1c1434398f51a919cc37d712d141a7abf Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 4 Apr 2023 00:09:17 -0400 Subject: [PATCH 81/86] fix(striker-ui): resolve Message in FormUtils --- striker-ui/types/FormUtils.d.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/striker-ui/types/FormUtils.d.ts b/striker-ui/types/FormUtils.d.ts index 5eed5d1b..b65f506c 100644 --- a/striker-ui/types/FormUtils.d.ts +++ b/striker-ui/types/FormUtils.d.ts @@ -29,8 +29,14 @@ type FormUtils = { setFormValidity: import('react').Dispatch< import('react').SetStateAction> >; - setMessage: (key: keyof M, message?: Message) => void; - setMessageRe: (re: RegExp, message?: Message) => void; + setMessage: ( + key: keyof M, + message?: import('../components/MessageBox').Message, + ) => void; + setMessageRe: ( + re: RegExp, + message?: import('../components/MessageBox').Message, + ) => void; setValidity: (key: keyof M, value?: boolean) => void; setValidityRe: (re: RegExp, value?: boolean) => void; unsetKey: (key: keyof M) => void; From 1b4a5baeb5efacf90415af8aa7b983f0aa21fe52 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 4 Apr 2023 00:19:30 -0400 Subject: [PATCH 82/86] fix(striker-ui): add password compare check in run manifest form --- .../ManageManifest/RunManifestInputGroup.tsx | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx index 69296dc9..618ef6ff 100644 --- a/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx +++ b/striker-ui/components/ManageManifest/RunManifestInputGroup.tsx @@ -1,11 +1,11 @@ import { styled } from '@mui/material'; -import { ReactElement, useMemo } from 'react'; +import { ReactElement, useMemo, useRef } from 'react'; import INPUT_TYPES from '../../lib/consts/INPUT_TYPES'; import FlexBox from '../FlexBox'; import Grid from '../Grid'; -import InputWithRef from '../InputWithRef'; +import InputWithRef, { InputForwardedRefContent } from '../InputWithRef'; import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; import SelectWithLabel from '../SelectWithLabel'; import { buildPeacefulStringTestBatch } from '../../lib/test_input'; @@ -42,6 +42,8 @@ const RunManifestInputGroup = ({ knownUpses = {}, previous: { domain: anDomain, hostConfig = {}, networkConfig = {} } = {}, }: RunManifestInputGroupProps): ReactElement => { + const passwordRef = useRef>({}); + const { hosts: initHostList = {} } = hostConfig; const { dnsCsv, @@ -272,6 +274,45 @@ const RunManifestInputGroup = ({ [hostListEntries, knownUpsListEntries], ); + const confirmPasswordProps = useMemo(() => { + const inputTestBatch = buildPeacefulStringTestBatch( + INPUT_LABEL_RM_AN_CONFIRM_PASSWORD, + () => { + setMessage(INPUT_ID_RM_AN_CONFIRM_PASSWORD); + }, + { + onFinishBatch: buildFinishInputTestBatchFunction( + INPUT_ID_RM_AN_CONFIRM_PASSWORD, + ), + }, + (message) => { + setMessage(INPUT_ID_RM_AN_CONFIRM_PASSWORD, { children: message }); + }, + ); + + const onFirstRender = buildInputFirstRenderFunction( + INPUT_ID_RM_AN_CONFIRM_PASSWORD, + ); + + inputTestBatch.tests.push({ + onFailure: () => { + setMessage(INPUT_ID_RM_AN_CONFIRM_PASSWORD, { + children: <>Confirm password must match password., + }); + }, + test: ({ value }) => passwordRef.current.getValue?.call(null) === value, + }); + + return { + inputTestBatch, + onFirstRender, + }; + }, [ + buildFinishInputTestBatchFunction, + buildInputFirstRenderFunction, + setMessage, + ]); + return ( ({ onFirstRender={buildInputFirstRenderFunction( INPUT_ID_RM_AN_PASSWORD, )} + ref={passwordRef} required /> ), @@ -352,6 +394,7 @@ const RunManifestInputGroup = ({ /> } required + {...confirmPasswordProps} /> ), }, From 39e3623085f0dbc95b603e5307b89a352ea58d42 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 4 Apr 2023 00:20:41 -0400 Subject: [PATCH 83/86] fix(striker-ui): enable confirm dialog for submit run manifest --- .../ManageManifest/ManageManifestPanel.tsx | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index acae1939..f2b25c14 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -149,18 +149,20 @@ const ManageManifestPanel: FC = () => { const runManifestFormDialogRef = useRef({}); const messageGroupRef = useRef({}); - const [confirmDialogProps] = useConfirmDialogProps(); + const [confirmDialogProps, setConfirmDialogProps] = useConfirmDialogProps(); const [hostOverviews, setHostOverviews] = useProtectedState< APIHostOverviewList | undefined >(undefined); const [isEditManifests, setIsEditManifests] = useState(false); const [isLoadingHostOverviews, setIsLoadingHostOverviews] = - useState(true); + useProtectedState(true); const [isLoadingManifestDetail, setIsLoadingManifestDetail] = useProtectedState(true); const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] = - useState(true); + useProtectedState(true); + const [isSubmittingManifestExecution, setIsSubmittingManifestExecution] = + useProtectedState(false); const [manifestDetail, setManifestDetail] = useProtectedState< APIManifestDetail | undefined >(undefined); @@ -196,11 +198,14 @@ const ManageManifestPanel: FC = () => { ); const { isFormInvalid: isRunFormInvalid } = runFormUtils; - const { hostConfig: { hosts: mdetailHosts = {} } = {}, name: mdetailName } = - useMemo>( - () => manifestDetail ?? {}, - [manifestDetail], - ); + const { + hostConfig: { hosts: mdetailHosts = {} } = {}, + name: mdetailName, + uuid: mdetailUuid, + } = useMemo>( + () => manifestDetail ?? {}, + [manifestDetail], + ); const { domain: mtemplateDomain, fences: knownFences, @@ -283,19 +288,42 @@ const ManageManifestPanel: FC = () => { ), loading: isLoadingManifestDetail, onSubmitAppend: (...args) => { - getRunFormData(mdetailHosts, ...args); + const body = getRunFormData(mdetailHosts, ...args); + + setConfirmDialogProps({ + actionProceedText: 'Run', + content: <>, + onProceedAppend: () => { + setIsSubmittingManifestExecution(true); + + api + .put(`/command/run-manifest/${mdetailUuid}`, body) + .catch((apiError) => { + handleAPIError(apiError); + }) + .finally(() => { + setIsSubmittingManifestExecution(false); + }); + }, + titleText: `Are you sure you want to run manifest ${mdetailName}?`, + }); + + confirmDialogRef.current.setOpen?.call(null, true); }, titleText: `Run install manifest ${mdetailName}`, }), [ - mdetailName, - hostOverviews, - mdetailHosts, - isLoadingManifestDetail, + runFormUtils, knownFences, + hostOverviews, knownUpses, manifestDetail, - runFormUtils, + isLoadingManifestDetail, + mdetailName, + mdetailHosts, + setConfirmDialogProps, + setIsSubmittingManifestExecution, + mdetailUuid, ], ); @@ -446,11 +474,16 @@ const ManageManifestPanel: FC = () => { - + ); }; From 3ab1fe88d22dc39d01ed0c0c372465c870cd5f6c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 4 Apr 2023 12:44:36 -0400 Subject: [PATCH 84/86] fix(striker-ui): connect manifest-related forms submission with confirm dialog --- .../ManageManifest/ManageManifestPanel.tsx | 121 +++++++++++++++--- 1 file changed, 101 insertions(+), 20 deletions(-) diff --git a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx index f2b25c14..a6dbdc3f 100644 --- a/striker-ui/components/ManageManifest/ManageManifestPanel.tsx +++ b/striker-ui/components/ManageManifest/ManageManifestPanel.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useMemo, useRef, useState } from 'react'; +import { FC, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; @@ -45,6 +45,8 @@ import useFormUtils from '../../hooks/useFormUtils'; import useIsFirstRender from '../../hooks/useIsFirstRender'; import useProtectedState from '../../hooks/useProtectedState'; +const MSG_ID_API = 'api'; + const getFormData = ( ...[{ target }]: DivFormEventHandlerParameters ): APIBuildManifestRequestBody => { @@ -161,7 +163,7 @@ const ManageManifestPanel: FC = () => { useProtectedState(true); const [isLoadingManifestTemplate, setIsLoadingManifestTemplate] = useProtectedState(true); - const [isSubmittingManifestExecution, setIsSubmittingManifestExecution] = + const [isSubmittingForm, setIsSubmittingForm] = useProtectedState(false); const [manifestDetail, setManifestDetail] = useProtectedState< APIManifestDetail | undefined @@ -186,7 +188,7 @@ const ManageManifestPanel: FC = () => { ], messageGroupRef, ); - const { isFormInvalid } = formUtils; + const { isFormInvalid, setMessage } = formUtils; const runFormUtils = useFormUtils( [ @@ -217,6 +219,41 @@ const ManageManifestPanel: FC = () => { [manifestTemplate], ); + const submitForm = useCallback( + ({ + body, + getErrorMsg, + method, + successMsg, + url, + }: { + body: Record; + getErrorMsg: (parentMsg: ReactNode) => ReactNode; + method: 'post' | 'put'; + successMsg: ReactNode; + url: string; + }) => { + setIsSubmittingForm(true); + + api[method](url, body) + .then(() => { + setMessage(MSG_ID_API, { + children: successMsg, + }); + }) + .catch((apiError) => { + const emsg = handleAPIError(apiError); + + emsg.children = getErrorMsg(emsg.children); + setMessage(MSG_ID_API, emsg); + }) + .finally(() => { + setIsSubmittingForm(false); + }); + }, + [setIsSubmittingForm, setMessage], + ); + const addManifestFormDialogProps = useMemo( () => ({ actionProceedText: 'Add', @@ -233,7 +270,26 @@ const ManageManifestPanel: FC = () => { /> ), onSubmitAppend: (...args) => { - getFormData(...args); + const body = getFormData(...args); + + setConfirmDialogProps({ + actionProceedText: 'Add', + content: <>, + onProceedAppend: () => { + submitForm({ + body, + getErrorMsg: (parentMsg) => ( + <>Failed to add install manifest. {parentMsg} + ), + method: 'post', + successMsg: 'Successfully added install manifest', + url: '/manifest', + }); + }, + titleText: `Add install manifest?`, + }); + + confirmDialogRef.current.setOpen?.call(null, true); }, titleText: 'Add an install manifest', }), @@ -244,6 +300,8 @@ const ManageManifestPanel: FC = () => { mtemplateDomain, mtemplatePrefix, mtemplateSequence, + setConfirmDialogProps, + submitForm, ], ); @@ -259,18 +317,40 @@ const ManageManifestPanel: FC = () => { /> ), onSubmitAppend: (...args) => { - getFormData(...args); + const body = getFormData(...args); + + setConfirmDialogProps({ + actionProceedText: 'Edit', + content: <>, + onProceedAppend: () => { + submitForm({ + body, + getErrorMsg: (parentMsg) => ( + <>Failed to update install manifest. {parentMsg} + ), + method: 'put', + successMsg: `Successfully updated install manifest ${mdetailName}`, + url: `/manifest/${mdetailUuid}`, + }); + }, + titleText: `Update install manifest ${mdetailName}?`, + }); + + confirmDialogRef.current.setOpen?.call(null, true); }, loading: isLoadingManifestDetail, titleText: `Update install manifest ${mdetailName}`, }), [ formUtils, - isLoadingManifestDetail, knownFences, knownUpses, - mdetailName, manifestDetail, + isLoadingManifestDetail, + mdetailName, + setConfirmDialogProps, + submitForm, + mdetailUuid, ], ); @@ -294,18 +374,17 @@ const ManageManifestPanel: FC = () => { actionProceedText: 'Run', content: <>, onProceedAppend: () => { - setIsSubmittingManifestExecution(true); - - api - .put(`/command/run-manifest/${mdetailUuid}`, body) - .catch((apiError) => { - handleAPIError(apiError); - }) - .finally(() => { - setIsSubmittingManifestExecution(false); - }); + submitForm({ + body, + getErrorMsg: (parentMsg) => ( + <>Failed to run install manifest. {parentMsg} + ), + method: 'put', + successMsg: `Successfully ran install manifest ${mdetailName}`, + url: `/command/run-manifest/${mdetailUuid}`, + }); }, - titleText: `Are you sure you want to run manifest ${mdetailName}?`, + titleText: `Run install manifest ${mdetailName}?`, }); confirmDialogRef.current.setOpen?.call(null, true); @@ -322,7 +401,7 @@ const ManageManifestPanel: FC = () => { mdetailName, mdetailHosts, setConfirmDialogProps, - setIsSubmittingManifestExecution, + submitForm, mdetailUuid, ], ); @@ -460,6 +539,7 @@ const ManageManifestPanel: FC = () => { { { Date: Tue, 4 Apr 2023 14:26:56 -0400 Subject: [PATCH 85/86] fix(striker-ui-api): correct ntp and network gateway asserts in build manifest --- .../request_handlers/manifest/buildManifest.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts index 5a3d4908..249499d8 100644 --- a/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts +++ b/striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts @@ -28,7 +28,7 @@ export const buildManifest = ( hostConfig: { hosts: hostList = {} } = {}, networkConfig: { dnsCsv: rawDns, - mtu: rawMtu, + mtu: rawMtu = 1500, networks: networkList = {}, ntpCsv: rawNtp, } = {}, @@ -59,7 +59,10 @@ export const buildManifest = ( assert(REP_INTEGER.test(String(mtu)), `MTU must be an integer; got [${mtu}]`); const ntp = sanitize(rawNtp, 'string'); - assert(REP_IPV4_CSV.test(ntp), `NTP must be an IPv4 CSV; got [${ntp}]`); + + if (ntp) { + assert(REP_IPV4_CSV.test(ntp), `NTP must be an IPv4 CSV; got [${ntp}]`); + } const prefix = sanitize(rawPrefix, 'string'); assert( @@ -103,10 +106,13 @@ export const buildManifest = ( const networkId = `${networkType}${networkNumber}`; const gateway = sanitize(rawGateway, 'string'); - assert( - REP_IPV4.test(gateway), - `Gateway of ${networkId} must be an IPv4; got [${gateway}]`, - ); + + if (networkType === 'ifn') { + assert( + REP_IPV4.test(gateway), + `Gateway of ${networkId} must be an IPv4; got [${gateway}]`, + ); + } const minIp = sanitize(rawMinIp, 'string'); assert( From 594062e823dfd0003e834f91fbaa0fd36028b4ad Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 4 Apr 2023 15:24:07 -0400 Subject: [PATCH 86/86] fix(striker-ui): add IPMI IP to host inputs in manage manifest --- .../ManageManifest/AnHostConfigInputGroup.tsx | 11 ++-- .../ManageManifest/AnHostInputGroup.tsx | 65 ++++++++++++++++++- striker-ui/types/ManageManifest.d.ts | 3 +- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx index 46e35075..6f27710c 100644 --- a/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostConfigInputGroup.tsx @@ -47,13 +47,15 @@ const AnHostConfigInputGroup = ({ fences: previousFenceList = {}, hostNumber, hostType, + ipmiIp, networks: previousNetworkList = {}, upses: previousUpsList = {}, }: ManifestHost = previousHostArgs; const fences = knownFenceListValues.reduce( (fenceList, { fenceName }) => { - const { fencePort = '' } = previousFenceList[fenceName] ?? {}; + const { [fenceName]: { fencePort = '' } = {} } = + previousFenceList; fenceList[fenceName] = { fenceName, fencePort }; @@ -63,7 +65,8 @@ const AnHostConfigInputGroup = ({ ); const networks = networkListEntries.reduce( (networkList, [networkId, { networkNumber, networkType }]) => { - const { networkIp = '' } = previousNetworkList[networkId] ?? {}; + const { [networkId]: { networkIp = '' } = {} } = + previousNetworkList; networkList[networkId] = { networkIp, @@ -77,7 +80,7 @@ const AnHostConfigInputGroup = ({ ); const upses = knownUpsListValues.reduce( (upsList, { upsName }) => { - const { isUsed = true } = previousUpsList[upsName] ?? {}; + const { [upsName]: { isUsed = true } = {} } = previousUpsList; upsList[upsName] = { isUsed, upsName }; @@ -95,7 +98,7 @@ const AnHostConfigInputGroup = ({ hostId={hostId} hostNumber={hostNumber} hostType={hostType} - previous={{ fences, networks, upses }} + previous={{ fences, ipmiIp, networks, upses }} /> ), md: 3, diff --git a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx index a1ee9aff..e0153b35 100644 --- a/striker-ui/components/ManageManifest/AnHostInputGroup.tsx +++ b/striker-ui/components/ManageManifest/AnHostInputGroup.tsx @@ -1,7 +1,5 @@ import { ReactElement, useMemo } from 'react'; -import NETWORK_TYPES from '../../lib/consts/NETWORK_TYPES'; - import FlexBox from '../FlexBox'; import Grid from '../Grid'; import InputWithRef from '../InputWithRef'; @@ -18,6 +16,8 @@ const INPUT_ID_PREFIX_AN_HOST = 'an-host-input'; const INPUT_CELL_ID_PREFIX_AH = `${INPUT_ID_PREFIX_AN_HOST}-cell`; +const INPUT_LABEL_AH_IPMI_IP = 'IPMI IP'; + const MAP_TO_AH_INPUT_HANDLER: MapToManifestFormInputHandler = { fence: (container, input) => { const { @@ -48,6 +48,19 @@ const MAP_TO_AH_INPUT_HANDLER: MapToManifestFormInputHandler = { hostType, }; }, + ipmi: (container, input) => { + const { + dataset: { hostId = '' }, + value: ipmiIp, + } = input; + const { + hostConfig: { + hosts: { [hostId]: host }, + }, + } = container; + + host.ipmiIp = ipmiIp; + }, network: (container, input) => { const { dataset: { @@ -99,6 +112,9 @@ const GRID_SPACING = '1em'; const buildInputIdAHFencePort = (hostId: string, fenceId: string): string => `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-${fenceId}-port`; +const buildInputIdAHIpmiIp = (hostId: string): string => + `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-ipmi-ip`; + const buildInputIdAHNetworkIp = (hostId: string, networkId: string): string => `${INPUT_ID_PREFIX_AN_HOST}-${hostId}-${networkId}-ip`; @@ -117,6 +133,7 @@ const AnHostInputGroup = ({ hostType, previous: { fences: fenceList = {}, + ipmiIp: previousIpmiIp, networks: networkList = {}, upses: upsList = {}, } = {}, @@ -142,6 +159,12 @@ const AnHostInputGroup = ({ () => `${INPUT_ID_PREFIX_AN_HOST}-${hostId}`, [hostId], ); + const inputIdAHIpmiIp = useMemo(() => buildInputIdAHIpmiIp(hostId), [hostId]); + + const inputCellIdAHIpmiIp = useMemo( + () => `${INPUT_CELL_ID_PREFIX_AH}-${hostId}-ipmi-ip`, + [hostId], + ); const fenceListGridLayout = useMemo( () => @@ -204,7 +227,7 @@ const AnHostInputGroup = ({ const cellId = `${INPUT_CELL_ID_PREFIX_AH}-${hostId}-${networkId}-ip`; const inputId = buildInputIdAHNetworkIp(hostId, networkId); - const inputLabel = `${NETWORK_TYPES[networkType]} ${networkNumber}`; + const inputLabel = `${networkType.toUpperCase()} ${networkNumber} IP`; previous[cellId] = { children: ( @@ -323,6 +346,41 @@ const AnHostInputGroup = ({ columns={GRID_COLUMNS} layout={{ ...networkListGridLayout, + [inputCellIdAHIpmiIp]: { + children: ( + + } + inputTestBatch={buildIPAddressTestBatch( + `${hostId} ${INPUT_LABEL_AH_IPMI_IP}`, + () => { + setMessage(inputIdAHIpmiIp); + }, + { + onFinishBatch: + buildFinishInputTestBatchFunction(inputIdAHIpmiIp), + }, + (message) => { + setMessage(inputIdAHIpmiIp, { children: message }); + }, + )} + onFirstRender={buildInputFirstRenderFunction( + inputIdAHIpmiIp, + )} + onUnmount={buildInputUnmountFunction(inputIdAHIpmiIp)} + required + /> + ), + }, ...fenceListGridLayout, }} spacing={GRID_SPACING} @@ -338,6 +396,7 @@ export { INPUT_ID_PREFIX_AN_HOST, MAP_TO_AH_INPUT_HANDLER, buildInputIdAHFencePort, + buildInputIdAHIpmiIp, buildInputIdAHNetworkIp, buildInputIdAHUpsPowerHost, }; diff --git a/striker-ui/types/ManageManifest.d.ts b/striker-ui/types/ManageManifest.d.ts index 30e03cc6..bc2aa6c3 100644 --- a/striker-ui/types/ManageManifest.d.ts +++ b/striker-ui/types/ManageManifest.d.ts @@ -51,6 +51,7 @@ type ManifestHost = { hostName?: string; hostNumber: number; hostType: string; + ipmiIp?: string; networks?: ManifestHostNetworkList; upses?: ManifestHostUpsList; }; @@ -122,7 +123,7 @@ type AnNetworkInputGroupProps = type AnHostInputGroupOptionalProps = { hostLabel?: string; - previous?: Pick; + previous?: Pick; }; type AnHostInputGroupProps =