Skip to content

Commit

Permalink
Add networking bonding configuration in the UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ammont82 committed Oct 23, 2024
1 parent 33878da commit 25b27d8
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 86 deletions.
4 changes: 4 additions & 0 deletions libs/locales/lib/en/translation.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"ai: The bond associated with the host will be removed.": " The bond associated with the host will be removed.",
"ai:({{size}} hosts available)": "({{size}} hosts available)",
"ai:(1 host available)": "(1 host available)",
"ai:(no hosts available)": "(no hosts available)",
Expand Down Expand Up @@ -658,6 +659,9 @@
"ai:Registering": "Registering",
"ai:Release domain resolution": "Release domain resolution",
"ai:Remove": "Remove",
"ai:Remove bond": "Remove bond",
"ai:Remove bond dialog": "Remove bond dialog",
"ai:Remove bond?": "Remove bond?",
"ai:Remove from the cluster": "Remove from the cluster",
"ai:Remove host": "Remove host",
"ai:Remove host?": "Remove host?",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as React from 'react';
import {
Button,
ButtonVariant,
Modal,
ModalBoxBody,
ModalBoxFooter,
Stack,
StackItem,
} from '@patternfly/react-core';

import { useTranslation } from '../../../../../../common/hooks/use-translation-wrapper';

type BondDeleteModalModalProps = {
isOpen: boolean;
onConfirm: VoidFunction;
onCancel: VoidFunction;
};

const BondDeleteModalModal = ({ isOpen, onConfirm, onCancel }: BondDeleteModalModalProps) => {
const { t } = useTranslation();

return (
<Modal
aria-label={t('ai:Remove bond dialog')}
title={t('ai:Remove bond?')}
isOpen={isOpen}
onClose={onCancel}
hasNoBodyWrapper
id="remove-bond-modal"
variant="medium"
titleIconVariant="warning"
>
<ModalBoxBody>
<Stack hasGutter>
<StackItem>{t('ai: The bond associated with the host will be removed.')}</StackItem>
</Stack>
</ModalBoxBody>
<ModalBoxFooter>
<Button onClick={onConfirm} variant={ButtonVariant.danger}>
{t('ai:Remove bond')}
</Button>
<Button onClick={onCancel} variant={ButtonVariant.secondary}>
{t('ai:Cancel')}
</Button>
</ModalBoxFooter>
</Modal>
);
};

export default BondDeleteModalModal;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SelectField } from '../../../../../../common';

type BondsSelectProps = {
onChange?: SelectFieldProps['onChange'];
id: string;
name: string;
};

