Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fdb225e
save progress
bnussman Mar 24, 2025
03addad
handle complex error case
bnussman Mar 24, 2025
77ea0ec
more fixes
bnussman Mar 24, 2025
7624e85
dial in UI
bnussman Mar 24, 2025
d5a14c6
default firewalls work on Linode Create flow
bnussman Mar 25, 2025
138aa2a
clean up a bit
bnussman Mar 25, 2025
7f9561f
unit test a few things
bnussman Mar 25, 2025
6dd40a0
Merge branch 'develop' into M3-9637-support-more-vpc-features-linode-…
bnussman Mar 25, 2025
15504c9
Added changeset: Support more VPC features when using Linode Interfac…
bnussman Mar 25, 2025
8b82080
Added changeset: Pre-select default firewalls on the Linode Create flow
bnussman Mar 25, 2025
5fcea03
not sure what to do here
bnussman Mar 25, 2025
66175d5
not sure what to do here
bnussman Mar 25, 2025
6bb48fa
Merge branch 'develop' into M3-9637-support-more-vpc-features-linode-…
bnussman Mar 25, 2025
40af5de
some fixes and better error handling
bnussman Mar 26, 2025
50a2f61
Merge branch 'develop' into M3-9637-support-more-vpc-features-linode-…
bnussman-akamai Mar 26, 2025
cff9a08
Merge branch 'develop' into M3-9637-support-more-vpc-features-linode-…
bnussman Mar 27, 2025
e94b095
fix clickable areas in VPC section
bnussman Mar 27, 2025
d566d46
improve default firewall ux
bnussman Mar 27, 2025
7ce8c8d
hacky fix to improve error handling for duplicate purpose field
bnussman Mar 27, 2025
eb3acd3
Revert "hacky fix to improve error handling for duplicate purpose field"
bnussman Mar 27, 2025
775f38e
Merge branch 'develop' into M3-9637-support-more-vpc-features-linode-…
bnussman-akamai Apr 2, 2025
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/manager": Upcoming Features
---

Support more VPC features when using Linode Interfaces on the Linode Create page ([#11915](https://github.com/linode/manager/pull/11915))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Pre-select default firewalls on the Linode Create flow ([#11915](https://github.com/linode/manager/pull/11915))
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useAllFirewallsQuery } from '@linode/queries';
import { Autocomplete, Box, Stack } from '@linode/ui';
import { Box, Stack } from '@linode/ui';
import React, { useState } from 'react';
import { useController, useFormContext, useWatch } from 'react-hook-form';

import { LinkButton } from 'src/components/LinkButton';
import { FirewallSelect } from 'src/features/Firewalls/components/FirewallSelect';
import { CreateFirewallDrawer } from 'src/features/Firewalls/FirewallLanding/CreateFirewallDrawer';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';

Expand All @@ -26,31 +26,23 @@ export const InterfaceFirewall = ({ index }: Props) => {
name: `linodeInterfaces.${index}.firewall_id`,
});

const { data: firewalls, error, isLoading } = useAllFirewallsQuery();

const [isDrawerOpen, setIsDrawerOpen] = useState(false);

const isLinodeCreateRestricted = useRestrictedGlobalGrantCheck({
globalGrantType: 'add_linodes',
});

const selectedFirewall =
firewalls?.find((firewall) => firewall.id === field.value) ?? null;

