diff --git a/app/pages/project/instances/NetworkingTab.tsx b/app/pages/project/instances/NetworkingTab.tsx
index 10817eac6..8c89087e4 100644
--- a/app/pages/project/instances/NetworkingTab.tsx
+++ b/app/pages/project/instances/NetworkingTab.tsx
@@ -9,7 +9,7 @@ import { useQuery } from '@tanstack/react-query'
import { createColumnHelper, getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
-import { type LoaderFunctionArgs } from 'react-router'
+import { Link, type LoaderFunctionArgs } from 'react-router'
import { match } from 'ts-pattern'
import {
@@ -347,6 +347,16 @@ export default function NetworkingTab() {
})
).data.items
+ // Firewall rules and other external networking state live on the VPC of the
+ // primary NIC. The parent InstancePage loader prefetches this VPC, but
+ // primaryNic may be undefined, so we can't use usePrefetchedQuery.
+ const primaryVpcId = nics.find((nic) => nic.primary)?.vpcId
+ const { data: primaryVpc } = useQuery({
+ // primaryVpcId is defined when enabled, so the assertion is safe
+ ...q(api.vpcView, { path: { vpc: primaryVpcId! } }),
+ enabled: !!primaryVpcId,
+ })
+
const { data: siloPools } = usePrefetchedQuery(
q(api.ipPoolList, { query: { limit: ALL_ISH } })
)
@@ -739,7 +749,7 @@ export default function NetworkingTab() {
-
+
+
+
+
+ {primaryVpc ? (
+ <>
+ Manage firewall rules affecting this instance in VPC{' '}
+
+ {primaryVpc.name}
+
+ .
+ >
+ ) : (
+ 'Firewall rules are managed on the VPC associated with the primary network interface.'
+ )}
+
+
)
}
diff --git a/test/e2e/instance-networking.e2e.ts b/test/e2e/instance-networking.e2e.ts
index 9c32f23ca..0b0117e58 100644
--- a/test/e2e/instance-networking.e2e.ts
+++ b/test/e2e/instance-networking.e2e.ts
@@ -22,6 +22,25 @@ const selectASiloImage = async (page: Page, name: string) => {
await page.getByRole('option', { name }).click()
}
+test('Instance networking tab — firewall rules card', async ({ page }) => {
+ // db1 has a primary NIC in mock-vpc, so the card names that VPC
+ await page.goto('/projects/mock-project/instances/db1/networking')
+ await expect(
+ page.getByText('Manage firewall rules affecting this instance in VPC mock-vpc')
+ ).toBeVisible()
+
+ // you-fail has no NICs, so there's no primary VPC and we show the fallback copy
+ await page.goto('/projects/mock-project/instances/you-fail/networking')
+ await expect(
+ page.getByText(
+ 'Firewall rules are managed on the VPC associated with the primary network interface.'
+ )
+ ).toBeVisible()
+ await expect(
+ page.getByText('Manage firewall rules affecting this instance in VPC')
+ ).toBeHidden()
+})
+
test('Instance networking tab — NIC table', async ({ page }) => {
await page.goto('/projects/mock-project/instances/db1')