const bondsList = [
Expand All @@ -16,19 +16,18 @@ const bondsList = [
{ value: 'balance-tlb', label: 'Balance-tlb(5)', default: false },
{ value: 'balance-alb', label: 'Balance-alb(6)', default: false },
];
const BondsSelect: React.FC<BondsSelectProps> = ({ onChange, id }) => {
const BondsSelect: React.FC<BondsSelectProps> = ({ onChange, name }) => {
const selectOptions = React.useMemo(
() =>
bondsList.map((version) => ({
label: version.label,
value: version.value,
selected: version.default,
})),
[],
);
return (
<SelectField
name={id}
name={name}
label="Bond type"
options={selectOptions}
isRequired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { FormGroup, Grid, TextContent, Text, TextVariants, Checkbox } from '@patternfly/react-core';
import { useField } from 'formik';
import { useField, useFormikContext } from 'formik';
import StaticIpHostsArray, { HostComponentProps } from '../StaticIpHostsArray';
import { getFieldId, PopoverIcon } from '../../../../../../common';
import HostSummary from '../CollapsedHost';
Expand All @@ -10,77 +10,107 @@ import { getEmptyFormViewHost } from '../../data/emptyData';
import { OcmInputField } from '../../../../ui/OcmFormFields';
import '../staticIp.css';
import BondsSelect from './BondsSelect';
import BondsConfirmationModal from './BondsConfirmationModal';

const getExpandedHostComponent = (protocolType: StaticProtocolType) => {
const Component: React.FC<HostComponentProps> = ({ fieldName, hostIdx }) => {
const [useBond, setUseBond] = useState(false);
const [bondType, setBondType] = useState('');
// eslint-disable-next-line no-console
console.log(bondType);
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const { setFieldValue } = useFormikContext();
const [bondPrimaryField] = useField(`${fieldName}.bondPrimaryInterface`);
const [bondSecondaryField] = useField(`${fieldName}.bondSecondaryInterface`);

const [useBond, setUseBond] = useState<boolean>(
!!bondPrimaryField.value || !!bondSecondaryField.value,
);

const handleUseBondChange = (checked: boolean) => {
if (!checked) {
setIsModalOpen(true);
} else {
setUseBond(true);
}
};

const handleModalConfirm = () => {
setUseBond(false);
setFieldValue(`${fieldName}.bondType`, 'active-backup');
setFieldValue(`${fieldName}.bondPrimaryInterface`, '');
setFieldValue(`${fieldName}.bondSecondaryInterface`, '');
setIsModalOpen(false);
};

const handleModalCancel = () => {
setIsModalOpen(false);
};
return (
<Grid hasGutter>
<FormGroup>
<Checkbox
label={
<>
{'Use bond'}{' '}
<PopoverIcon
noVerticalAlign
bodyContent="Select this to combine two network interfaces for redundancy and/or increased bandwidth."
/>
</>
}
isChecked={useBond}
onChange={(_event, value) => setUseBond(value)}
id={`use-bond-${hostIdx}`}
/>
</FormGroup>
{useBond && (
<>
<div className="use-bond">
<Grid hasGutter>
<FormGroup fieldId={`bond-type-${hostIdx}`}>
<BondsSelect
id={`bond-type-${hostIdx}`}
onChange={(_event: any, value: string) => {
setBondType(value);
}}
<>
<Grid hasGutter>
<FormGroup>
<Checkbox
label={
<>
{'Use bond'}{' '}
<PopoverIcon
noVerticalAlign
bodyContent="Select this to combine two network interfaces for redundancy and/or increased bandwidth."
/>
</FormGroup>
<OcmInputField
name={`${fieldName}.bondPrimaryInterface`}
label="Port 1 MAC Address"
data-testid={`bond-primary-interface-${hostIdx}`}
/>{' '}
<OcmInputField
name={`${fieldName}.bondSecondaryInterface`}
label="Port 2 MAC Adddress"
data-testid={`bond-secondary-interface-${hostIdx}`}
/>
</Grid>
</div>
</>
)}
<OcmInputField
name={`${fieldName}.macAddress`}
label="MAC Address"
isRequired
data-testid={`mac-address-${hostIdx}`}
/>
{getShownProtocolVersions(protocolType).map((protocolVersion) => (
<FormGroup
label={`IP address (${getProtocolVersionLabel(protocolVersion)})`}
fieldId={getFieldId(`${fieldName}.ips.${protocolVersion}`, 'input')}
key={protocolVersion}
>
<OcmInputField
name={`${fieldName}.ips.${protocolVersion}`}
isRequired
data-testid={`${protocolVersion}-address-${hostIdx}`}
/>{' '}
</>
}
isChecked={useBond}
onChange={(_event, value) => handleUseBondChange(value)}
id={`use-bond-${hostIdx}`}
/>
</FormGroup>
))}
</Grid>
{useBond && (
<>
<div className="use-bond">
<Grid hasGutter>
<FormGroup fieldId={`bond-type-${hostIdx}`}>
<BondsSelect
name={`${fieldName}.bondType`}
data-testid={`bond-type-${hostIdx}`}
/>
</FormGroup>
<OcmInputField
name={`${fieldName}.bondPrimaryInterface`}
label="Port 1 MAC Address"
data-testid={`bond-primary-interface-${hostIdx}`}
/>{' '}
<OcmInputField
name={`${fieldName}.bondSecondaryInterface`}
label="Port 2 MAC Adddress"
data-testid={`bond-secondary-interface-${hostIdx}`}
/>
</Grid>
</div>
</>
)}
<OcmInputField
name={`${fieldName}.macAddress`}
label="MAC Address"
isRequired
data-testid={`mac-address-${hostIdx}`}
/>
{getShownProtocolVersions(protocolType).map((protocolVersion) => (
<FormGroup
label={`IP address (${getProtocolVersionLabel(protocolVersion)})`}
fieldId={getFieldId(`${fieldName}.ips.${protocolVersion}`, 'input')}
key={protocolVersion}
>
<OcmInputField
name={`${fieldName}.ips.${protocolVersion}`}
isRequired
data-testid={`${protocolVersion}-address-${hostIdx}`}
/>{' '}
</FormGroup>
))}
</Grid>
<BondsConfirmationModal
isOpen={isModalOpen}
onConfirm={handleModalConfirm}
onCancel={handleModalCancel}
/>
</>
);
};
return Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export type MachineNetworks = { [protocolVersion in ProtocolVersion]: string };
export type FormViewHost = {
macAddress: string;
ips: HostIps;
bondType: string;
bondPrimaryInterface: string;
bondSecondaryInterface: string;
};

export type StaticFormData = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
isVlanInterface,
NmstateEthernetInterface,
NmstateVlanInterface,
NmstateBondInterface,
NmstateInterfaceType,
} from './nmstateTypes';
import { isDummyInterface } from './dummyData';

Expand Down Expand Up @@ -64,7 +66,7 @@ const getVlanId = (interfaces: NmstateInterface[]): number | null => {
};

const getIpAddress = (
networkInterface: NmstateEthernetInterface | NmstateVlanInterface,
networkInterface: NmstateEthernetInterface | NmstateVlanInterface | NmstateBondInterface,
protocolVersion: ProtocolVersion,
): string => {
const ipAddressData = networkInterface[protocolVersion];
Expand Down Expand Up @@ -145,8 +147,17 @@ const getFormViewHost = (
ipv4: '',
ipv6: '',
},
bondType: 'active-backup',
bondPrimaryInterface: '',
bondSecondaryInterface: '',
};

if (realInterface.type === NmstateInterfaceType.BOND) {
ret.bondType = realInterface['link-aggregation'].mode;
ret.bondPrimaryInterface = infraEnvHost.macInterfaceMap[0].macAddress ?? '';
ret.bondSecondaryInterface = infraEnvHost.macInterfaceMap[1].macAddress ?? '';
}

for (const protocolVersion of getShownProtocolVersions(protocolType)) {
ret.ips[protocolVersion] = getIpAddress(realInterface, protocolVersion);
}
Expand Down
Loading

0 comments on commit 25b27d8

Please sign in to comment.