diff --git a/packages/api-v4/.changeset/pr-13394-changed-1770903849543.md b/packages/api-v4/.changeset/pr-13394-changed-1770903849543.md new file mode 100644 index 00000000000..b5ebcae1586 --- /dev/null +++ b/packages/api-v4/.changeset/pr-13394-changed-1770903849543.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Changed +--- + +UIE-9888: Added new fields in NodeBalancer details object and NodeBalancerVPC object as per API changes ([#13394](https://github.com/linode/manager/pull/13394)) diff --git a/packages/api-v4/src/nodebalancers/types.ts b/packages/api-v4/src/nodebalancers/types.ts index 77d577d33c6..4ee00d3070c 100644 --- a/packages/api-v4/src/nodebalancers/types.ts +++ b/packages/api-v4/src/nodebalancers/types.ts @@ -10,7 +10,7 @@ type UDPStickiness = 'none' | 'session' | 'source_ip'; export type Stickiness = TCPStickiness | UDPStickiness; -type NodeBalancerType = 'common' | 'premium'; +type NodeBalancerType = 'common' | 'premium' | 'premium_40GB'; export interface LKEClusterInfo { id: number; @@ -33,6 +33,8 @@ export interface NodeBalancer { */ client_udp_sess_throttle?: number; created: string; + frontend_address_type: 'public' | 'vpc'; + frontend_vpc_subnet_id: null | number; hostname: string; id: number; ipv4: string; @@ -145,6 +147,7 @@ export interface NodeBalancerVpcConfig { ipv4_range: null | string; ipv6_range: null | string; nodebalancer_id: number; + purpose: 'backend' | 'frontend'; subnet_id: number; vpc_id: number; } diff --git a/packages/manager/.changeset/pr-13394-changed-1770903888397.md b/packages/manager/.changeset/pr-13394-changed-1770903888397.md new file mode 100644 index 00000000000..d3223a0ac18 --- /dev/null +++ b/packages/manager/.changeset/pr-13394-changed-1770903888397.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +UIE-9888: Display front end IP and backend VPCs for Nodebalancer ([#13394](https://github.com/linode/manager/pull/13394)) diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.test.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.test.tsx index 5bf5b5559f5..5d524fb8437 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.test.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.test.tsx @@ -1,7 +1,7 @@ import { nodeBalancerConfigFactory, - nodeBalancerConfigVPCFactory, nodeBalancerFactory, + nodeBalancerVPCFactory, } from '@linode/utilities'; import { waitFor } from '@testing-library/react'; import * as React from 'react'; @@ -51,8 +51,8 @@ vi.mock('@linode/queries', async () => { }; }); -const nodeBalancerDetails = 'NodeBalancer Details'; -const nbVpcConfig = nodeBalancerConfigVPCFactory.build(); +const nodeBalancerDetails = 'Details'; +const nbVpcConfig = nodeBalancerVPCFactory.build(); describe('SummaryPanel', () => { beforeEach(() => { @@ -110,8 +110,8 @@ describe('SummaryPanel', () => { expect(getByText('Host Name:')).toBeVisible(); expect(getByText('example.com')).toBeVisible(); expect(getByText('Region:')).toBeVisible(); - // Type should not display for non-premium NBs - expect(queryByText('Type:')).not.toBeInTheDocument(); + // Type should be visible and default to Basic since the NB is not premium + expect(getByText('Basic')).toBeVisible(); // Cluster should not display for if the NB is not associated with LKE or LKE-E expect(queryByText('Cluster:')).not.toBeInTheDocument(); @@ -120,11 +120,11 @@ describe('SummaryPanel', () => { expect(getByText('mock-firewall-1')).toBeVisible(); // IP Address panel - expect(getByText('IP Addresses')).toBeVisible(); + expect(getByText('Frontend Configuration')).toBeVisible(); expect(getByText('0.0.0.0')).toBeVisible(); // VPC Details Panel - expect(getByText('VPC')).toBeVisible(); + expect(getByText('Backend Configuration - VPC')).toBeVisible(); expect(getByText('Subnets:')).toBeVisible(); expect(getByText(`${nbVpcConfig.ipv4_range}`)).toBeVisible(); @@ -133,6 +133,18 @@ describe('SummaryPanel', () => { expect(getByText('Add a tag')).toBeVisible(); }); + it('displays type: Basic if the nodebalancer is non premium', () => { + queryMocks.useNodeBalancerQuery.mockReturnValue({ + data: nodeBalancerFactory.build({ type: 'common' }), + }); + + const { container } = renderWithTheme(); + + expect(container.querySelector('[data-qa-type]')).toHaveTextContent( + 'Type: Basic' + ); + }); + it('displays type: premium if the nodebalancer is premium', () => { queryMocks.useNodeBalancerQuery.mockReturnValue({ data: nodeBalancerFactory.build({ type: 'premium' }), @@ -145,6 +157,18 @@ describe('SummaryPanel', () => { ); }); + it('displays type: Enterprise if the nodebalancer is premium_40GB', () => { + queryMocks.useNodeBalancerQuery.mockReturnValue({ + data: nodeBalancerFactory.build({ type: 'premium_40GB' }), + }); + + const { container } = renderWithTheme(); + + expect(container.querySelector('[data-qa-type]')).toHaveTextContent( + 'Type: Enterprise' + ); + }); + it('displays link to cluster if it exists', () => { queryMocks.useNodeBalancerQuery.mockReturnValue({ data: nodeBalancerFactory.build({ diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx index 8b46e8a6541..b2a0d16552f 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerDetail/NodeBalancerSummary/SummaryPanel.tsx @@ -50,23 +50,27 @@ export const SummaryPanel = () => { flags.isNodebalancerVPCEnabled ); + // "/nodebalancers/:id/vpcs" returns both frontend and backend VPC configs, + // but we only want to display the backend configs and + // a nodebalancer can have only one backend VPC. + const nbBackendVpcConfig = + vpcConfig?.data.filter((v) => v.purpose === 'backend')[0] ?? null; + const { data: vpcDetails } = useVPCQuery( - Number(vpcConfig?.data[0]?.vpc_id) || -1, - Boolean(vpcConfig?.data[0]?.vpc_id) + Number(nbBackendVpcConfig?.vpc_id) || -1, + Boolean(nbBackendVpcConfig?.vpc_id) ); - const nbVPCConfigs = vpcConfig?.data ?? []; const subnets = vpcDetails?.subnets ?? []; - const mergedSubnets = nbVPCConfigs.map((config) => { - const subnet = subnets.find((s) => s.id === config.subnet_id); + const subnet = subnets.find((s) => s.id === nbBackendVpcConfig?.subnet_id); - return { - id: config.subnet_id, - label: subnet?.label ?? `Subnet ${config.subnet_id}`, - ipv4Range: config.ipv4_range, - }; - }); + const subnetWithConfigData = { + id: nbBackendVpcConfig?.subnet_id, + label: subnet?.label ?? `Subnet ${nbBackendVpcConfig?.subnet_id}`, + ipv4Range: nbBackendVpcConfig?.ipv4_range, + ipv6Range: nbBackendVpcConfig?.ipv6_range, + }; // If we can't get the cluster (status === 'error'), we can assume it's been deleted const { status: clusterStatus } = useKubernetesClusterQuery({ @@ -101,38 +105,16 @@ export const SummaryPanel = () => { - NodeBalancer Details + Details - {nodebalancer.type === 'premium' && ( - - - Type: - Premium - - - )} - {nodebalancer.lke_cluster && ( - - - Cluster: - {clusterStatus === 'error' ? ( - <> - - {nodebalancer.lke_cluster.label} - - (deleted) - - ) : ( - - {nodebalancer.lke_cluster.label} - - )} - - - )} + + + Type: + {nodebalancer.type === 'common' && 'Basic'} + {nodebalancer.type === 'premium' && 'Premium'} + {nodebalancer.type === 'premium_40GB' && 'Enterprise'} + + Ports: @@ -166,7 +148,7 @@ export const SummaryPanel = () => { Host Name: - {nodebalancer.hostname} + {nodebalancer.hostname || 'None'} @@ -176,61 +158,83 @@ export const SummaryPanel = () => { - {displayFirewallLink && ( + {nodebalancer.lke_cluster && ( - Firewall + LKE Cluster - - - {linkText} - - + + + Cluster: + {clusterStatus === 'error' ? ( + <> + + {nodebalancer.lke_cluster.label} + + (deleted) + + ) : ( + + {nodebalancer.lke_cluster.label} + + )} + + )} - IP Addresses + Frontend Configuration - - {nodebalancer?.ipv4 && ( + + Type: + {nodebalancer.frontend_address_type === 'public' + ? 'Public' + : nodebalancer.frontend_address_type === 'vpc' + ? 'VPC' + : ''} + + + + {nodebalancer?.ipv4 && ( + - )} - {nodebalancer?.ipv6 && ( + + )} + {nodebalancer?.ipv6 && ( + - )} - - + + )} + - {flags.isNodebalancerVPCEnabled && Boolean(vpcConfig?.data.length) && ( + {flags.isNodebalancerVPCEnabled && nbBackendVpcConfig && ( - VPC + Backend Configuration - VPC VPC:{' '} - {vpcConfig?.data.map((vpc, i) => ( - + {nbBackendVpcConfig && ( + {vpcDetails?.label} - {i < vpcConfig.data.length - 1 ? ', ' : ''} - ))} + )} @@ -238,22 +242,58 @@ export const SummaryPanel = () => { Subnets: - {mergedSubnets.map((subnet) => ( - + {subnetWithConfigData && ( + - {`${subnet.label}:`} - - - {subnet.ipv4Range} + {`${subnetWithConfigData.label}`} + + {subnetWithConfigData.ipv4Range && ( + + + + )} + {subnetWithConfigData.ipv6Range && ( + + + + )} + - ))} + )} )} + {displayFirewallLink && ( + + + Firewall + + + + {linkText} + + + + )} Tags @@ -289,7 +329,6 @@ const StyledSummarySection = styled(Paper, { })(({ theme }) => ({ height: '93%', marginBottom: theme.spacing(2), - minHeight: '160px', padding: theme.spacing(2.5), })); diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancerVPC.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancerVPC.tsx index c28242278cc..b54badd44ff 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancerVPC.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancerVPC.tsx @@ -15,28 +15,33 @@ export const NodeBalancerVPC = ({ nodeBalancerId }: Props) => { const { data: vpcConfig, isLoading: isVPCConfigLoading } = useNodeBalancerVPCConfigsBetaQuery(nodeBalancerId, Boolean(nodeBalancerId)); + // NodeBalancerVPCConfigsBetaQuery returns both frontend and backend VPC configs, + // but we only want to display the backend configs and + // a nodebalancer can have only one backend VPC. + const nbBackendVpcConfig = + vpcConfig?.data.filter((v) => v.purpose === 'backend')[0] ?? null; + const { data: vpcDetails, isLoading: isVPCDetailsLoading } = useVPCQuery( - Number(vpcConfig?.data[0]?.vpc_id), - Boolean(vpcConfig?.data[0]?.vpc_id) + Number(nbBackendVpcConfig?.vpc_id), + Boolean(nbBackendVpcConfig?.vpc_id) ); if (isVPCConfigLoading || isVPCDetailsLoading) { return ; } - if (vpcConfig?.data?.length === 0) { + if (!nbBackendVpcConfig) { return 'None'; } - return vpcConfig?.data.map(({ vpc_id: vpcId }, i) => ( - + return ( + {vpcDetails?.label} - {i < vpcConfig.data.length - 1 ? ', ' : ''} - )); + ); }; diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.test.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.test.tsx index 72934f64d7a..70049b834a2 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.test.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.test.tsx @@ -89,7 +89,7 @@ describe('NodeBalancersLanding', () => { expect(getByText('Backend Status')).toBeVisible(); expect(getByText('Transferred')).toBeVisible(); expect(getByText('Ports')).toBeVisible(); - expect(getByText('IP Address')).toBeVisible(); + expect(getByText('Frontend IP')).toBeVisible(); expect(getByText('Region')).toBeVisible(); }); diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx index 5b88ac4b80c..bd0d96fd660 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx @@ -126,7 +126,7 @@ export const NodeBalancersLanding = () => { Transferred Ports - IP Address + Frontend IP ({ useAllNodeBalancerConfigsQuery: vi.fn().mockReturnValue({}), useNodeBalancerQuery: vi.fn().mockReturnValue({}), + useNodeBalancerVPCConfigsBetaQuery: vi.fn().mockReturnValue({}), useNodeBalancersFirewallsQuery: vi.fn().mockReturnValue({}), })); @@ -27,8 +26,9 @@ vi.mock('@linode/queries', async () => { const actual = await vi.importActual('@linode/queries'); return { ...actual, - useAllNodeBalancerConfigsQuery: queryMocks.useAllNodeBalancerConfigsQuery, useNodeBalancerQuery: queryMocks.useNodeBalancerQuery, + useNodeBalancerVPCConfigsBetaQuery: + queryMocks.useNodeBalancerVPCConfigsBetaQuery, useNodeBalancersFirewallsQuery: queryMocks.useNodeBalancersFirewallsQuery, }; }); @@ -41,19 +41,28 @@ describe('SubnetNodeBalancerRow', () => { label: 'test-nodebalancer', }; - const configs = [ - { nodes_status: { up: 3, down: 1 } }, - { nodes_status: { up: 2, down: 2 } }, - ]; + const subnetId = 456; const firewalls = makeResourcePage( firewallFactory.buildList(1, { label: 'mock-firewall' }) ); - const subnetNodebalancer = subnetAssignedNodebalancerDataFactory.build({ - id: nodebalancer.id, - ipv4_range: '192.168.99.0/30', - }); + const vpcConfigs = makeResourcePage([ + nodeBalancerVPCFactory.build({ + ipv4_range: '192.168.1.0/30', + ipv6_range: '2001:db8::1/64', + nodebalancer_id: nodebalancer.id, + purpose: 'frontend', + subnet_id: subnetId, + }), + nodeBalancerVPCFactory.build({ + ipv4_range: '192.168.2.0/30', + ipv6_range: '2001:db8::2/64', + nodebalancer_id: nodebalancer.id, + purpose: 'backend', + subnet_id: subnetId, + }), + ]); it('renders loading state', async () => { queryMocks.useNodeBalancerQuery.mockReturnValue({ @@ -62,8 +71,8 @@ describe('SubnetNodeBalancerRow', () => { const { getByTestId } = renderWithTheme( wrapWithTableBody( ) ); @@ -77,28 +86,36 @@ describe('SubnetNodeBalancerRow', () => { queryMocks.useNodeBalancerQuery.mockReturnValue({ data: nodebalancer, }); - queryMocks.useAllNodeBalancerConfigsQuery.mockReturnValue({ - data: configs, - }); queryMocks.useNodeBalancersFirewallsQuery.mockReturnValue({ data: firewalls, }); + queryMocks.useNodeBalancerVPCConfigsBetaQuery.mockReturnValue({ + data: vpcConfigs, + }); const { getByText, getByRole } = renderWithTheme( wrapWithTableBody( ) ); await waitFor(() => { - expect(getByText(nodebalancer.label)).toBeInTheDocument(); + getByText(nodebalancer.label); }); - expect(getByText(subnetNodebalancer.ipv4_range)).toBeInTheDocument(); - expect(getByText('mock-firewall')).toBeInTheDocument(); + // Frontend IPv4 range + getByText('192.168.1.0/30'); + // Frontend IPv6 range + getByText('2001:db8::1/64'); + // Backend IPv4 range + getByText('192.168.2.0/30'); + // Backend IPv6 range + getByText('2001:db8::2/64'); + // Firewall + getByText('mock-firewall'); const nodebalancerLink = getByRole('link', { name: nodebalancer.label, diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetNodebalancerRow.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetNodebalancerRow.tsx index 5246002a51e..91671f72cb0 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetNodebalancerRow.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetNodebalancerRow.tsx @@ -1,7 +1,7 @@ import { - useAllNodeBalancerConfigsQuery, useNodeBalancerQuery, useNodeBalancersFirewallsQuery, + useNodeBalancerVPCConfigsBetaQuery, } from '@linode/queries'; import { Box, CircleProgress, Typography } from '@linode/ui'; import ErrorOutline from '@mui/icons-material/ErrorOutline'; @@ -11,60 +11,67 @@ import { Link } from 'src/components/Link'; import { TableCell } from 'src/components/TableCell'; import { TableRow } from 'src/components/TableRow'; -import type { APIError, Firewall, NodeBalancerConfig } from '@linode/api-v4'; +const LOADING_TEXT = 'Loading...'; + +import type { APIError, Firewall, NodeBalancerVpcConfig } from '@linode/api-v4'; interface Props { hover?: boolean; - ipv4: string; nodeBalancerId: number; + subnetId: number; } export const SubnetNodeBalancerRow = ({ nodeBalancerId, hover = false, - ipv4, + subnetId, }: Props) => { const { data: nodebalancer, error: nodebalancerError, isLoading: nodebalancerLoading, } = useNodeBalancerQuery(nodeBalancerId); - const { - data: configs, - isLoading: isConfigsLoading, - error: configsError, - } = useAllNodeBalancerConfigsQuery(Number(nodeBalancerId)); const { data: attachedFirewallData, isLoading, error, } = useNodeBalancersFirewallsQuery(Number(nodeBalancerId)); - const getNodebalancerStatus = ( - data: NodeBalancerConfig[], + const { + data: vpcConfigs, + error: vpcConfigsError, + isLoading: isVpcConfigsLoading, + } = useNodeBalancerVPCConfigsBetaQuery( + Number(nodeBalancerId), + Boolean(nodeBalancerId) + ); + + const frontendVpcConfig = vpcConfigs?.data.find( + (config) => config.purpose === 'frontend' && config.subnet_id === subnetId + ); + + const backendVpcConfig = vpcConfigs?.data.find( + (config) => config.purpose === 'backend' && config.subnet_id === subnetId + ); + + const getVpcIpCellString = ( + data: NodeBalancerVpcConfig | undefined, + ipProperty: 'ipv4_range' | 'ipv6_range', loading: boolean, error?: APIError[] ): React.JSX.Element | string => { if (loading) { - return 'Loading...'; + return LOADING_TEXT; } if (error) { - return 'Error retrieving Status'; + return 'Error retrieving IP'; } - const down = data?.reduce((acc: number, config) => { - return acc + config.nodes_status.down; - }, 0); - - const up = data?.reduce((acc: number, config) => { - return acc + config.nodes_status.up; - }, 0); + if (!data || data.subnet_id !== subnetId) { + return '—'; + } - return ( - <> - {up} up - {down} down - - ); + return data[ipProperty] ?? '—'; }; const getFirewallsCellString = ( @@ -73,7 +80,7 @@ export const SubnetNodeBalancerRow = ({ error?: APIError[] ): React.JSX.Element | string => { if (loading) { - return 'Loading...'; + return LOADING_TEXT; } if (error) { @@ -142,14 +149,38 @@ export const SubnetNodeBalancerRow = ({ {nodebalancer?.label} - - {getNodebalancerStatus( - configs ?? [], - isConfigsLoading, - configsError ?? undefined + + {getVpcIpCellString( + frontendVpcConfig, + 'ipv4_range', + isVpcConfigsLoading, + vpcConfigsError ?? undefined + )} + + + {getVpcIpCellString( + frontendVpcConfig, + 'ipv6_range', + isVpcConfigsLoading, + vpcConfigsError ?? undefined + )} + + + {getVpcIpCellString( + backendVpcConfig, + 'ipv4_range', + isVpcConfigsLoading, + vpcConfigsError ?? undefined + )} + + + {getVpcIpCellString( + backendVpcConfig, + 'ipv6_range', + isVpcConfigsLoading, + vpcConfigsError ?? undefined )} - {ipv4} {getFirewallsCellString( attachedFirewallData?.data ?? [], @@ -163,9 +194,11 @@ export const SubnetNodeBalancerRow = ({ export const SubnetNodebalancerTableRowHead = ( - NodeBalancer - Backend Status - VPC IPv4 Range - Firewalls + NodeBalancer + Frontend IPv4 + Frontend IPv6 + Backend IPv4 Ranges + Backend IPv6 Ranges + Firewall ); diff --git a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.test.tsx b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.test.tsx index 5959af08e95..d885bc3bb61 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.test.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.test.tsx @@ -287,8 +287,10 @@ describe('VPC Subnets table', () => { await userEvent.click(expandTableButton); await findByText('NodeBalancer'); - await findByText('Backend Status'); - await findByText('VPC IPv4 Range'); + await findByText('Frontend IPv4'); + await findByText('Frontend IPv6'); + await findByText('Backend IPv4 Ranges'); + await findByText('Backend IPv6 Ranges'); } ); diff --git a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx index ed27397248d..fe49e01abee 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/VPCSubnetsTable.tsx @@ -388,9 +388,9 @@ export const VPCSubnetsTable = (props: Props) => { {subnet.nodebalancers.map((nb) => ( ))} diff --git a/packages/manager/src/mocks/mockState.ts b/packages/manager/src/mocks/mockState.ts index c3acbcf1c4f..66be91b6f58 100644 --- a/packages/manager/src/mocks/mockState.ts +++ b/packages/manager/src/mocks/mockState.ts @@ -43,6 +43,7 @@ export const emptyStore: MockState = { linodeIps: [], linodes: [], locks: [], + nodeBalancerVPCs: [], nodeBalancerConfigNodes: [], nodeBalancerConfigs: [], nodeBalancers: [], diff --git a/packages/manager/src/mocks/presets/crud/handlers/nodebalancers.ts b/packages/manager/src/mocks/presets/crud/handlers/nodebalancers.ts index f9ecf041997..810300363b7 100644 --- a/packages/manager/src/mocks/presets/crud/handlers/nodebalancers.ts +++ b/packages/manager/src/mocks/presets/crud/handlers/nodebalancers.ts @@ -24,6 +24,7 @@ import type { NodeBalancerConfig, NodeBalancerConfigNode, NodeBalancerStats, + NodeBalancerVpcConfig, PriceType, } from '@linode/api-v4'; import type { StrictResponse } from 'msw'; @@ -102,6 +103,35 @@ export const getNodeBalancers = (mockState: MockState) => [ } ), + http.get( + '*/v4beta/nodebalancers/:id/vpcs', + async ({ + params, + request, + }): Promise< + StrictResponse< + APIErrorResponse | APIPaginatedResponse + > + > => { + const nodeBalancerId = Number(params.id); + const nodeBalancer = await mswDB.get('nodeBalancers', nodeBalancerId); + const nodeBalancerVPCs = await mswDB.getAll('nodeBalancerVPCs'); + + if (!nodeBalancer || !nodeBalancerVPCs) { + return makeNotFoundResponse(); + } + + const configs = nodeBalancerVPCs.filter( + (config) => config.nodebalancer_id === nodeBalancerId + ); + + return makePaginatedResponse({ + data: configs, + request, + }); + } + ), + http.get( '*/v4/nodebalancers/:id/configs/:configId', async ({ diff --git a/packages/manager/src/mocks/presets/crud/seeds/nodebalancers.ts b/packages/manager/src/mocks/presets/crud/seeds/nodebalancers.ts index 9756ba4edfe..0d79e941f30 100644 --- a/packages/manager/src/mocks/presets/crud/seeds/nodebalancers.ts +++ b/packages/manager/src/mocks/presets/crud/seeds/nodebalancers.ts @@ -2,6 +2,7 @@ import { nodeBalancerConfigFactory, nodeBalancerConfigNodeFactory, nodeBalancerFactory, + nodeBalancerVPCFactory, } from '@linode/utilities'; import { getSeedsCountMap } from 'src/dev-tools/utils'; @@ -32,6 +33,13 @@ export const nodeBalancerSeeder: MockSeeder = { ), }); + const nodeBalancerVPCSeeds = seedWithUniqueIds<'nodeBalancerVPCs'>({ + dbEntities: await mswDB.getAll('nodeBalancerVPCs'), + seedEntities: nodeBalancerSeeds.map((nb) => + nodeBalancerVPCFactory.build({ nodebalancer_id: nb.id }) + ), + }); + const nodeBalancerConfigNodeSeeds = seedWithUniqueIds<'nodeBalancerConfigNodes'>({ dbEntities: await mswDB.getAll('nodeBalancerConfigNodes'), @@ -54,6 +62,7 @@ export const nodeBalancerSeeder: MockSeeder = { nodeBalancerConfigs: mockState.nodeBalancerConfigs.concat( nodeBalancerConfigSeeds ), + nodeBalancerVPCs: mockState.nodeBalancerVPCs.concat(nodeBalancerVPCSeeds), nodeBalancers: mockState.nodeBalancers.concat(nodeBalancerSeeds), }; diff --git a/packages/manager/src/mocks/types.ts b/packages/manager/src/mocks/types.ts index 67f3fd53ff6..14b8887ec69 100644 --- a/packages/manager/src/mocks/types.ts +++ b/packages/manager/src/mocks/types.ts @@ -26,6 +26,7 @@ import type { NodeBalancer, NodeBalancerConfig, NodeBalancerConfigNode, + NodeBalancerVpcConfig, Notification, PlacementGroup, Region, @@ -232,6 +233,7 @@ export interface MockState { locks: ResourceLock[]; nodeBalancerConfigNodes: NodeBalancerConfigNode[]; nodeBalancerConfigs: NodeBalancerConfig[]; + nodeBalancerVPCs: NodeBalancerVpcConfig[]; nodeBalancers: NodeBalancer[]; notificationQueue: Notification[]; placementGroups: PlacementGroup[]; diff --git a/packages/utilities/src/factories/nodebalancer.ts b/packages/utilities/src/factories/nodebalancer.ts index 77c2fa122ae..8dcc6a16488 100644 --- a/packages/utilities/src/factories/nodebalancer.ts +++ b/packages/utilities/src/factories/nodebalancer.ts @@ -12,10 +12,18 @@ import type { export const nodeBalancerFactory = Factory.Sync.makeFactory({ client_conn_throttle: 0, created: '2019-12-12T00:00:00', + frontend_address_type: Factory.each((i) => { + if (i % 2 === 0) { + return 'vpc'; + } else { + return 'public'; + } + }), + frontend_vpc_subnet_id: null, hostname: 'example.com', id: Factory.each((id) => id), ipv4: '0.0.0.0', - ipv6: null, + ipv6: '2600:3c11:e954:1::1', label: Factory.each((i) => `nodebalancer-id-${i}`), region: 'us-east', tags: [], @@ -25,8 +33,28 @@ export const nodeBalancerFactory = Factory.Sync.makeFactory({ total: 0, }, updated: '2019-12-13T00:00:00', - lke_cluster: null, - type: 'common', + lke_cluster: Factory.each((i) => { + if (i % 2 === 0) { + return { + id: 1, + type: 'lkecluster', + label: 'cluster-1', + url: '/v4/lke/clusters/1', + }; + } else { + return null; + } + }), + type: Factory.each((i) => { + if (i === 1) { + return 'premium_40GB'; + } + if (i % 2 === 0) { + return 'premium'; + } else { + return 'common'; + } + }), }); export const nodeBalancerConfigFactory = @@ -67,14 +95,15 @@ export const nodeBalancerConfigNodeFactory = vpc_config_id: null, }); -export const nodeBalancerConfigVPCFactory = +export const nodeBalancerVPCFactory = Factory.Sync.makeFactory({ id: Factory.each((i) => i), ipv4_range: Factory.each((i) => `192.168.${i}.0/30`), ipv6_range: null, - nodebalancer_id: Factory.each((i) => 1000 + i), - subnet_id: Factory.each((i) => 2000 + i), - vpc_id: Factory.each((i) => 3000 + i), + nodebalancer_id: Factory.each((i) => i), + subnet_id: Factory.each((i) => i), + vpc_id: Factory.each((i) => i), + purpose: 'backend', }); export const nodeBalancerStatsFactory =