Skip to content
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-7121 & M3-7122] - Support VPC in Linode Add/Edit Config dialog #9709

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
168fa98
display vpc in the network interfaces column of the config table
hana-akamai Sep 19, 2023
e325b51
linode add config vpc state
hana-akamai Sep 21, 2023
21bc318
fix label error on submit
hana-akamai Sep 22, 2023
6e66e00
fix vpc select and errors not displaying
hana-akamai Sep 22, 2023
8bb08e9
filter by region
hana-akamai Sep 22, 2023
2728840
feature flag vpc option
hana-akamai Sep 25, 2023
307c089
fix vpc and subnet validation errors
hana-akamai Sep 25, 2023
d87c0d3
fix vpc ipv4 input being overwritten
hana-akamai Sep 25, 2023
0c2fed9
clean up payload
hana-akamai Sep 25, 2023
3242a49
fix vlan ipam_address not nullable
hana-akamai Sep 25, 2023
55bfffb
clean up error code
hana-akamai Sep 25, 2023
c1c4106
fix type errors
hana-akamai Sep 26, 2023
94cc92e
use VPCPanel
hana-akamai Sep 27, 2023
fc008f2
clean up and fix styling
hana-akamai Sep 27, 2023
66015bf
Merge branch 'develop' into M3-7121-vpc-add-config-dialog-and-table
hana-akamai Sep 27, 2023
47a48cf
Merge in latest develop and resolve conflicts
DevDW Sep 28, 2023
8e508c8
Fix styling of VPC panel in Linode Create flow
DevDW Sep 28, 2023
0243356
Formatting fixed for VPCPanel in Add/Edit Config dialog; Primary Inte…
DevDW Sep 29, 2023
4f42031
Refactors in LinodeConfigDialog.tsx; refactors in InterfaceSelect.tsx…
DevDW Sep 29, 2023
8f919ce
Move destructured props higher in function component body in Interfac…
DevDW Sep 30, 2023
8af257c
Ensure primaryInterfaceIndex is 0 if we are in Create mode upon dialo…
DevDW Oct 2, 2023
bc6ea6e
Adjust useEffect() logic to handle for non-VPC cases to prevent VLAN bug
DevDW Oct 2, 2023
1c5d210
Don't display 'None' option for VPC interfaces in Config dialog
DevDW Oct 3, 2023
6950b89
Fix Assign Linode bug; minor casing adjustment in variable name
DevDW Oct 5, 2023
bfa3ea6
Temporary solution for surfacing a couple of errors pending API fix
DevDW Oct 6, 2023
ccba866
fix types
hana-akamai Oct 6, 2023
6922b0b
Set custom breakpoint conditionally to stack elements sooner and avoi…
DevDW Oct 6, 2023
6d969d7
Merge branch 'M3-7121-vpc-add-config-dialog-and-table' of https://git…
DevDW Oct 6, 2023
f3fbeac
use invalidateQueries for VPCs if any of the config interfaces have a…
DevDW Oct 10, 2023
d1a4830
Merge branch 'develop' into M3-7121-vpc-add-config-dialog-and-table
DevDW Oct 11, 2023
e9b411d
Feedback @abailly-akamai: type adjustments, unit tests
DevDW Oct 12, 2023
9ff5c34
Add changesets
DevDW Oct 12, 2023
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 VPCs in Add/Edit Linode Config dialog ([#9709](https://github.com/linode/manager/pull/9709))
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {

import { InterfaceSelect } from '../LinodesDetail/LinodeSettings/InterfaceSelect';

// @TODO Delete this file when VPC is released
// @TODO VPC: Delete this file when VPC is released

interface Props {
handleVLANChange: (updatedInterface: Interface) => void;
Expand Down Expand Up @@ -105,14 +105,16 @@ export const AttachVLAN = React.memo((props: Props) => {
.
</Typography>
<InterfaceSelect
errors={{
ipamError,
labelError,
}}
handleChange={(newInterface: Interface) =>
handleVLANChange(newInterface)
}
fromAddonsPanel
ipamAddress={ipamAddress}
ipamError={ipamError}
label={vlanLabel}
labelError={labelError}
purpose="vlan"
readOnly={readOnly || !regionSupportsVLANs || false}
region={region}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ export class LinodeCreate extends React.PureComponent<
}
assignPublicIPv4Address={this.props.assignPublicIPv4Address}
autoassignIPv4WithinVPC={this.props.autoassignIPv4WithinVPC}
from="linodeCreate"
handleSelectVPC={this.props.setSelectedVPC}
handleSubnetChange={this.props.handleSubnetChange}
handleVPCIPv4Change={this.props.handleVPCIPv4Change}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,16 @@ export const VLANAccordion = React.memo((props: Props) => {
.
</Typography>
<InterfaceSelect
errors={{
ipamError,
labelError,
}}
handleChange={(newInterface: Interface) =>
handleVLANChange(newInterface)
}
fromAddonsPanel
ipamAddress={ipamAddress}
ipamError={ipamError}
label={vlanLabel}
labelError={labelError}
purpose="vlan"
readOnly={readOnly || !regionSupportsVLANs || false}
region={region}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { makeResourcePage } from 'src/mocks/serverHandlers';
import { rest, server } from 'src/mocks/testServer';
import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers';

import { VPCPanel } from './VPCPanel';
import { VPCPanel, VPCPanelProps } from './VPCPanel';

const queryClient = new QueryClient();

Expand All @@ -19,6 +19,7 @@ afterEach(() => {
const props = {
assignPublicIPv4Address: false,
autoassignIPv4WithinVPC: true,
from: 'linodeCreate' as VPCPanelProps['from'],
handleSelectVPC: jest.fn(),
handleSubnetChange: jest.fn(),
handleVPCIPv4Change: jest.fn(),
Expand Down
107 changes: 76 additions & 31 deletions packages/manager/src/features/Linodes/LinodesCreate/VPCPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Stack from '@mui/material/Stack';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import * as React from 'react';

import { Box } from 'src/components/Box';
Expand All @@ -23,20 +25,23 @@ import scrollErrorIntoView from 'src/utilities/scrollErrorIntoView';
import { StyledCreateLink } from './LinodeCreate.styles';
import { REGION_CAVEAT_HELPER_TEXT } from './constants';

interface VPCPanelProps {
export interface VPCPanelProps {
assignPublicIPv4Address: boolean;
autoassignIPv4WithinVPC: boolean;
from: 'linodeConfig' | 'linodeCreate';
handleSelectVPC: (vpcId: number) => void;
handleSubnetChange: (subnetId: number) => void;
handleVPCIPv4Change: (IPv4: string) => void;
publicIPv4Error?: string;
region: string | undefined;
selectedSubnetId: number | undefined;
selectedVPCId: number | undefined;
selectedSubnetId: null | number | undefined;
selectedVPCId: null | number | undefined;
subnetError?: string;
toggleAssignPublicIPv4Address: () => void;
toggleAutoassignIPv4WithinVPCEnabled: () => void;
vpcIPv4AddressOfLinode: string | undefined;
vpcIPv4Error?: string;
vpcIdError?: string;
}

const ERROR_GROUP_STRING = 'vpc-errors';
Expand All @@ -45,9 +50,11 @@ export const VPCPanel = (props: VPCPanelProps) => {
const {
assignPublicIPv4Address,
autoassignIPv4WithinVPC,
from,
handleSelectVPC,
handleSubnetChange,
handleVPCIPv4Change,
publicIPv4Error,
region,
selectedSubnetId,
selectedVPCId,
Expand All @@ -56,8 +63,12 @@ export const VPCPanel = (props: VPCPanelProps) => {
toggleAutoassignIPv4WithinVPCEnabled,
vpcIPv4AddressOfLinode,
vpcIPv4Error,
vpcIdError,
} = props;

const theme = useTheme();
const isSmallBp = useMediaQuery(theme.breakpoints.down('sm'));

const flags = useFlags();
const { account } = useAccountManagement();

Expand Down Expand Up @@ -104,10 +115,15 @@ export const VPCPanel = (props: VPCPanelProps) => {
: accumulator;
}, []);

vpcDropdownOptions.unshift({
label: 'None',
value: -1,
});
const fromLinodeCreate = from === 'linodeCreate';
const fromLinodeConfig = from === 'linodeConfig';

if (fromLinodeCreate) {
vpcDropdownOptions.unshift({
label: 'None',
value: -1,
});
}

const subnetDropdownOptions: Item[] =
vpcs
Expand All @@ -121,27 +137,46 @@ export const VPCPanel = (props: VPCPanelProps) => {
? getAPIErrorOrDefault(error, 'Unable to load VPCs')[0].reason
: undefined;

const mainCopyVPC =
vpcDropdownOptions.length <= 1
? 'Allow Linode to communicate in an isolated environment.'
: 'Assign this Linode to an existing VPC.';
const getMainCopyVPC = () => {
if (fromLinodeConfig) {
return null;
}

const copy =
vpcDropdownOptions.length <= 1
? 'Allow Linode to communicate in an isolated environment.'
: 'Assign this Linode to an existing VPC.';

return (
<>
{/* @TODO VPC: Update link */}
{copy} <Link to="">Learn more</Link>.
dwiley-akamai marked this conversation as resolved.
Show resolved Hide resolved
</>
);
};

return (
<Paper
sx={(theme) => ({
...(fromLinodeCreate && {
marginTop: theme.spacing(3),
}),
...(fromLinodeConfig && {
padding: 0,
}),
})}
data-testid="vpc-panel"
sx={(theme) => ({ marginTop: theme.spacing(3) })}
>
<Typography
sx={(theme) => ({ marginBottom: theme.spacing(2) })}
variant="h2"
>
VPC
</Typography>
<Stack>
<Typography>
{/* @TODO VPC: Update link */}
{mainCopyVPC} <Link to="">Learn more</Link>.
{fromLinodeCreate && (
<Typography
sx={(theme) => ({ marginBottom: theme.spacing(2) })}
variant="h2"
>
VPC
</Typography>
)}
<Stack>
<Typography>{getMainCopyVPC()}</Typography>
<Select
onChange={(selectedVPC: Item<number, string>) => {
handleSelectVPC(selectedVPC.value);
Expand All @@ -152,15 +187,15 @@ export const VPCPanel = (props: VPCPanelProps) => {
value={vpcDropdownOptions.find(
(option) => option.value === selectedVPCId
)}
defaultValue={vpcDropdownOptions[0]}
defaultValue={fromLinodeConfig ? null : vpcDropdownOptions[0]} // If we're in the Config dialog, there is no "None" option at index 0
disabled={!regionSupportsVPCs}
errorText={vpcError}
errorText={vpcIdError ?? vpcError}
isClearable={false}
isLoading={isLoading}
label="Assign VPC"
label={from === 'linodeCreate' ? 'Assign VPC' : 'VPC'}
noOptionsMessage={() => 'Create a VPC to assign to this Linode.'}
options={vpcDropdownOptions}
placeholder={''}
placeholder={'Select a VPC'}
/>
{vpcDropdownOptions.length <= 1 && regionSupportsVPCs && (
<Typography sx={(theme) => ({ paddingTop: theme.spacing(1.5) })}>
Expand All @@ -169,9 +204,11 @@ export const VPCPanel = (props: VPCPanelProps) => {
</Typography>
)}

<StyledCreateLink to={`${APP_ROOT}/vpcs/create`}>
Create VPC
</StyledCreateLink>
{from === 'linodeCreate' && (
<StyledCreateLink to={`${APP_ROOT}/vpcs/create`}>
Create VPC
</StyledCreateLink>
)}

{selectedVPCId !== -1 && regionSupportsVPCs && (
<Stack data-testid="subnet-and-additional-options-section">
Expand Down Expand Up @@ -214,7 +251,7 @@ export const VPCPanel = (props: VPCPanelProps) => {
flexDirection="row"
sx={{}}
>
<Typography noWrap>
<Typography noWrap={!isSmallBp && from === 'linodeConfig'}>
Auto-assign a VPC IPv4 address for this Linode in the VPC
</Typography>
<TooltipIcon
Expand Down Expand Up @@ -245,7 +282,6 @@ export const VPCPanel = (props: VPCPanelProps) => {
})}
alignItems="center"
display="flex"
flexDirection="row"
>
<FormControlLabel
control={
Expand All @@ -268,6 +304,15 @@ export const VPCPanel = (props: VPCPanelProps) => {
</Box>
}
/>
{assignPublicIPv4Address && publicIPv4Error && (
<Typography
sx={(theme) => ({
color: theme.color.red,
})}
>
{publicIPv4Error}
</Typography>
)}
</Box>
</Stack>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Config, Interface } from '@linode/api-v4/lib/linodes';
import { Config } from '@linode/api-v4/lib/linodes';
import { styled } from '@mui/material/styles';
import * as React from 'react';

Expand All @@ -9,6 +9,7 @@ import { useAllLinodeDisksQuery } from 'src/queries/linodes/disks';
import { useLinodeKernelQuery } from 'src/queries/linodes/linodes';
import { useLinodeVolumesQuery } from 'src/queries/volumes';

import { InterfaceListItem } from './InterfaceListItem';
import { ConfigActionMenu } from './LinodeConfigActionMenu';

interface Props {
Expand Down Expand Up @@ -59,7 +60,7 @@ export const ConfigRow = React.memo((props: Props) => {
return undefined;
}
return (
<li style={{ paddingBottom: 4 }} key={thisDevice}>
<li key={thisDevice} style={{ paddingBottom: 4 }}>
/dev/{thisDevice} - {label}
</li>
);
Expand All @@ -76,17 +77,12 @@ export const ConfigRow = React.memo((props: Props) => {
const InterfaceList = (
<StyledUl>
{interfaces.map((interfaceEntry, idx) => {
// The order of the config.interfaces array as returned by the API is significant.
// Index 0 is eth0, index 1 is eth1, index 2 is eth2.
const interfaceName = `eth${idx}`;

return (
<li
style={{ paddingBottom: 4 }}
<InterfaceListItem
idx={idx}
interfaceEntry={interfaceEntry}
key={interfaceEntry.label ?? 'public' + idx}
>
{interfaceName} – {getInterfaceLabel(interfaceEntry)}
</li>
/>
);
})}
</StyledUl>
Expand Down Expand Up @@ -132,15 +128,3 @@ const StyledTableCell = styled(TableCell, { label: 'StyledTableCell' })({
},
padding: '0 !important',
});

export const getInterfaceLabel = (configInterface: Interface): string => {
if (configInterface.purpose === 'public') {
return 'Public Internet';
}

const interfaceLabel = configInterface.label;
const ipamAddress = configInterface.ipam_address;
const hasIPAM = Boolean(ipamAddress);

return `VLAN: ${interfaceLabel} ${hasIPAM ? `(${ipamAddress})` : ''}`;
};
Loading