return (
<Stack spacing={2}>
<Stack spacing={1.5}>
<Autocomplete
<FirewallSelect
disabled={isLinodeCreateRestricted}
errorText={fieldState.error?.message ?? error?.[0].reason}
errorText={fieldState.error?.message}
label={`${labelMap[interfaceType]} Interface Firewall`}
loading={isLoading}
noMarginTop
onBlur={field.onBlur}
onChange={(e, firewall) => field.onChange(firewall?.id ?? null)}
options={firewalls ?? []}
placeholder="None"
value={selectedFirewall}
value={field.value}
/>
<Box>
<LinkButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { useFirewallSettingsQuery } from '@linode/queries';
import { FormControl, FormControlLabel, Radio, RadioGroup } from '@linode/ui';
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';

import { FormLabel } from 'src/components/FormLabel';

import { getDefaultFirewallForInterfacePurpose } from './utilities';

import type { LinodeCreateFormValues } from '../utilities';
import type { InterfacePurpose } from '@linode/api-v4';

interface Props {
index: number;
}

export const InterfaceType = ({ index }: Props) => {
const { control } = useFormContext<LinodeCreateFormValues>();
const {
control,
setValue,
getFieldState,
} = useFormContext<LinodeCreateFormValues>();

const { data: firewallSettings } = useFirewallSettingsQuery();

const { field } = useController({
control,
Expand All @@ -24,8 +34,26 @@ export const InterfaceType = ({ index }: Props) => {
Network Connection
</FormLabel>
<RadioGroup
onChange={(e, value) => {
// Change the interface purpose (Public, VPC, VLAN)
field.onChange(value);

const defaultFirewall = getDefaultFirewallForInterfacePurpose(
value as InterfacePurpose,
firewallSettings
);

// Set the Firewall based on defaults if:
// - there is a default firewall for this interface type
// - the user has not touched the Firewall field
if (
defaultFirewall &&
!getFieldState(`linodeInterfaces.${index}.firewall_id`).isTouched
) {
setValue(`linodeInterfaces.${index}.firewall_id`, defaultFirewall);
}
}}
aria-labelledby="network-interface"
onChange={field.onChange}
row
sx={{ mb: '0px !important' }}
value={field.value}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useFirewallSettingsQuery } from '@linode/queries';
import {
Button,
Divider,
Expand All @@ -13,6 +14,7 @@ import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
import { Firewall } from './Firewall';
import { InterfaceGeneration } from './InterfaceGeneration';
import { LinodeInterface } from './LinodeInterface';
import { getDefaultInterfacePayload } from './utilities';

import type { LinodeCreateFormValues } from '../utilities';

Expand All @@ -22,6 +24,8 @@ export const Networking = () => {
formState: { errors },
} = useFormContext<LinodeCreateFormValues>();

const { data: firewallSettings } = useFirewallSettingsQuery();

const { append, fields, remove } = useFieldArray({
control,
name: 'linodeInterfaces',
Expand All @@ -42,16 +46,9 @@ export const Networking = () => {
>
<Typography variant="h2">Networking</Typography>
<Button
onClick={() =>
append({
default_route: null,
firewall_id: null,
public: {},
purpose: 'public',
vlan: null,
vpc: null,
})
}
onClick={() => {
append(getDefaultInterfacePayload('public', firewallSettings));
}}
buttonType="outlined"
endIcon={<PlusSignIcon height="12px" width="12px" />}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { useAllVPCsQuery, useRegionQuery } from '@linode/queries';
import { Autocomplete, Box, Notice, Stack } from '@linode/ui';
import {
Autocomplete,
Box,
Checkbox,
FormControlLabel,
Notice,
Stack,
TextField,
TooltipIcon,
Typography,
} from '@linode/ui';
import React, { useState } from 'react';
import { Controller, useFormContext, useWatch } from 'react-hook-form';

import { LinkButton } from 'src/components/LinkButton';
import { REGION_CAVEAT_HELPER_TEXT } from 'src/features/VPCs/constants';
import {
REGION_CAVEAT_HELPER_TEXT,
VPC_AUTO_ASSIGN_IPV4_TOOLTIP,
} from 'src/features/VPCs/constants';
import { VPCCreateDrawer } from 'src/features/VPCs/VPCCreateDrawer/VPCCreateDrawer';

import { VPCRanges } from './VPCRanges';

import type { LinodeCreateFormValues } from '../utilities';

interface Props {
Expand All @@ -18,6 +33,7 @@ export const VPC = ({ index }: Props) => {
control,
resetField,
setValue,
formState: { errors },
} = useFormContext<LinodeCreateFormValues>();
const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false);

Expand Down Expand Up @@ -118,6 +134,85 @@ export const VPC = ({ index }: Props) => {
control={control}
name={`linodeInterfaces.${index}.vpc.subnet_id`}
/>
<Stack>
<Controller
render={({ field, fieldState }) => (
<Box>
<FormControlLabel
label={
<Stack alignItems="center" direction="row">
<Typography>
Auto-assign a VPC IPv4 address for this Linode in the
VPC
</Typography>
<TooltipIcon
status="help"
text={VPC_AUTO_ASSIGN_IPV4_TOOLTIP}
/>
</Stack>
}
onChange={(e, checked) =>
field.onChange(checked ? 'auto' : '')
}
checked={field.value === 'auto'}
control={<Checkbox sx={{ ml: 0.4 }} />}
disabled={!regionSupportsVPCs}
/>
{field.value !== 'auto' && (
<TextField
errorText={
fieldState.error?.message ??
errors.linodeInterfaces?.[index]?.vpc?.ipv4
?.addresses?.[0]?.message
}
containerProps={{ sx: { mb: 1.5, mt: 1 } }}
label="VPC IPv4"
noMarginTop
onBlur={field.onBlur}
onChange={field.onChange}
required
value={field.value}
/>
)}
</Box>
)}
control={control}
name={`linodeInterfaces.${index}.vpc.ipv4.addresses.0.address`}
/>
<Controller
render={({ field, fieldState }) => (
<Box>
{fieldState.error?.message && (
<Notice text={fieldState.error.message} variant="error" />
)}
<FormControlLabel
label={
<Stack alignItems="center" direction="row">
<Typography>
Assign a public IPv4 address for this Linode
</Typography>
<TooltipIcon
text={
'Access the internet through the public IPv4 address using static 1:1 NAT.'
}
status="help"
/>
</Stack>
}
onChange={(e, checked) =>
field.onChange(checked ? 'auto' : null)
}
checked={field.value === 'auto'}
control={<Checkbox sx={{ ml: 0.4 }} />}
disabled={!regionSupportsVPCs}
/>
</Box>
)}
control={control}
name={`linodeInterfaces.${index}.vpc.ipv4.addresses.0.nat_1_1_address`}
/>
</Stack>
<VPCRanges disabled={!regionSupportsVPCs} interfaceIndex={index} />
</Stack>
<VPCCreateDrawer
onSuccess={(vpc) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
IconButton,
Stack,
TextField,
TooltipIcon,
Typography,
} from '@linode/ui';
import CloseIcon from '@mui/icons-material/Close';
import React from 'react';
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';

import { Link } from 'src/components/Link';
import { LinkButton } from 'src/components/LinkButton';

import type { LinodeCreateFormValues } from '../utilities';

interface Props {
disabled: boolean;
interfaceIndex: number;
}

export const VPCRanges = ({ disabled, interfaceIndex }: Props) => {
const { control } = useFormContext<LinodeCreateFormValues>();

const { append, fields, remove } = useFieldArray({
control,
name: `linodeInterfaces.${interfaceIndex}.vpc.ipv4.ranges`,
});

return (
<Stack>
<Stack spacing={1}>
{fields.map((field, index) => (
<Stack
alignItems="flex-start"
direction="row"
key={field.id}
spacing={0.5}
>
<Controller
render={({ field, fieldState }) => (
<TextField
errorText={fieldState.error?.message}
hideLabel
label={`IP Range ${index}`}
onBlur={field.onBlur}
onChange={field.onChange}
placeholder="10.0.0.0/24"
sx={{ minWidth: 290 }}
value={field.value}
/>
)}
control={control}
name={`linodeInterfaces.${interfaceIndex}.vpc.ipv4.ranges.${index}.range`}
/>
<IconButton
aria-label={`Remove IP Range ${index}`}
onClick={() => remove(index)}
sx={{ padding: 0.75 }}
>
<CloseIcon />
</IconButton>
</Stack>
))}
</Stack>
<Stack alignItems="center" direction="row" spacing={1}>
<LinkButton isDisabled={disabled} onClick={() => append({ range: '' })}>
Add IPv4 Range
</LinkButton>
<TooltipIcon
text={
<Typography>
Assign additional IPv4 address ranges that the VPC can use to
reach services running on this Linode.{' '}
<Link to="https://techdocs.akamai.com/cloud-computing/docs/assign-a-compute-instance-to-a-vpc">
Learn more
</Link>
.
</Typography>
}
status="help"
sxTooltipIcon={{ p: 0.5 }}
/>
</Stack>
</Stack>
);
};
Loading