Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
cpathipa committed Feb 13, 2024
2 parents d27c561 + f1d10aa commit 5f1c77b
Showing 33 changed files with 1,035 additions and 256 deletions.
21 changes: 21 additions & 0 deletions packages/manager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [2024-02-13] - v1.112.0

### Added:

- Support for IPv4 Ranges in VPC 'Assign Linodes to subnet' drawer ([#10089](https://github.com/linode/manager/pull/10089))
- VPC IPv4 address and range to Linode IP Address Table ([#10108](https://github.com/linode/manager/pull/10108))
- Support for VPC IPv4 Ranges data in Unassign Linodes drawer ([#10114](https://github.com/linode/manager/pull/10114))
- Support for VPC IPv4 Ranges in Linode Create flow and 'VPC IPv4 Ranges' column to inner Subnets table on VPC Detail page ([#10116](https://github.com/linode/manager/pull/10116))
- Support VPC IPv4 Ranges in Add/Edit Linode Config dialog ([#10170](https://github.com/linode/manager/pull/10170))

### Changed:

- "Learn more" docs link for IPv4 ranges in Add/Edit Linode Config dialog, Linode Create flow, and VPC "Assign Linodes" drawer

### Fixed:

- Error when enabling backups for Linodes in regions with $0 pricing ([#10153](https://github.com/linode/manager/pull/10153))
- Error notices for $0 regions in LKE Resize and Add Node Pools drawers ([#10157](https://github.com/linode/manager/pull/10157))
- Error in Enable All Backups drawer when one or more Linode is in a $0 region ([#10161](https://github.com/linode/manager/pull/10161))
- Display $0.00 prices in Linode Migration dialog ([#10166](https://github.com/linode/manager/pull/10166))

## [2024-02-05] - v1.111.0
### Changed:

Original file line number Diff line number Diff line change
@@ -153,7 +153,7 @@ describe('VPC assign/unassign flows', () => {
mockGetLinodeConfigs(mockLinode.id, [mockConfig]).as(
'getLinodeConfigs'
);
cy.findByLabelText('Linodes')
cy.findByLabelText('Linode')
.should('be.visible')
.click()
.type(mockLinode.label)
2 changes: 1 addition & 1 deletion packages/manager/package.json
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
"name": "linode-manager",
"author": "Linode",
"description": "The Linode Manager website",
"version": "1.111.0",
"version": "1.112.0",
"private": true,
"type": "module",
"bugs": {
39 changes: 28 additions & 11 deletions packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@ import { makeStyles } from 'tss-react/mui';

import { Button } from 'src/components/Button/Button';
import { InputLabel } from 'src/components/InputLabel';
import { LinkButton } from 'src/components/LinkButton';
import { Notice } from 'src/components/Notice/Notice';
import { StyledLinkButtonBox } from 'src/components/SelectFirewallPanel/SelectFirewallPanel';
import { TextField } from 'src/components/TextField';
import { TooltipIcon } from 'src/components/TooltipIcon';
import { Typography } from 'src/components/Typography';
@@ -56,9 +58,11 @@ const useStyles = makeStyles()((theme: Theme) => ({
}));

interface Props {
buttonText?: string;
className?: string;
error?: string;
forDatabaseAccessControls?: boolean;
forVPCIPv4Ranges?: boolean;
helperText?: string;
inputProps?: InputBaseProps;
ips: ExtendedIP[];
@@ -72,9 +76,11 @@ interface Props {

export const MultipleIPInput = React.memo((props: Props) => {
const {
buttonText,
className,
error,
forDatabaseAccessControls,
forVPCIPv4Ranges,
helperText,
ips,
onBlur,
@@ -122,6 +128,21 @@ export const MultipleIPInput = React.memo((props: Props) => {
return null;
}

const addIPButton = forVPCIPv4Ranges ? (
<StyledLinkButtonBox>
<LinkButton onClick={addNewInput}>{buttonText}</LinkButton>
</StyledLinkButtonBox>
) : (
<Button
buttonType="secondary"
className={classes.addIP}
compactX
onClick={addNewInput}
>
{buttonText ?? 'Add an IP'}
</Button>
);

return (
<div className={cx(classes.root, className)}>
{tooltip ? (
@@ -156,6 +177,7 @@ export const MultipleIPInput = React.memo((props: Props) => {
direction="row"
justifyContent="center"
key={`domain-transfer-ip-${idx}`}
maxWidth={forVPCIPv4Ranges ? '415px' : undefined}
spacing={2}
>
<Grid xs={11}>
@@ -177,27 +199,22 @@ export const MultipleIPInput = React.memo((props: Props) => {
value={thisIP.address}
/>
</Grid>
{/** Don't show the button for the first input since it won't do anything, unless this component is used in DBaaS */}
{/** Don't show the button for the first input since it won't do anything, unless this component is
* used in DBaaS or for Linode VPC interfaces
*/}
<Grid xs={1}>
{idx > 0 || forDatabaseAccessControls ? (
{(idx > 0 || forDatabaseAccessControls || forVPCIPv4Ranges) && (
<Button
className={classes.button}
onClick={() => removeInput(idx)}
>
<Close data-testid={`delete-ip-${idx}`} />
</Button>
) : null}
)}
</Grid>
</Grid>
))}
<Button
buttonType="secondary"
className={classes.addIP}
compactX
onClick={addNewInput}
>
Add an IP
</Button>
{addIPButton}
</div>
);
});
Original file line number Diff line number Diff line change
@@ -88,3 +88,10 @@ export const StyledScrollBox = styled(Box, {
maxWidth: `${maxWidth}px`,
overflow: 'auto',
}));

export const StyledItemWithPlusChip = styled('span', {
label: 'ItemWithPlusChip',
})({
alignItems: 'center',
display: 'inline-flex',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Close from '@mui/icons-material/Close';
import * as React from 'react';

import { IconButton } from 'src/components/IconButton';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
import { TableCell } from 'src/components/TableCell';
import { TableHead } from 'src/components/TableHead';
import { TableRow } from 'src/components/TableRow';
import { determineNoneSingleOrMultipleWithChip } from 'src/utilities/noneSingleOrMultipleWithChip';

import { TableRowEmpty } from '../TableRowEmpty/TableRowEmpty';
import {
SelectedOptionsHeader,
StyledLabel,
} from './RemovableSelectionsList.style';

export type RemovableItem = {
id: number;
label: string;
// The remaining key-value pairs must have their values typed
// as 'any' because we do not know what types they could be.
// Trying to type them as 'unknown' led to type errors.
} & { [key: string]: any };

export interface RemovableSelectionsListTableProps {
/**
* The descriptive text to display above the list
*/
headerText: string;
/**
* If false, hide the remove button
*/
isRemovable?: boolean;
/**
* The text to display if there is no data
*/
noDataText: string;
/**
* The action to perform when a data item is clicked
*/
onRemove: (data: RemovableItem) => void;
/**
* Assumes the passed in prop is a key within the selectionData, and that the
* value of this key is a string.
* Displays the value of this key as the label of the data item, rather than data.label
*/
preferredDataLabel?: string;
/**
* The data to display in the list
*/
selectionData: RemovableItem[];
/**
* Headers for the table containing the list of selected options
*/
tableHeaders: string[];
}

export const RemovableSelectionsListTable = (
props: RemovableSelectionsListTableProps
) => {
const {
headerText,
isRemovable = true,
noDataText,
onRemove,
preferredDataLabel,
selectionData,
tableHeaders,
} = props;

const handleOnClick = (selection: RemovableItem) => {
onRemove(selection);
};

const selectedOptionsJSX =
selectionData.length === 0 ? (
<TableRowEmpty colSpan={4} message={noDataText} />
) : (
selectionData.map((selection) => (
<TableRow key={selection.id}>
<TableCell>
<StyledLabel>
{preferredDataLabel
? selection[preferredDataLabel]
: selection.label}
</StyledLabel>
</TableCell>
<TableCell>{selection.interfaceData?.ipv4?.vpc ?? null}</TableCell>
<TableCell>
{determineNoneSingleOrMultipleWithChip(
selection.interfaceData?.ip_ranges ?? []
)}
</TableCell>
<TableCell>
{isRemovable && (
<IconButton
aria-label={`remove ${
preferredDataLabel
? selection[preferredDataLabel]
: selection.label
}`}
disableRipple
onClick={() => handleOnClick(selection)}
size="medium"
>
<Close />
</IconButton>
)}
</TableCell>
</TableRow>
))
);

const tableHeadersJSX = tableHeaders.map((thisHeader, idx) => {
const lastHeader = idx === tableHeaders.length - 1;

return (
<TableCell
colSpan={lastHeader ? 2 : 1}
key={`removable-selections-list-header-${thisHeader}`}
>
{thisHeader}
</TableCell>
);
});

return (
<>
<SelectedOptionsHeader>{headerText}</SelectedOptionsHeader>
<Table>
<TableHead>
<TableRow>{tableHeadersJSX}</TableRow>
</TableHead>
<TableBody>{selectedOptionsJSX}</TableBody>
</Table>
</>
);
};
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ export const LinodeConfigInterfaceFactoryWithVPC = Factory.Sync.makeFactory<Inte
{
active: false,
id: Factory.each((i) => i),
ip_ranges: ['192.0.2.0/24'],
ip_ranges: ['192.0.2.0/24', '192.0.3.0/24'],
ipam_address: '10.0.0.1/24',
ipv4: {
nat_1_1: 'some nat',
Original file line number Diff line number Diff line change
@@ -10,6 +10,11 @@ import { useAllLinodesQuery } from 'src/queries/linodes/linodes';
import { mapIdsToDevices } from 'src/utilities/mapIdsToDevices';

interface LinodeSelectProps {
/** Determine whether isOptionEqualToValue prop should be defined for Autocomplete
* component (to avoid "The value provided to Autocomplete is invalid [...]" console
* errors). See https://github.com/linode/manager/pull/10089 for context & discussion.
*/
checkIsOptionEqualToValue?: boolean;
/** Whether to display the clear icon. Defaults to `true`. */
clearable?: boolean;
/** Disable editing the input value. */
@@ -73,6 +78,7 @@ export const LinodeSelect = (
props: LinodeMultiSelectProps | LinodeSingleSelectProps
) => {
const {
checkIsOptionEqualToValue,
clearable = true,
disabled,
errorText,
@@ -116,6 +122,11 @@ export const LinodeSelect = (
getOptionLabel={(linode: Linode) =>
renderOptionLabel ? renderOptionLabel(linode) : linode.label
}
isOptionEqualToValue={
checkIsOptionEqualToValue
? (option, value) => option.id === value.id
: undefined
}
noOptionsText={
noOptionsMessage ?? getDefaultNoOptionsMessage(error, isLoading)
}
Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@ import { doesRegionSupportFeature } from 'src/utilities/doesRegionSupportFeature
import { getErrorMap } from 'src/utilities/errorUtils';
import { extendType } from 'src/utilities/extendType';
import { filterCurrentTypes } from 'src/utilities/filterCurrentLinodeTypes';
import { ExtendedIP } from 'src/utilities/ipUtils';
import { getMonthlyBackupsPrice } from 'src/utilities/pricing/backups';
import { UNKNOWN_PRICE } from 'src/utilities/pricing/constants';
import { renderMonthlyPriceToCorrectDecimalPlace } from 'src/utilities/pricing/dynamicPricing';
@@ -103,13 +104,15 @@ import {
import type { Tab } from 'src/components/Tabs/TabLinkList';

export interface LinodeCreateProps {
additionalIPv4RangesForVPC: ExtendedIP[];
assignPublicIPv4Address: boolean;
autoassignIPv4WithinVPC: boolean;
checkValidation: LinodeCreateValidation;
createType: CreateTypes;
firewallId?: number;
handleAgreementChange: () => void;
handleFirewallChange: (firewallId: number) => void;
handleIPv4RangesForVPC: (ranges: ExtendedIP[]) => void;
handleShowApiAwarenessModal: () => void;
handleSubmitForm: HandleSubmit;
handleSubnetChange: (subnetId: number) => void;
@@ -677,9 +680,11 @@ export class LinodeCreate extends React.PureComponent<
toggleAutoassignIPv4WithinVPCEnabled={
this.props.toggleAutoassignIPv4WithinVPCEnabled
}
additionalIPv4RangesForVPC={this.props.additionalIPv4RangesForVPC}
assignPublicIPv4Address={this.props.assignPublicIPv4Address}
autoassignIPv4WithinVPC={this.props.autoassignIPv4WithinVPC}
from="linodeCreate"
handleIPv4RangeChange={this.props.handleIPv4RangesForVPC}
handleSelectVPC={this.props.setSelectedVPC}
handleSubnetChange={this.props.handleSubnetChange}
handleVPCIPv4Change={this.props.handleVPCIPv4Change}
@@ -858,6 +863,9 @@ export class LinodeCreate extends React.PureComponent<
this.props.selectedVPCId !== -1
) {
const vpcInterfaceData: InterfacePayload = {
ip_ranges: this.props.additionalIPv4RangesForVPC
.map((ipRange) => ipRange.address)
.filter((ipRange) => ipRange !== ''),
ipam_address: null,
ipv4: {
nat_1_1: this.props.assignPublicIPv4Address ? 'any' : undefined,
Loading

0 comments on commit 5f1c77b

Please sign in to comment.