Skip to content

Commit 239ff0b

Browse files
committed
Merge branch 'vpc-ipv6-remaining' of github.com:linode/manager into vpc-ipv6-remaining
2 parents 8f58cc4 + efce498 commit 239ff0b

File tree

20 files changed

+528
-87
lines changed

20 files changed

+528
-87
lines changed

packages/manager/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6363
- Cypress tests for Premium Plans and Horizontal Resizing ([#12854](https://github.com/linode/manager/pull/12854))
6464
- Cypress test flake in "events-fetching.spec.ts" ([#12875](https://github.com/linode/manager/pull/12875))
6565
- Update Cypress tests following LKE-E postLa feature flag enablement ([#12883](https://github.com/linode/manager/pull/12883))
66+
- Reflect 'Allow public IPv4 access (1:1 NAT)' copy change in E2E and unit tests related to Linode Create and networking flows ([#12885](https://github.com/linode/manager/pull/12885))
6667

6768
### Upcoming Features:
6869

@@ -81,6 +82,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
8182
- ACLP: Add `GlobalFilterGroupByRendererComponent` and `WidgetFilterGroupByRendererComponent` ([#12865](https://github.com/linode/manager/pull/12865))
8283
- Account scope support for ACLP-Alerting firewall dimension filters ([#12879](https://github.com/linode/manager/pull/12879))
8384
- Ability to edit tags on volume details page ([#12800](https://github.com/linode/manager/pull/12800))
85+
- Add VPC IPv6 support in Linode Create flow ([#12885](https://github.com/linode/manager/pull/12885))
86+
- Add VPC IPv6 support in the Linode Details page -> Network tab ([#12856](https://github.com/linode/manager/pull/12856))
8487

8588
## [2025-09-11] - v1.150.1
8689

packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ describe('Create Linode with VPCs (Legacy)', () => {
311311
.click();
312312

313313
// Check box to assign public IPv4.
314-
cy.findByText('Assign a public IPv4 address for this Linode')
314+
cy.findByText('Allow public IPv4 access (1:1 NAT)')
315315
.should('be.visible')
316316
.click();
317317

@@ -548,7 +548,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => {
548548
* - Confirms that outgoing API request contains expected VPC interface data.
549549
* - Confirms newly assigned Linode does not have an unrecommended config notice inside VPC
550550
*/
551-
it('can assign existing VPCs during Linode Create flow (Linode Inteface)', () => {
551+
it('can assign existing VPCs during Linode Create flow (Linode Interface)', () => {
552552
const mockSubnet = subnetFactory.build({
553553
id: randomNumber(),
554554
ipv4: `${randomIp()}/0`,
@@ -820,7 +820,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => {
820820
.click();
821821

822822
// Check box to assign public IPv4.
823-
cy.findByText('Assign a public IPv4 address for this Linode')
823+
cy.findByText('Allow public IPv4 access (1:1 NAT)')
824824
.should('be.visible')
825825
.click();
826826

@@ -1005,7 +1005,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => {
10051005
.click();
10061006

10071007
// Check box to assign public IPv4.
1008-
cy.findByText('Assign a public IPv4 address for this Linode')
1008+
cy.findByText('Allow public IPv4 access (1:1 NAT)')
10091009
.should('be.visible')
10101010
.click();
10111011

packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ describe('Linode Config management', () => {
727727
/*
728728
* - Tests Linode config edit and VPC interface assignment UI flows using mock API data.
729729
* - When the user sets primary interface to eth0, sets eth0 to "Public Internet", and sets eth1 to "VPC", confirm that correct notice appears.
730-
* - When the user sets primary interface to eth0, sets eth0 to "Public Internet", sets eth1 to "VPC", and checks "Assign a public IPv4 address for this Linode", confirm that correct notice appears.
730+
* - When the user sets primary interface to eth0, sets eth0 to "Public Internet", sets eth1 to "VPC", and checks "Allow public IPv4 access (1:1 NAT)", confirm that correct notice appears.
731731
* - Confirms that "REBOOT NEEDED" status indicator appears upon creating VPC config.
732732
*/
733733
it('Creates a new config using non-recommended settings and confirm the informational notices', () => {
@@ -823,7 +823,7 @@ describe('Linode Config management', () => {
823823
cy.findByText(LINODE_UNREACHABLE_HELPER_TEXT).should('be.visible');
824824

825825
// Sets eth0 to "Public Internet", and sets eth1 to "VPC",
826-
// and checks "Assign a public IPv4 address for this Linode"
826+
// and checks "Allow public IPv4 access (1:1 NAT)"
827827
cy.get('[data-qa-textfield-label="VPC"]').scrollIntoView();
828828
cy.get('[data-qa-textfield-label="VPC"]').click();
829829
cy.focused().type(`${mockVPC.label}`);
@@ -840,7 +840,7 @@ describe('Linode Config management', () => {
840840
.findByTitle(`${mockSubnet.label} (${mockSubnet.ipv4})`)
841841
.should('be.visible')
842842
.click();
843-
cy.findByText('Assign a public IPv4 address for this Linode')
843+
cy.findByText('Allow public IPv4 access (1:1 NAT)')
844844
.should('be.visible')
845845
.click();
846846
// Confirm that internet access warning is displayed.

packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Autocomplete,
44
Box,
55
Checkbox,
6+
Divider,
67
FormControlLabel,
78
Notice,
89
Stack,
@@ -14,17 +15,25 @@ import { LinkButton } from '@linode/ui';
1415
import React, { useState } from 'react';
1516
import { Controller, useFormContext, useWatch } from 'react-hook-form';
1617

17-
import { VPCPublicIPLabel } from 'src/features/VPCs/components/VPCPublicIPLabel';
18+
import {
19+
VPCIPv6PublicIPLabel,
20+
VPCPublicIPLabel,
21+
} from 'src/features/VPCs/components/VPCPublicIPLabel';
1822
import {
1923
REGION_CAVEAT_HELPER_TEXT,
2024
VPC_AUTO_ASSIGN_IPV4_TOOLTIP,
25+
VPC_AUTO_ASSIGN_IPV6_TOOLTIP,
2126
} from 'src/features/VPCs/constants';
27+
import { generateVPCIPv6InputHelperText } from 'src/features/VPCs/utils';
2228
import { VPCCreateDrawer } from 'src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer';
29+
import { useVPCDualStack } from 'src/hooks/useVPCDualStack';
2330

2431
import { VPCAvailabilityNotice } from './VPCAvailabilityNotice';
32+
import { VPCIPv6Ranges } from './VPCIPv6Ranges';
2533
import { VPCRanges } from './VPCRanges';
2634

2735
import type { LinodeCreateFormValues } from '../utilities';
36+
import type { Theme } from '@linode/ui';
2837

2938
interface Props {
3039
index: number;
@@ -33,6 +42,7 @@ interface Props {
3342
export const VPC = ({ index }: Props) => {
3443
const {
3544
control,
45+
getValues,
3646
resetField,
3747
setValue,
3848
formState: { errors },
@@ -58,8 +68,20 @@ export const VPC = ({ index }: Props) => {
5868
filter: { region: regionId },
5969
});
6070

71+
const { isDualStackEnabled } = useVPCDualStack();
72+
6173
const selectedVPC = vpcs?.find((vpc) => vpc.id === selectedVPCId);
6274

75+
// Check that selected subnet supports IPv6
76+
const selectedSubnet = selectedVPC?.subnets.find(
77+
(subnet) =>
78+
subnet.id === getValues(`linodeInterfaces.${index}.vpc.subnet_id`)
79+
);
80+
81+
const showIPv6Fields =
82+
isDualStackEnabled &&
83+
Boolean(selectedSubnet?.ipv6?.length && selectedSubnet?.ipv6?.length > 0);
84+
6385
return (
6486
<Box>
6587
<Stack spacing={1.5}>
@@ -83,7 +105,7 @@ export const VPC = ({ index }: Props) => {
83105
field.onChange(vpc?.id ?? null);
84106

85107
if (vpc && vpc.subnets.length === 1) {
86-
// If the user selectes a VPC and the VPC only has one subnet,
108+
// If the user selects a VPC and the VPC only has one subnet,
87109
// preselect that subnet for the user.
88110
setValue(
89111
`linodeInterfaces.${index}.vpc.subnet_id`,
@@ -145,10 +167,7 @@ export const VPC = ({ index }: Props) => {
145167
disabled={!regionSupportsVPCs}
146168
label={
147169
<Stack alignItems="center" direction="row">
148-
<Typography>
149-
Auto-assign a VPC IPv4 address for this Linode in the
150-
VPC
151-
</Typography>
170+
<Typography>Auto-assign VPC IPv4 address</Typography>
152171
<TooltipIcon
153172
status="info"
154173
text={VPC_AUTO_ASSIGN_IPV4_TOOLTIP}
@@ -178,28 +197,111 @@ export const VPC = ({ index }: Props) => {
178197
</Box>
179198
)}
180199
/>
181-
<Controller
182-
control={control}
183-
name={`linodeInterfaces.${index}.vpc.ipv4.addresses.0.nat_1_1_address`}
184-
render={({ field, fieldState }) => (
185-
<Box>
186-
{fieldState.error?.message && (
187-
<Notice text={fieldState.error.message} variant="error" />
200+
{showIPv6Fields && (
201+
<Controller
202+
control={control}
203+
name={`linodeInterfaces.${index}.vpc.ipv6.slaac.0.range`}
204+
render={({ field, fieldState }) => (
205+
<Box>
206+
<FormControlLabel
207+
checked={field.value === 'auto'}
208+
control={<Checkbox sx={{ ml: 0.4 }} />}
209+
disabled={!regionSupportsVPCs}
210+
label={
211+
<Stack alignItems="center" direction="row">
212+
<Typography>Auto-assign VPC IPv6 address</Typography>
213+
<TooltipIcon
214+
status="info"
215+
text={VPC_AUTO_ASSIGN_IPV6_TOOLTIP}
216+
/>
217+
</Stack>
218+
}
219+
onChange={(e, checked) =>
220+
field.onChange(checked ? 'auto' : '')
221+
}
222+
/>
223+
{field.value !== 'auto' && (
224+
<TextField
225+
containerProps={{ sx: { mb: 1.5, mt: 1 } }}
226+
errorText={
227+
fieldState.error?.message ??
228+
errors.linodeInterfaces?.[index]?.vpc?.ipv6?.slaac?.[0]
229+
?.range?.message
230+
}
231+
helperText={generateVPCIPv6InputHelperText(
232+
selectedSubnet?.ipv6?.[0].range ?? ''
233+
)}
234+
label="VPC IPv6"
235+
noMarginTop
236+
onBlur={field.onBlur}
237+
onChange={field.onChange}
238+
required
239+
value={field.value}
240+
/>
241+
)}
242+
</Box>
243+
)}
244+
/>
245+
)}
246+
<Box>
247+
<Divider
248+
sx={(theme) => ({ marginBottom: theme.spacingFunction(16) })}
249+
/>
250+
<Typography sx={(theme: Theme) => ({ font: theme.font.bold })}>
251+
Public access
252+
</Typography>
253+
<Controller
254+
control={control}
255+
name={`linodeInterfaces.${index}.vpc.ipv4.addresses.0.nat_1_1_address`}
256+
render={({ field, fieldState }) => (
257+
<Box>
258+
{fieldState.error?.message && (
259+
<Notice text={fieldState.error.message} variant="error" />
260+
)}
261+
<FormControlLabel
262+
checked={field.value === 'auto'}
263+
control={<Checkbox sx={{ ml: 0.4 }} />}
264+
disabled={!regionSupportsVPCs}
265+
label={<VPCPublicIPLabel />}
266+
onChange={(e, checked) =>
267+
field.onChange(checked ? 'auto' : null)
268+
}
269+
/>
270+
</Box>
271+
)}
272+
/>
273+
{showIPv6Fields && (
274+
<Controller
275+
control={control}
276+
name={`linodeInterfaces.${index}.vpc.ipv6.is_public`}
277+
render={({ field, fieldState }) => (
278+
<Box>
279+
{fieldState.error?.message && (
280+
<Notice text={fieldState.error.message} variant="error" />
281+
)}
282+
<FormControlLabel
283+
checked={field.value === true}
284+
control={<Checkbox sx={{ ml: 0.4 }} />}
285+
disabled={!regionSupportsVPCs}
286+
label={<VPCIPv6PublicIPLabel />}
287+
onChange={() => field.onChange(!field.value)}
288+
/>
289+
</Box>
188290
)}
189-
<FormControlLabel
190-
checked={field.value === 'auto'}
191-
control={<Checkbox sx={{ ml: 0.4 }} />}
192-
disabled={!regionSupportsVPCs}
193-
label={<VPCPublicIPLabel />}
194-
onChange={(e, checked) =>
195-
field.onChange(checked ? 'auto' : null)
196-
}
197-
/>
198-
</Box>
291+
/>
199292
)}
200-
/>
293+
<Divider
294+
sx={(theme) => ({ marginTop: theme.spacingFunction(16) })}
295+
/>
296+
</Box>
201297
</Stack>
202298
<VPCRanges disabled={!regionSupportsVPCs} interfaceIndex={index} />
299+
{showIPv6Fields && (
300+
<VPCIPv6Ranges
301+
disabled={!regionSupportsVPCs}
302+
interfaceIndex={index}
303+
/>
304+
)}
203305
</Stack>
204306
<VPCCreateDrawer
205307
onClose={() => setIsCreateDrawerOpen(false)}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {
2+
CloseIcon,
3+
IconButton,
4+
LinkButton,
5+
Notice,
6+
Stack,
7+
TextField,
8+
TooltipIcon,
9+
} from '@linode/ui';
10+
import React from 'react';
11+
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
12+
13+
import { VPCIPv6RangesDescription } from 'src/features/VPCs/components/VPCRangesDescription';
14+
15+
import type { LinodeCreateFormValues } from '../utilities';
16+
17+
interface Props {
18+
disabled: boolean;
19+
interfaceIndex: number;
20+
}
21+
22+
export const VPCIPv6Ranges = ({ disabled, interfaceIndex }: Props) => {
23+
const {
24+
control,
25+
formState: { errors },
26+
} = useFormContext<LinodeCreateFormValues>();
27+
28+
const { append, fields, remove } = useFieldArray({
29+
control,
30+
name: `linodeInterfaces.${interfaceIndex}.vpc.ipv6.ranges`,
31+
});
32+
33+
return (
34+
<Stack>
35+
<Stack spacing={1}>
36+
{errors?.linodeInterfaces?.[interfaceIndex]?.vpc?.ipv6?.ranges
37+
?.message && (
38+
<Notice variant="error">
39+
{
40+
errors?.linodeInterfaces?.[interfaceIndex]?.vpc?.ipv6?.ranges
41+
?.message
42+
}
43+
</Notice>
44+
)}
45+
{fields.map((field, index) => (
46+
<Stack
47+
alignItems="flex-start"
48+
direction="row"
49+
key={field.id}
50+
spacing={0.5}
51+
>
52+
<Controller
53+
control={control}
54+
name={`linodeInterfaces.${interfaceIndex}.vpc.ipv6.ranges.${index}.range`}
55+
render={({ field, fieldState }) => (
56+
<TextField
57+
errorText={fieldState.error?.message}
58+
hideLabel
59+
inputRef={field.ref}
60+
label={`IP Range ${index}`}
61+
onBlur={field.onBlur}
62+
onChange={field.onChange}
63+
sx={{ minWidth: 290 }}
64+
value={field.value}
65+
/>
66+
)}
67+
/>
68+
<IconButton
69+
aria-label={`Remove IP Range ${index}`}
70+
onClick={() => remove(index)}
71+
sx={{ padding: 1 }}
72+
>
73+
<CloseIcon />
74+
</IconButton>
75+
</Stack>
76+
))}
77+
</Stack>
78+
<Stack alignItems="center" direction="row" spacing={1}>
79+
<LinkButton disabled={disabled} onClick={() => append({ range: '' })}>
80+
Add IPv6 Range
81+
</LinkButton>
82+
<TooltipIcon
83+
status="info"
84+
sxTooltipIcon={{ p: 0.5 }}
85+
text={<VPCIPv6RangesDescription />}
86+
/>
87+
</Stack>
88+
</Stack>
89+
);
90+
};

packages/manager/src/features/Linodes/LinodeCreate/Networking/VPCRanges.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ export const VPCRanges = ({ disabled, interfaceIndex }: Props) => {
6060
label={`IP Range ${index}`}
6161
onBlur={field.onBlur}
6262
onChange={field.onChange}
63-
// eslint-disable-next-line sonarjs/no-hardcoded-ip
64-
placeholder="10.0.0.0/24"
6563
sx={{ minWidth: 290 }}
6664
value={field.value}
6765
/>

0 commit comments

Comments
 (0)