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 =