From a01c2ee65dd0457e1b744ac437f117771d3d9343 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Wed, 16 Apr 2025 13:03:19 -0400 Subject: [PATCH 01/15] initial changes --- .../src/components/ShowMore/ShowMore.tsx | 24 +---------- .../LinodeNetworking/LinodeIPAddresses.tsx | 32 ++++++++------ .../LinodeInterfaces/LinodeInterfaceIPs.tsx | 36 ++++++++++++++++ .../LinodeInterfaceIPs.utils.ts | 43 +++++++++++++++++++ .../LinodeInterfaceTableRow.tsx | 22 +++++----- .../LinodeInterfacesTable.tsx | 3 +- .../LinodeInterfacesTableContent.tsx | 8 ++-- .../LinodeNetworkingActionMenu.tsx | 21 ++++++--- 8 files changed, 132 insertions(+), 57 deletions(-) create mode 100644 packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx create mode 100644 packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts diff --git a/packages/manager/src/components/ShowMore/ShowMore.tsx b/packages/manager/src/components/ShowMore/ShowMore.tsx index 8c2bcd0b6c3..7b1541da76e 100644 --- a/packages/manager/src/components/ShowMore/ShowMore.tsx +++ b/packages/manager/src/components/ShowMore/ShowMore.tsx @@ -29,7 +29,7 @@ export const ShowMore = (props: ShowMoreProps) => { return ( - (props: ShowMoreProps) => { ); }; -const StyledChip = styled(Chip)(({ theme }) => ({ - '& .MuiChip-label': { - paddingLeft: 6, - paddingRight: 6, - }, - '&:focus': { - backgroundColor: theme.bg.lightBlue1, - outline: `1px dotted ${theme.tokens.color.Neutrals[50]}`, - }, - '&:hover': { - backgroundColor: theme.palette.primary.main, - color: theme.tokens.color.Neutrals.White, - }, - backgroundColor: theme.bg.lightBlue1, - font: theme.font.bold, - lineHeight: 1, - marginLeft: theme.spacing(0.5), - paddingLeft: 2, - paddingRight: 2, - position: 'relative', -})); - const StyledPopover = styled(Popover)(({ theme }) => ({ '& .MuiPopover-paper': { '&::-webkit-scrollbar': { diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeIPAddresses.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeIPAddresses.tsx index f59d15a2cbe..95fa8b9ff03 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeIPAddresses.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeIPAddresses.tsx @@ -26,6 +26,7 @@ import { TableRow } from 'src/components/TableRow'; import { TableSortCell } from 'src/components/TableSortCell'; import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted'; import { useVPCInterface } from 'src/hooks/useVPCInterface'; +import { useIsLinodeInterfacesEnabled } from 'src/utilities/linodes'; import { AddIPDrawer } from './AddIPDrawer'; import { DeleteIPDialog } from './DeleteIPDialog'; @@ -63,6 +64,7 @@ export const LinodeIPAddresses = (props: LinodeIPAddressesProps) => { const { data: ips, error, isLoading } = useLinodeIPsQuery(linodeID); const { data: linode } = useLinodeQuery(linodeID); const { data: regions } = useRegionsQuery(); + const { isLinodeInterfacesEnabled } = useIsLinodeInterfacesEnabled(); const linodeIsInDistributedRegion = getIsDistributedRegion( regions ?? [], @@ -161,11 +163,15 @@ export const LinodeIPAddresses = (props: LinodeIPAddressesProps) => { {isSmallScreen ? ( setIsAddDrawerOpen(true), - title: 'Add an IP Address', - }, + ...(!isLinodeInterfacesEnabled + ? [ + { + disabled: isLinodesGrantReadOnly, + onClick: () => setIsAddDrawerOpen(true), + title: 'Add an IP Address', + }, + ] + : []), { disabled: isLinodesGrantReadOnly, onClick: () => setIsTransferDialogOpen(true), @@ -195,13 +201,15 @@ export const LinodeIPAddresses = (props: LinodeIPAddressesProps) => { > IP Sharing - + {!isLinodeInterfacesEnabled && ( + + )} )} diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx new file mode 100644 index 00000000000..3fb9efc04dc --- /dev/null +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx @@ -0,0 +1,36 @@ +import { Stack } from '@linode/ui'; +import React from 'react'; + +import { MaskableText } from 'src/components/MaskableText/MaskableText'; +import { ShowMore } from 'src/components/ShowMore/ShowMore'; + +import { getLinodeInterfaceIPs } from './LinodeInterfaceIPs.utils'; + +import type { LinodeInterface } from '@linode/api-v4'; + +interface Props { + linodeInterface: LinodeInterface; +} + +export const LinodeInterfaceIPs = ({ linodeInterface }: Props) => { + const [primary, ...ips] = getLinodeInterfaceIPs(linodeInterface); + + return ( + + + {ips.length > 0 && ( + ( + + {ips.map((ip) => ( + + ))} + + )} + /> + )} + + ); +}; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts new file mode 100644 index 00000000000..486e8c042f9 --- /dev/null +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts @@ -0,0 +1,43 @@ +import type { LinodeInterface } from '@linode/api-v4'; + +export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { + const ips: string[] = []; + + if (linodeInterface.public) { + // IPv4s + for (const address of linodeInterface.public.ipv4.addresses) { + ips.push(address.address); + } + + // IPv6 Ranges + for (const address of linodeInterface.public.ipv6.ranges) { + ips.push(`${address.range} (Range)`); + } + + // IPv6 Shared + for (const address of linodeInterface.public.ipv6.shared) { + ips.push(`${address.range} (Shared)`); + } + + // IPv6 SLAAC + for (const address of linodeInterface.public.ipv6.slaac) { + ips.push(`${address.address} ${address.prefix} (SLAAC)`); + } + } + + if (linodeInterface.vpc) { + // VPC IPv4s + for (const address of linodeInterface.vpc.ipv4.addresses) { + ips.push(address.address); + if (address.nat_1_1_address) { + ips.push(`${address.nat_1_1_address} (VPC NAT)`); + } + } + // VPC IPv4 Ranges + for (const address of linodeInterface.vpc.ipv4.ranges) { + ips.push(`${address.range} (Range)`); + } + } + + return ips; +} diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx index 9469641d213..e8a84ba61a3 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx @@ -10,6 +10,8 @@ import { getLinodeInterfaceType } from './utilities'; import type { InterfaceActionHandlers } from './LinodeInterfaceActionMenu'; import type { LinodeInterface } from '@linode/api-v4'; +import { LinodeInterfaceIPs } from './LinodeInterfaceIPs'; +import { MaskableText } from 'src/components/MaskableText/MaskableText'; interface Props extends LinodeInterface { handlers: InterfaceActionHandlers; @@ -17,23 +19,21 @@ interface Props extends LinodeInterface { } export const LinodeInterfaceTableRow = (props: Props) => { - const { - created, - handlers, - id, - linodeId, - mac_address, - updated, - version, - } = props; + const { created, handlers, id, linodeId, mac_address, updated, version } = + props; const type = getLinodeInterfaceType(props); return ( - {id} {type} - {mac_address} + {id} + + + + + + {version} diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTable.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTable.tsx index 88ec3cc8cd4..d7d4b76bb14 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTable.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTable.tsx @@ -20,9 +20,10 @@ export const LinodeInterfacesTable = ({ handlers, linodeId }: Props) => { - ID Type + ID MAC Address + IP Addresses Version Firewall Updated diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTableContent.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTableContent.tsx index ed60dae0350..25d13f40427 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTableContent.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfacesTableContent.tsx @@ -17,18 +17,20 @@ interface Props { export const LinodeInterfacesTableContent = ({ handlers, linodeId }: Props) => { const { data, error, isPending } = useLinodeInterfacesQuery(linodeId); + const cols = 9; + if (isPending) { - return ; + return ; } if (error) { - return ; + return ; } if (data.interfaces.length === 0) { return ( ); diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeNetworkingActionMenu.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeNetworkingActionMenu.tsx index e2b2748ad26..69a7f44988c 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeNetworkingActionMenu.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeNetworkingActionMenu.tsx @@ -57,6 +57,9 @@ export const LinodeNetworkingActionMenu = (props: Props) => { ? 'Linodes must have at least one public IP' : undefined; + const isAssociatedWithLinodeInterface = + 'address' in ipAddress && ipAddress.interface_id !== null; + const getAriaLabel = (): string => { if ('address' in ipAddress) { return `Action menu for IP Address ${ipAddress.address}`; @@ -66,7 +69,11 @@ export const LinodeNetworkingActionMenu = (props: Props) => { }; const actions = [ - onRemove && ipAddress && !is116Range && deletableIPTypes.includes(ipType) + onRemove && + ipAddress && + !is116Range && + deletableIPTypes.includes(ipType) && + !isAssociatedWithLinodeInterface ? { disabled: readOnly || isOnlyPublicIP || isVPCOnlyLinode, id: 'delete', @@ -77,10 +84,10 @@ export const LinodeNetworkingActionMenu = (props: Props) => { tooltip: readOnly ? readOnlyTooltip : isVPCOnlyLinode - ? PUBLIC_IP_ADDRESSES_TOOLTIP_TEXT - : isOnlyPublicIP - ? isOnlyPublicIPTooltip - : undefined, + ? PUBLIC_IP_ADDRESSES_TOOLTIP_TEXT + : isOnlyPublicIP + ? isOnlyPublicIPTooltip + : undefined, } : null, onEdit && ipAddress && showEdit @@ -94,8 +101,8 @@ export const LinodeNetworkingActionMenu = (props: Props) => { tooltip: readOnly ? readOnlyTooltip : isVPCOnlyLinode - ? PUBLIC_IP_ADDRESSES_TOOLTIP_TEXT - : undefined, + ? PUBLIC_IP_ADDRESSES_TOOLTIP_TEXT + : undefined, } : null, ].filter(Boolean) as Action[]; From 6180543f392072b93e8c97151b0058bfdc84c650 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Wed, 16 Apr 2025 13:10:15 -0400 Subject: [PATCH 02/15] polish things --- .../LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx | 4 ++++ .../LinodeInterfaces/LinodeInterfaceIPs.utils.ts | 4 ++++ .../LinodeInterfaces/LinodeInterfaceTableRow.tsx | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx index 3fb9efc04dc..778886a663f 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx @@ -15,6 +15,10 @@ interface Props { export const LinodeInterfaceIPs = ({ linodeInterface }: Props) => { const [primary, ...ips] = getLinodeInterfaceIPs(linodeInterface); + if (!primary && ips.length === 0) { + return 'None'; + } + return ( diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts index 486e8c042f9..c9f4edd6593 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts @@ -1,6 +1,10 @@ import type { LinodeInterface } from '@linode/api-v4'; export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { + if (linodeInterface.vlan && linodeInterface.vlan.ipam_address) { + return [`${linodeInterface.vlan.ipam_address} (IPAM)`]; + } + const ips: string[] = []; if (linodeInterface.public) { diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx index e8a84ba61a3..e4340d3c0aa 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceTableRow.tsx @@ -1,17 +1,17 @@ import React from 'react'; import { DateTimeDisplay } from 'src/components/DateTimeDisplay'; +import { MaskableText } from 'src/components/MaskableText/MaskableText'; import { TableCell } from 'src/components/TableCell'; import { TableRow } from 'src/components/TableRow'; import { LinodeInterfaceActionMenu } from './LinodeInterfaceActionMenu'; import { LinodeInterfaceFirewall } from './LinodeInterfaceFirewall'; +import { LinodeInterfaceIPs } from './LinodeInterfaceIPs'; import { getLinodeInterfaceType } from './utilities'; import type { InterfaceActionHandlers } from './LinodeInterfaceActionMenu'; import type { LinodeInterface } from '@linode/api-v4'; -import { LinodeInterfaceIPs } from './LinodeInterfaceIPs'; -import { MaskableText } from 'src/components/MaskableText/MaskableText'; interface Props extends LinodeInterface { handlers: InterfaceActionHandlers; From 3cb6b2f22e0d0545e01d011b16a4b288bedce33a Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Wed, 16 Apr 2025 18:34:09 -0400 Subject: [PATCH 03/15] add testing --- .../LinodeInterfaceIPs.utils.test.ts | 96 +++++++++++++++++++ .../LinodeInterfaceIPs.utils.ts | 25 ++++- 2 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts new file mode 100644 index 00000000000..36e76ded738 --- /dev/null +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts @@ -0,0 +1,96 @@ +import { + linodeInterfaceFactoryPublic, + linodeInterfaceFactoryVlan, + linodeInterfaceFactoryVPC, +} from '@linode/utilities'; + +import { getLinodeInterfaceIPs } from './LinodeInterfaceIPs.utils'; + +describe('getLinodeInterfaceIPs', () => { + it('should return VPC IPs with the primary IP first', () => { + const linodeInterface = linodeInterfaceFactoryVPC.build({ + vpc: { + ipv4: { + addresses: [ + { address: '10.0.0.1' }, + { + address: '10.0.0.2', + primary: true, + nat_1_1_address: '255.255.255.255', + }, + { address: '10.0.0.3' }, + ], + ranges: [{ range: '10.0.0.5/32' }], + }, + }, + }); + + const ips = getLinodeInterfaceIPs(linodeInterface); + + expect(ips).toStrictEqual([ + '10.0.0.2', + '255.255.255.255 (VPC NAT)', + '10.0.0.1', + '10.0.0.3', + '10.0.0.5/32 (Range)', + ]); + }); + + it('should return Public Interface IPs with the primary IP first', () => { + const linodeInterface = linodeInterfaceFactoryPublic.build({ + public: { + ipv4: { + addresses: [ + { address: '10.0.0.1' }, + { + address: '10.0.0.2', + primary: true, + }, + { address: '10.0.0.3' }, + ], + }, + ipv6: { + ranges: [{ range: '192.168.1.0/24' }], + shared: [], + slaac: [{ address: '2600:3c11::f03c:93ff:fe3a:130f', prefix: '64' }], + }, + }, + }); + + const ips = getLinodeInterfaceIPs(linodeInterface); + + expect(ips).toStrictEqual([ + '10.0.0.2', + '10.0.0.1', + '10.0.0.3', + '192.168.1.0/24 (Range)', + '2600:3c11::f03c:93ff:fe3a:130f (SLAAC)', + ]); + }); + + it('should return an empty array for VLAN without a IPAM Address', () => { + const linodeInterface = linodeInterfaceFactoryVlan.build({ + vlan: { + ipam_address: '', + vlan_label: 'vlan-1', + }, + }); + + const ips = getLinodeInterfaceIPs(linodeInterface); + + expect(ips).toStrictEqual([]); + }); + + it('should return an empty with the IPAM Addresss for VLAN with an IPAM Address', () => { + const linodeInterface = linodeInterfaceFactoryVlan.build({ + vlan: { + ipam_address: '192.168.21.34', + vlan_label: 'vlan-1', + }, + }); + + const ips = getLinodeInterfaceIPs(linodeInterface); + + expect(ips).toStrictEqual(['192.168.21.34 (IPAM)']); + }); +}); diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts index c9f4edd6593..4f894a9e5ea 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts @@ -1,5 +1,9 @@ import type { LinodeInterface } from '@linode/api-v4'; +/** + * getLinodeInterfaceIPs + * @returns a string array of IP addresses for display purposes + */ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { if (linodeInterface.vlan && linodeInterface.vlan.ipam_address) { return [`${linodeInterface.vlan.ipam_address} (IPAM)`]; @@ -10,7 +14,11 @@ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { if (linodeInterface.public) { // IPv4s for (const address of linodeInterface.public.ipv4.addresses) { - ips.push(address.address); + if (address.primary) { + ips.unshift(address.address); + } else { + ips.push(address.address); + } } // IPv6 Ranges @@ -25,16 +33,23 @@ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { // IPv6 SLAAC for (const address of linodeInterface.public.ipv6.slaac) { - ips.push(`${address.address} ${address.prefix} (SLAAC)`); + ips.push(`${address.address} (SLAAC)`); } } if (linodeInterface.vpc) { // VPC IPv4s for (const address of linodeInterface.vpc.ipv4.addresses) { - ips.push(address.address); - if (address.nat_1_1_address) { - ips.push(`${address.nat_1_1_address} (VPC NAT)`); + if (address.primary) { + if (address.nat_1_1_address) { + ips.unshift(`${address.nat_1_1_address} (VPC NAT)`); + } + ips.unshift(address.address); + } else { + ips.push(address.address); + if (address.nat_1_1_address) { + ips.push(`${address.nat_1_1_address} (VPC NAT)`); + } } } // VPC IPv4 Ranges From eadbbe179bec56b5007d94eab45f43da6d052bb3 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 12:48:02 -0400 Subject: [PATCH 04/15] remove interface suffixes --- .../LinodeInterfaceIPs.utils.test.ts | 10 +++++----- .../LinodeInterfaces/LinodeInterfaceIPs.utils.ts | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts index 36e76ded738..779005f3d18 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.test.ts @@ -29,10 +29,10 @@ describe('getLinodeInterfaceIPs', () => { expect(ips).toStrictEqual([ '10.0.0.2', - '255.255.255.255 (VPC NAT)', + '255.255.255.255', '10.0.0.1', '10.0.0.3', - '10.0.0.5/32 (Range)', + '10.0.0.5/32', ]); }); @@ -63,8 +63,8 @@ describe('getLinodeInterfaceIPs', () => { '10.0.0.2', '10.0.0.1', '10.0.0.3', - '192.168.1.0/24 (Range)', - '2600:3c11::f03c:93ff:fe3a:130f (SLAAC)', + '192.168.1.0/24', + '2600:3c11::f03c:93ff:fe3a:130f', ]); }); @@ -91,6 +91,6 @@ describe('getLinodeInterfaceIPs', () => { const ips = getLinodeInterfaceIPs(linodeInterface); - expect(ips).toStrictEqual(['192.168.21.34 (IPAM)']); + expect(ips).toStrictEqual(['192.168.21.34']); }); }); diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts index 4f894a9e5ea..660973cde8b 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.utils.ts @@ -6,7 +6,7 @@ import type { LinodeInterface } from '@linode/api-v4'; */ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { if (linodeInterface.vlan && linodeInterface.vlan.ipam_address) { - return [`${linodeInterface.vlan.ipam_address} (IPAM)`]; + return [linodeInterface.vlan.ipam_address]; } const ips: string[] = []; @@ -23,17 +23,17 @@ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { // IPv6 Ranges for (const address of linodeInterface.public.ipv6.ranges) { - ips.push(`${address.range} (Range)`); + ips.push(address.range); } // IPv6 Shared for (const address of linodeInterface.public.ipv6.shared) { - ips.push(`${address.range} (Shared)`); + ips.push(address.range); } // IPv6 SLAAC for (const address of linodeInterface.public.ipv6.slaac) { - ips.push(`${address.address} (SLAAC)`); + ips.push(address.address); } } @@ -42,19 +42,19 @@ export function getLinodeInterfaceIPs(linodeInterface: LinodeInterface) { for (const address of linodeInterface.vpc.ipv4.addresses) { if (address.primary) { if (address.nat_1_1_address) { - ips.unshift(`${address.nat_1_1_address} (VPC NAT)`); + ips.unshift(address.nat_1_1_address); } ips.unshift(address.address); } else { ips.push(address.address); if (address.nat_1_1_address) { - ips.push(`${address.nat_1_1_address} (VPC NAT)`); + ips.push(address.nat_1_1_address); } } } // VPC IPv4 Ranges for (const address of linodeInterface.vpc.ipv4.ranges) { - ips.push(`${address.range} (Range)`); + ips.push(address.range); } } From ca2ebdc66c3f0e30b225e8a1bd265a962e9f9ea1 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:00:03 -0400 Subject: [PATCH 05/15] add some extra invalidations to fix table not updating when modifying an interface --- packages/queries/src/linodes/interfaces.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/queries/src/linodes/interfaces.ts b/packages/queries/src/linodes/interfaces.ts index cdddc19f522..48973ed2ca9 100644 --- a/packages/queries/src/linodes/interfaces.ts +++ b/packages/queries/src/linodes/interfaces.ts @@ -23,6 +23,7 @@ import type { UpgradeInterfacePayload, } from '@linode/api-v4'; import type { UseMutationOptions } from '@tanstack/react-query'; +import { networkingQueries } from '../networking'; export const useLinodeInterfacesQuery = ( linodeId: number, @@ -76,6 +77,12 @@ export const useLinodeInterfaceSettingsMutation = (linodeId: number) => { queryKey: linodeQueries.linode(linodeId)._ctx.interfaces._ctx.interface._def, }); + queryClient.invalidateQueries({ + queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, + }); + queryClient.invalidateQueries({ + queryKey: networkingQueries._def, + }); }, }); }; From 93b672405ada1e086ff0693bd0462aa4c7c32df0 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:06:16 -0400 Subject: [PATCH 06/15] improve form focus, forgot to commit on last PR --- .../VPCInterface/VPCIPv4Ranges.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Ranges.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Ranges.tsx index 99b391afc5c..4f416de1447 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Ranges.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Ranges.tsx @@ -15,7 +15,7 @@ import { VPCRangesDescription } from 'src/features/VPCs/components/VPCRangesDesc import type { ModifyLinodeInterfacePayload } from '@linode/api-v4'; export const VPCIPv4Ranges = () => { - const { control } = useFormContext(); + const { control, setFocus } = useFormContext(); const { fields, remove, append } = useFieldArray({ control, name: 'vpc.ipv4.ranges', @@ -40,6 +40,7 @@ export const VPCIPv4Ranges = () => { containerProps={{ flexGrow: 1 }} errorText={fieldState.error?.message} hideLabel + inputRef={field.ref} label={`VPC IPv4 Range ${index}`} onChange={field.onChange} value={field.value} @@ -47,7 +48,16 @@ export const VPCIPv4Ranges = () => { )} /> remove(index)} + onClick={() => { + remove(index); + + const previousRangeIndex = index - 1; + + // If there is a previous range, focus it when the current one is removed + if (previousRangeIndex >= 0) { + setFocus(`vpc.ipv4.ranges.${previousRangeIndex}.range`); + } + }} sx={{ p: 1 }} title="Remove" > From fb4ba5126e835e1af0a4d6b0191e82ba946b7f98 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:13:18 -0400 Subject: [PATCH 07/15] add some extra invalidations to fix table not updating when modifying an interface --- packages/queries/src/linodes/interfaces.ts | 36 +++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/queries/src/linodes/interfaces.ts b/packages/queries/src/linodes/interfaces.ts index 48973ed2ca9..71f0f8336e7 100644 --- a/packages/queries/src/linodes/interfaces.ts +++ b/packages/queries/src/linodes/interfaces.ts @@ -7,6 +7,7 @@ import { } from '@linode/api-v4'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { networkingQueries } from '../networking'; import { linodeQueries } from './linodes'; import type { @@ -23,7 +24,6 @@ import type { UpgradeInterfacePayload, } from '@linode/api-v4'; import type { UseMutationOptions } from '@tanstack/react-query'; -import { networkingQueries } from '../networking'; export const useLinodeInterfacesQuery = ( linodeId: number, @@ -77,12 +77,6 @@ export const useLinodeInterfaceSettingsMutation = (linodeId: number) => { queryKey: linodeQueries.linode(linodeId)._ctx.interfaces._ctx.interface._def, }); - queryClient.invalidateQueries({ - queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, - }); - queryClient.invalidateQueries({ - queryKey: networkingQueries._def, - }); }, }); }; @@ -132,16 +126,19 @@ export const useUpdateLinodeInterfaceMutation = ( ...options, onSuccess(linodeInterface, variables, context) { options?.onSuccess?.(linodeInterface, variables, context); + // Invalidate this Linode's interface queries queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.interfaces._ctx.interfaces .queryKey, }); + // Invalidate a Linode's IPs because this edit action can change a Linode's IPs queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, }); + // Set the specific interface in the cache queryClient.setQueryData( linodeQueries @@ -149,6 +146,17 @@ export const useUpdateLinodeInterfaceMutation = ( ._ctx.interfaces._ctx.interface(linodeInterface.id).queryKey, linodeInterface, ); + + // Invaliate networking queries because IPs likely changed + queryClient.invalidateQueries({ + queryKey: networkingQueries._def, + }); + + // Invalidate the Linode itself + queryClient.invalidateQueries({ + queryKey: linodeQueries.linode(linodeId).queryKey, + exact: true, + }); }, }, ); @@ -164,9 +172,23 @@ export const useDeleteLinodeInterfaceMutation = ( ...options, onSuccess(...params) { options?.onSuccess?.(...params); + + // remove the cached interface + queryClient.removeQueries({ + queryKey: linodeQueries + .linode(linodeId) + ._ctx.interfaces._ctx.interface(params[1]).queryKey, + }); + + // Invalidate the interfaces list queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.interfaces.queryKey, }); + + // Invalidate a Linode's IPs because this edit action can change a Linode's IPs + queryClient.invalidateQueries({ + queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, + }); }, }); }; From ca05f8833924bb8578100f6da25da90c262d7b92 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:29:02 -0400 Subject: [PATCH 08/15] add custom no options text for VPC select --- .../Linodes/LinodeCreate/Networking/VPC.tsx | 3 ++- .../src/features/Linodes/LinodeCreate/VPC/VPC.tsx | 14 +++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx index c9df03bdb9d..236f1da6979 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VPC.tsx @@ -14,6 +14,7 @@ import React, { useState } from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { LinkButton } from 'src/components/LinkButton'; +import { VPCPublicIPLabel } from 'src/features/VPCs/components/VPCPublicIPLabel'; import { REGION_CAVEAT_HELPER_TEXT, VPC_AUTO_ASSIGN_IPV4_TOOLTIP, @@ -24,7 +25,6 @@ import { VPCAvailability } from './VPCAvailability'; import { VPCRanges } from './VPCRanges'; import type { LinodeCreateFormValues } from '../utilities'; -import { VPCPublicIPLabel } from 'src/features/VPCs/components/VPCPublicIPLabel'; interface Props { index: number; @@ -80,6 +80,7 @@ export const VPC = ({ index }: Props) => { label="VPC" loading={isLoading} noMarginTop + noOptionsText="There are no VPCs in the selected region." onBlur={field.onBlur} onChange={(e, vpc) => { field.onChange(vpc?.id ?? null); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx index 8d6b11c1ff5..1545a895ca1 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx @@ -1,4 +1,4 @@ -import { useAllVPCsQuery, useRegionsQuery } from '@linode/queries'; +import { useAllVPCsQuery, useRegionQuery } from '@linode/queries'; import { Autocomplete, Box, @@ -12,7 +12,6 @@ import { TooltipIcon, Typography, } from '@linode/ui'; -import { doesRegionSupportFeature } from '@linode/utilities'; import React, { useState } from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; @@ -39,8 +38,6 @@ export const VPC = () => { const { control, formState, setValue } = useFormContext(); - const { data: regions } = useRegionsQuery(); - const [regionId, selectedVPCId, selectedSubnetId, linodeVPCIPAddress] = useWatch({ control, @@ -52,11 +49,9 @@ export const VPC = () => { ], }); - const regionSupportsVPCs = doesRegionSupportFeature( - regionId, - regions ?? [], - 'VPCs' - ); + const { data: region } = useRegionQuery(regionId); + + const regionSupportsVPCs = region?.capabilities.includes('VPCs') ?? false; const { data: vpcs, @@ -117,6 +112,7 @@ export const VPC = () => { label="Assign VPC" loading={isLoading} noMarginTop + noOptionsText="There are no VPCs in the selected region." onBlur={field.onBlur} onChange={(e, vpc) => { field.onChange(vpc?.id ?? null); From 423a3ef00f5cdae8b1cb62a42b485eba2735427e Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:39:53 -0400 Subject: [PATCH 09/15] invalidate linode interfaces when a range is deleted using range delete endpoint --- packages/queries/src/linodes/networking.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/queries/src/linodes/networking.ts b/packages/queries/src/linodes/networking.ts index 4e5da99ffa5..0bfbfbd8aad 100644 --- a/packages/queries/src/linodes/networking.ts +++ b/packages/queries/src/linodes/networking.ts @@ -104,6 +104,10 @@ export const useLinodeRemoveRangeMutation = (range: string) => { queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linode)._ctx.ips.queryKey, }); + queryClient.invalidateQueries({ + exact: true, + queryKey: linodeQueries.linode(linode)._ctx.interfaces.queryKey, + }); } }, }); From 70b460e7355713111d89a2370dc72dd8b551758b Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:42:53 -0400 Subject: [PATCH 10/15] invalidate linode interfaces when a range is deleted using range delete endpoint --- packages/queries/src/linodes/networking.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/queries/src/linodes/networking.ts b/packages/queries/src/linodes/networking.ts index 0bfbfbd8aad..a38fc13c3a7 100644 --- a/packages/queries/src/linodes/networking.ts +++ b/packages/queries/src/linodes/networking.ts @@ -105,7 +105,6 @@ export const useLinodeRemoveRangeMutation = (range: string) => { queryKey: linodeQueries.linode(linode)._ctx.ips.queryKey, }); queryClient.invalidateQueries({ - exact: true, queryKey: linodeQueries.linode(linode)._ctx.interfaces.queryKey, }); } From 84efb8f294e21d4419d1acd4394ee3285d8e6ae8 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:49:56 -0400 Subject: [PATCH 11/15] fix up more cache invalidations --- packages/queries/src/linodes/interfaces.ts | 33 ++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/queries/src/linodes/interfaces.ts b/packages/queries/src/linodes/interfaces.ts index 71f0f8336e7..6af423c2601 100644 --- a/packages/queries/src/linodes/interfaces.ts +++ b/packages/queries/src/linodes/interfaces.ts @@ -101,9 +101,26 @@ export const useCreateLinodeInterfaceMutation = (linodeId: number) => { { mutationFn: (data) => createLinodeInterface(linodeId, data), onSuccess() { + // Invalidate the list of interfaces queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.interfaces.queryKey, }); + + // Invalidate the Linode's IPs because adding a new interface likely adds IPs to the Linode + queryClient.invalidateQueries({ + queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, + }); + + // Invaliate networking queries because IPs likely changed + queryClient.invalidateQueries({ + queryKey: networkingQueries._def, + }); + + // Invalidate the Linode itself in case IPs changed + queryClient.invalidateQueries({ + queryKey: linodeQueries.linode(linodeId).queryKey, + exact: true, + }); }, }, ); @@ -127,6 +144,14 @@ export const useUpdateLinodeInterfaceMutation = ( onSuccess(linodeInterface, variables, context) { options?.onSuccess?.(linodeInterface, variables, context); + // Set the specific interface in the cache + queryClient.setQueryData( + linodeQueries + .linode(linodeId) + ._ctx.interfaces._ctx.interface(linodeInterface.id).queryKey, + linodeInterface, + ); + // Invalidate this Linode's interface queries queryClient.invalidateQueries({ queryKey: @@ -139,14 +164,6 @@ export const useUpdateLinodeInterfaceMutation = ( queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, }); - // Set the specific interface in the cache - queryClient.setQueryData( - linodeQueries - .linode(linodeId) - ._ctx.interfaces._ctx.interface(linodeInterface.id).queryKey, - linodeInterface, - ); - // Invaliate networking queries because IPs likely changed queryClient.invalidateQueries({ queryKey: networkingQueries._def, From 100cdce269163df03f04e73d14496d8188f655f1 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Thu, 17 Apr 2025 17:56:22 -0400 Subject: [PATCH 12/15] show support links for ip limits --- .../EditInterfaceDrawer/VPCInterface/VPCIPv4Address.tsx | 7 ++++++- .../EditInterfaceDrawer/VPCInterface/VPCIPv4Addresses.tsx | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Address.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Address.tsx index 34ac868413e..873e0a35ae7 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Address.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/VPCInterface/VPCIPv4Address.tsx @@ -8,6 +8,7 @@ import { import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; +import { ErrorMessage } from 'src/components/ErrorMessage'; import { VPCPublicIPLabel } from 'src/features/VPCs/components/VPCPublicIPLabel'; import type { @@ -31,7 +32,11 @@ export const VPCIPv4Address = (props: Props) => { return ( - {error && } + {error && ( + + + + )} { IPv4 Addresses {errors.vpc?.ipv4?.addresses?.message && ( - + + + )} {fields.map((field, index) => ( From 4e39a25ee3b2c3eb62f76cd63e64d8b172731e99 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 18 Apr 2025 09:30:37 -0400 Subject: [PATCH 13/15] feedback @bill-akamai --- .../LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx index 778886a663f..2c719a2b1e1 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/LinodeInterfaceIPs.tsx @@ -1,4 +1,4 @@ -import { Stack } from '@linode/ui'; +import { Stack, Typography } from '@linode/ui'; import React from 'react'; import { MaskableText } from 'src/components/MaskableText/MaskableText'; @@ -16,7 +16,7 @@ export const LinodeInterfaceIPs = ({ linodeInterface }: Props) => { const [primary, ...ips] = getLinodeInterfaceIPs(linodeInterface); if (!primary && ips.length === 0) { - return 'None'; + return None; } return ( From b75d3df58664d4d63e1e30fd76768842475abe71 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Mon, 21 Apr 2025 09:53:57 -0400 Subject: [PATCH 14/15] more invalidations @coliu-akamai --- packages/queries/src/linodes/interfaces.ts | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/queries/src/linodes/interfaces.ts b/packages/queries/src/linodes/interfaces.ts index 6af423c2601..ec54108780a 100644 --- a/packages/queries/src/linodes/interfaces.ts +++ b/packages/queries/src/linodes/interfaces.ts @@ -7,6 +7,7 @@ import { } from '@linode/api-v4'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { firewallQueries } from '../firewalls'; import { networkingQueries } from '../networking'; import { linodeQueries } from './linodes'; @@ -100,7 +101,7 @@ export const useCreateLinodeInterfaceMutation = (linodeId: number) => { return useMutation( { mutationFn: (data) => createLinodeInterface(linodeId, data), - onSuccess() { + onSuccess(linodeInterface, variables) { // Invalidate the list of interfaces queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.interfaces.queryKey, @@ -121,6 +122,19 @@ export const useCreateLinodeInterfaceMutation = (linodeId: number) => { queryKey: linodeQueries.linode(linodeId).queryKey, exact: true, }); + + // If a Firewall is attached at the time of creation... + if (variables.firewall_id) { + // Invalidate all Firewall lists + queryClient.invalidateQueries({ + queryKey: firewallQueries.firewalls.queryKey, + }); + + // Invalidate the specific firewall + queryClient.invalidateQueries({ + queryKey: firewallQueries.firewall(variables.firewall_id).queryKey, + }); + } }, }, ); @@ -206,6 +220,17 @@ export const useDeleteLinodeInterfaceMutation = ( queryClient.invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, }); + + // Because we don't easily know the interface's Firewall here, + // we'll just invalidate all firewall queries. + // If this ever needs to be optimized, we can fetch the interface's firewalls before deletion, + // and do a more granular invalidation knowing the firewall ID. + queryClient.invalidateQueries({ + queryKey: firewallQueries.firewall._def, + }); + queryClient.invalidateQueries({ + queryKey: firewallQueries.firewalls.queryKey, + }); }, }); }; From 977a9adeb01e856b63d62a808c53d7f1766b4109 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Mon, 21 Apr 2025 10:18:41 -0400 Subject: [PATCH 15/15] add similar VPC messaging in the Add Interface drawer --- .../LinodeInterfaces/AddInterfaceDrawer/VPC/VPCDetails.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/VPC/VPCDetails.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/VPC/VPCDetails.tsx index dc04d3a1bcc..47d2bc3305f 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/VPC/VPCDetails.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/VPC/VPCDetails.tsx @@ -36,6 +36,7 @@ export const VPCDetails = ({ regionId }: Props) => { label="VPC" loading={isLoading} noMarginTop + noOptionsText="You have no VPCs in this Linode's region." onBlur={field.onBlur} onChange={(e, vpc) => { field.onChange(vpc?.id ?? null);