-
Notifications
You must be signed in to change notification settings - Fork 367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: [M3-7645] β Support VPC IPv4 Ranges in Add/Edit Linode Config dialog #10170
Changes from 3 commits
4239c58
934c0b7
42fd6f2
48ec35b
f6f8ea3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Added | ||
--- | ||
|
||
Support VPC IPv4 Ranges in Add/Edit Linode Config dialog ([#10170](https://github.com/linode/manager/pull/10170)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,8 +38,6 @@ import { | |
NATTED_PUBLIC_IP_HELPER_TEXT, | ||
NOT_NATTED_HELPER_TEXT, | ||
} from 'src/features/VPCs/constants'; | ||
import { useAccountManagement } from 'src/hooks/useAccountManagement'; | ||
import { useFlags } from 'src/hooks/useFlags'; | ||
import { | ||
useLinodeConfigCreateMutation, | ||
useLinodeConfigUpdateMutation, | ||
|
@@ -53,7 +51,6 @@ import { useRegionsQuery } from 'src/queries/regions'; | |
import { queryKey as vlansQueryKey } from 'src/queries/vlans'; | ||
import { useAllVolumesQuery } from 'src/queries/volumes'; | ||
import { vpcQueryKey } from 'src/queries/vpcs'; | ||
import { isFeatureEnabled } from 'src/utilities/accountCapabilities'; | ||
import { | ||
DevicesAsStrings, | ||
createDevicesFromStrings, | ||
|
@@ -64,6 +61,7 @@ import { | |
handleGeneralErrors, | ||
} from 'src/utilities/formikErrorUtils'; | ||
import { getSelectedOptionFromGroupedOptions } from 'src/utilities/getSelectedOptionFromGroupedOptions'; | ||
import { ExtendedIP } from 'src/utilities/ipUtils'; | ||
import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; | ||
|
||
import { | ||
|
@@ -116,6 +114,7 @@ interface Props { | |
} | ||
|
||
const defaultInterface = { | ||
ip_ranges: [], | ||
ipam_address: '', | ||
label: '', | ||
purpose: 'none', | ||
|
@@ -183,6 +182,7 @@ const interfacesToState = (interfaces?: Interface[]) => { | |
const interfacesPayload = interfaces.map( | ||
({ | ||
id, | ||
ip_ranges, | ||
ipam_address, | ||
ipv4, | ||
label, | ||
|
@@ -192,6 +192,7 @@ const interfacesToState = (interfaces?: Interface[]) => { | |
vpc_id, | ||
}) => ({ | ||
id, | ||
ip_ranges, | ||
ipam_address, | ||
ipv4, | ||
label, | ||
|
@@ -262,8 +263,6 @@ export const LinodeConfigDialog = (props: Props) => { | |
); | ||
|
||
const theme = useTheme(); | ||
const flags = useFlags(); | ||
const { account } = useAccountManagement(); | ||
|
||
const regions = useRegionsQuery().data ?? []; | ||
|
||
|
@@ -291,13 +290,6 @@ export const LinodeConfigDialog = (props: Props) => { | |
thisRegion.capabilities.includes('VPCs') | ||
); | ||
|
||
// @TODO VPC: remove once VPC is fully rolled out | ||
const vpcEnabled = isFeatureEnabled( | ||
'VPCs', | ||
Boolean(flags.vpc), | ||
account?.capabilities ?? [] | ||
); | ||
|
||
const { resetForm, setFieldValue, values, ...formik } = useFormik({ | ||
initialValues: defaultFieldsValues, | ||
onSubmit: (values) => onSubmit(values), | ||
|
@@ -493,7 +485,7 @@ export const LinodeConfigDialog = (props: Props) => { | |
(_interface) => _interface.primary === true | ||
); | ||
|
||
if (vpcEnabled && indexOfExistingPrimaryInterface !== -1) { | ||
if (indexOfExistingPrimaryInterface !== -1) { | ||
setPrimaryInterfaceIndex(indexOfExistingPrimaryInterface); | ||
} | ||
|
||
|
@@ -523,7 +515,7 @@ export const LinodeConfigDialog = (props: Props) => { | |
setPrimaryInterfaceIndex(0); | ||
} | ||
} | ||
}, [open, config, initrdFromConfig, resetForm, queryClient, vpcEnabled]); | ||
}, [open, config, initrdFromConfig, resetForm, queryClient]); | ||
|
||
const generalError = formik.status?.generalError; | ||
|
||
|
@@ -667,12 +659,9 @@ export const LinodeConfigDialog = (props: Props) => { | |
|
||
const networkInterfacesHelperText = ( | ||
<Typography> | ||
Configure the network that a selected interface will connect to ( | ||
{vpcEnabled | ||
? '"Public Internet", VLAN, or VPC' | ||
: 'either "Public Internet" or a VLAN'} | ||
) . Each Linode can have up to three Network Interfaces. For more | ||
information, see our{' '} | ||
Configure the network that a selected interface will connect to | ||
"Public Internet", VLAN, or VPC. Each Linode can have up to | ||
three Network Interfaces. For more information, see our{' '} | ||
<Link to="https://www.linode.com/docs/products/networking/vlans/guides/attach-to-compute-instance/#attaching-a-vlan-to-an-existing-compute-instance"> | ||
Network Interfaces guide | ||
</Link> | ||
|
@@ -951,9 +940,7 @@ export const LinodeConfigDialog = (props: Props) => { | |
|
||
<Grid xs={12}> | ||
<Box alignItems="center" display="flex"> | ||
<Typography variant="h3"> | ||
{vpcEnabled ? 'Networking' : 'Network Interfaces'} | ||
</Typography> | ||
<Typography variant="h3">Networking</Typography> | ||
<TooltipIcon | ||
sxTooltipIcon={{ | ||
paddingBottom: 0, | ||
|
@@ -971,30 +958,44 @@ export const LinodeConfigDialog = (props: Props) => { | |
variant="error" | ||
/> | ||
)} | ||
{vpcEnabled && ( | ||
<> | ||
<Select | ||
defaultValue={ | ||
primaryInterfaceOptions[primaryInterfaceIndex ?? 0] | ||
} | ||
data-testid="primary-interface-dropdown" | ||
disabled={isReadOnly} | ||
isClearable={false} | ||
label="Primary Interface (Default Route)" | ||
onChange={handlePrimaryInterfaceChange} | ||
options={getPrimaryInterfaceOptions(values.interfaces)} | ||
/> | ||
<Divider | ||
sx={{ | ||
margin: `${theme.spacing( | ||
4.5 | ||
)} ${theme.spacing()} ${theme.spacing(1.5)} `, | ||
width: `calc(100% - ${theme.spacing(2)})`, | ||
}} | ||
/> | ||
</> | ||
)} | ||
<> | ||
<Select | ||
defaultValue={ | ||
primaryInterfaceOptions[primaryInterfaceIndex ?? 0] | ||
} | ||
data-testid="primary-interface-dropdown" | ||
disabled={isReadOnly} | ||
isClearable={false} | ||
label="Primary Interface (Default Route)" | ||
onChange={handlePrimaryInterfaceChange} | ||
options={getPrimaryInterfaceOptions(values.interfaces)} | ||
/> | ||
<Divider | ||
sx={{ | ||
margin: `${theme.spacing( | ||
4.5 | ||
)} ${theme.spacing()} ${theme.spacing(1.5)} `, | ||
width: `calc(100% - ${theme.spacing(2)})`, | ||
}} | ||
/> | ||
</> | ||
{values.interfaces.map((thisInterface, idx) => { | ||
const thisInterfaceIPRanges: ExtendedIP[] = ( | ||
thisInterface.ip_ranges ?? [] | ||
).map((ip_range, index) => { | ||
// Display a more user-friendly error to the user as opposed to, for example, "interfaces[1].ip_ranges[1] is invalid" | ||
const errorString: string = formik.errors[ | ||
`interfaces[${idx}].ip_ranges[${index}]` | ||
]?.includes('is invalid') | ||
? 'Invalid IP range' | ||
: formik.errors[`interfaces[${idx}].ip_ranges[${index}]`]; | ||
|
||
return { | ||
address: ip_range, | ||
error: errorString, | ||
}; | ||
}); | ||
Comment on lines
+983
to
+997
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because these are deeply nested errors that we're trying to surface, it required a bit of less-than-ideal logic and mapping. I am aware of one unfortunate shortcomingβif you have multiple IP ranges but one of them (not the last one in the sequence) gets an error when you click "Save Changes", clicking the "X" to remove that IP range will result in the error moving onto the range that moves up to occupy that index. Sadly, I could not see a way around this, as I tried having something like |
||
|
||
return ( | ||
<React.Fragment key={`${idx}-interface`}> | ||
{unrecommendedConfigNoticeSelector({ | ||
|
@@ -1005,6 +1006,8 @@ export const LinodeConfigDialog = (props: Props) => { | |
})} | ||
<InterfaceSelect | ||
errors={{ | ||
ipRangeError: | ||
formik.errors[`interfaces[${idx}].ip_ranges`], | ||
ipamError: | ||
formik.errors[`interfaces[${idx}].ipam_address`], | ||
labelError: formik.errors[`interfaces[${idx}].label`], | ||
|
@@ -1021,6 +1024,7 @@ export const LinodeConfigDialog = (props: Props) => { | |
handleChange={(newInterface: Interface) => | ||
handleInterfaceChange(idx, newInterface) | ||
} | ||
additionalIPv4RangesForVPC={thisInterfaceIPRanges} | ||
ipamAddress={thisInterface.ipam_address} | ||
key={`eth${idx}-interface`} | ||
label={thisInterface.label} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the
"
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ESLint recommended escaping them