From b269770d3a186fdece2385c06eb4b554698018c8 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Mar 2023 23:03:55 -0400 Subject: [PATCH] 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; }; };