Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Change range property of IPv6SLAAC to be optional ([#13209](https://github.com/linode/manager/pull/13209))
2 changes: 1 addition & 1 deletion packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export interface ConfigInterfaceIPv4 {

export interface IPv6SLAAC {
address?: string;
range: string;
range?: string;
}

// The legacy interface type - for Configuration Profile Interfaces
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add VPC IPv6 support in Linode Add/Edit Config dialog ([#13209](https://github.com/linode/manager/pull/13209))
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
FirewallCell,
LKEClusterCell,
} from './LinodeEntityDetailRowInterfaceFirewall';
import { DEFAULT_UPGRADE_BUTTON_HELPER_TEXT } from './LinodesDetail/LinodeConfigs/LinodeConfigs';
import { DEFAULT_UPGRADE_BUTTON_HELPER_TEXT } from './LinodesDetail/LinodeConfigs/constants';
import { getUnableToUpgradeTooltipText } from './LinodesDetail/LinodeConfigs/UpgradeInterfaces/utils';

import type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import {
import 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import {
LinodeConfigDialog,
padList,
unrecommendedConfigNoticeSelector,
} from './LinodeConfigDialog';
import { LinodeConfigDialog } from './LinodeConfigDialog';
import { padList, unrecommendedConfigNoticeSelector } from './utilities';

import type { MemoryLimit } from './LinodeConfigDialog';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ import { FormLabel } from 'src/components/FormLabel';
import { Link } from 'src/components/Link';
import { DeviceSelection } from 'src/features/Linodes/LinodesDetail/LinodeRescue/DeviceSelection';
import { titlecase } from 'src/features/Linodes/presentation';
import {
LINODE_UNREACHABLE_HELPER_TEXT,
NATTED_PUBLIC_IP_HELPER_TEXT,
NOT_NATTED_HELPER_TEXT,
} from 'src/features/VPCs/constants';
import {
handleFieldErrors,
handleGeneralErrors,
Expand All @@ -68,7 +63,11 @@ import {
StyledFormGroup,
StyledRadioGroup,
} from './LinodeConfigDialog.styles';
import { getPrimaryInterfaceIndex, useGetDeviceLimit } from './utilities';
import {
getPrimaryInterfaceIndex,
unrecommendedConfigNoticeSelector,
useGetDeviceLimit,
} from './utilities';

import type { ExtendedInterface } from '../LinodeSettings/InterfaceSelect';
import type {
Expand All @@ -92,7 +91,7 @@ type RunLevel = 'binbash' | 'default' | 'single';
type VirtMode = 'fullvirt' | 'paravirt';
export type MemoryLimit = 'no_limit' | 'set_limit';

interface EditableFields {
export interface EditableFields {
comments?: string;
devices: DevicesAsStrings;
helpers: Helpers;
Expand Down Expand Up @@ -181,6 +180,7 @@ const interfacesToState = (interfaces?: Interface[] | null) => {
ip_ranges,
ipam_address,
ipv4,
ipv6,
label,
primary,
purpose,
Expand All @@ -191,6 +191,7 @@ const interfacesToState = (interfaces?: Interface[] | null) => {
ip_ranges,
ipam_address,
ipv4,
ipv6,
label,
primary,
purpose,
Expand Down Expand Up @@ -1049,7 +1050,7 @@ export const LinodeConfigDialog = (props: Props) => {
/>
</>
{values.interfaces?.map((thisInterface, idx) => {
const thisInterfaceIPRanges: ExtendedIP[] = (
const thisInterfaceIPv4Ranges: 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"
Expand All @@ -1069,6 +1070,26 @@ export const LinodeConfigDialog = (props: Props) => {
};
});

const thisInterfaceIPv6Ranges: ExtendedIP[] = (
thisInterface.ipv6?.ranges ?? []
).map((ipv6Range, index) => {
// Display a more user-friendly error to the user as opposed to, for example, "interfaces[1].ipv6.ranges[1] is invalid"
// @ts-expect-error this form intentionally breaks formik's error type
const errorString: string = formik.errors[
`interfaces[${idx}].ipv6.ranges[${index}].range`
]?.includes('is invalid')
? 'Invalid IPv6 range'
: // @ts-expect-error this form intentionally breaks formik's error type
formik.errors[
`interfaces[${idx}].ipv6.ranges[${index}].range`
];

return {
address: ipv6Range.range,
error: errorString,
};
});

return (
<React.Fragment key={`${idx}-interface`}>
{unrecommendedConfigNoticeSelector({
Expand All @@ -1078,7 +1099,8 @@ export const LinodeConfigDialog = (props: Props) => {
values,
})}
<InterfaceSelect
additionalIPv4RangesForVPC={thisInterfaceIPRanges}
additionalIPv4RangesForVPC={thisInterfaceIPv4Ranges}
additionalIPv6RangesForVPC={thisInterfaceIPv6Ranges}
errors={{
ipRangeError:
// @ts-expect-error this form intentionally breaks formik's error type
Expand All @@ -1104,6 +1126,11 @@ export const LinodeConfigDialog = (props: Props) => {
vpcIPv4Error:
// @ts-expect-error this form intentionally breaks formik's error type
formik.errors[`interfaces[${idx}].ipv4.vpc`],
vpcIPv6Error:
// @ts-expect-error this form intentionally breaks formik's error type
formik.errors[
`interfaces[${idx}].ipv6.slaac[0].range`
],
}}
handleChange={(newInterface: ExtendedInterface) => {
handleInterfaceChange(idx, newInterface);
Expand All @@ -1122,6 +1149,12 @@ export const LinodeConfigDialog = (props: Props) => {
subnetId={thisInterface.subnet_id}
vpcId={thisInterface.vpc_id}
vpcIPv4={thisInterface.ipv4?.vpc ?? undefined}
vpcIPv6={
thisInterface.ipv6?.slaac?.[0]?.range ?? undefined
}
vpcIPv6IsPublic={
thisInterface.ipv6?.is_public ?? false
}
/>
</React.Fragment>
);
Expand Down Expand Up @@ -1243,76 +1276,3 @@ const DialogContent = (props: ConfigFormProps) => {

const isUsingCustomRoot = (value: string) =>
pathsOptionsLabels.includes(value) === false;

const noticeForScenario = (scenarioText: string) => (
<Notice
data-testid={'notice-for-unrecommended-scenario'}
text={scenarioText}
variant="warning"
/>
);

/**
* Returns a JSX warning notice if the current network interface configuration
* is unrecommended and may lead to undesired or unsupported behavior.
*
* @param _interface the current config interface being passed in
* @param primaryInterfaceIndex the index of the primary interface
* @param thisIndex the index of the current config interface within the `interfaces` array of the `config` object
* @param values the values held in Formik state, having a type of `EditableFields`
* @returns JSX.Element | null
*/
export const unrecommendedConfigNoticeSelector = ({
_interface,
primaryInterfaceIndex,
thisIndex,
values,
}: {
_interface: ExtendedInterface;
primaryInterfaceIndex: null | number;
thisIndex: number;
values: EditableFields;
}): JSX.Element | null => {
const vpcInterface = _interface.purpose === 'vpc';
const nattedIPv4Address = Boolean(_interface.ipv4?.nat_1_1);

const filteredInterfaces =
values.interfaces?.filter((_interface) => _interface.purpose !== 'none') ??
[];

// Edge case: users w/ ability to have multiple VPC interfaces. Scenario 1 & 2 notices not helpful if that's done
const primaryInterfaceIsVPC =
primaryInterfaceIndex !== null &&
values.interfaces &&
values.interfaces[primaryInterfaceIndex].purpose === 'vpc';

/*
Scenario 1:
- the interface passed in to this function is a VPC interface
- the index of the primary interface !== the index of the interface passed in to this function
- nattedIPv4Address (i.e., "Assign a public IPv4 address for this Linode" checked)

Scenario 2:
- all of Scenario 1, except: !nattedIPv4Address (i.e., "Assign a public IPv4 address for this Linode" unchecked)

Scenario 3:
- only eth0 populated, and it is a VPC interface

If not one of the above scenarios, do not display a warning notice re: configuration
*/
if (
vpcInterface &&
primaryInterfaceIndex !== thisIndex &&
!primaryInterfaceIsVPC
) {
return nattedIPv4Address
? noticeForScenario(NATTED_PUBLIC_IP_HELPER_TEXT)
: noticeForScenario(LINODE_UNREACHABLE_HELPER_TEXT);
}

if (filteredInterfaces.length === 1 && vpcInterface && !nattedIPv4Address) {
return noticeForScenario(NOT_NATTED_HELPER_TEXT);
}

return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
useGrants,
useLinodeQuery,
} from '@linode/queries';
import { Box, Button, Typography } from '@linode/ui';
import { Box, Button } from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import { useNavigate, useParams } from '@tanstack/react-router';
import * as React from 'react';
Expand All @@ -28,24 +28,11 @@ import { useIsLinodeInterfacesEnabled } from 'src/utilities/linodes';
import { useLinodeDetailContext } from '../LinodesDetailContext';
import { BootConfigDialog } from './BootConfigDialog';
import { ConfigRow } from './ConfigRow';
import { DEFAULT_UPGRADE_BUTTON_HELPER_TEXT } from './constants';
import { DeleteConfigDialog } from './DeleteConfigDialog';
import { LinodeConfigDialog } from './LinodeConfigDialog';
import { getUnableToUpgradeTooltipText } from './UpgradeInterfaces/utils';

export const DEFAULT_UPGRADE_BUTTON_HELPER_TEXT = (
<>
<Typography>
Configuration Profile interfaces from a single profile can be upgraded to
Linode Interfaces.
</Typography>
<Typography mt={2}>
After the upgrade, the Linode can only use Linode Interfaces and cannot
revert to Configuration Profile interfaces. Use the dry-run feature to
review the changes before committing.
</Typography>
</>
);

const LinodeConfigs = () => {
const theme = useTheme();
const navigate = useNavigate();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Typography } from '@linode/ui';
import * as React from 'react';

export const deviceSlots = [
'sda',
'sdb',
Expand Down Expand Up @@ -133,3 +136,17 @@ export const pathsOptions = [
];

export const pathsOptionsLabels = pathsOptions.map((path) => path.label);

export const DEFAULT_UPGRADE_BUTTON_HELPER_TEXT = (
<>
<Typography>
Configuration Profile interfaces from a single profile can be upgraded to
Linode Interfaces.
</Typography>
<Typography mt={2}>
After the upgrade, the Linode can only use Linode Interfaces and cannot
revert to Configuration Profile interfaces. Use the dry-run feature to
review the changes before committing.
</Typography>
</>
);

This file was deleted.

Loading