[FE-4483] fix(studio): disable network bans for v3 projects (#50997)

Disables network bans for v3 (`AWS_K8S`) projects and shows a specific
unsupported notice. The shared banned-IP query waits for project details
and skips unsupported projects, covering both Database Settings and
Advisor for v3 and High Availability projects.

The hook returns the standard query result and uses `skipToken` to
prevent unsupported requests, including manual refetches. Database
Settings handles project-detail errors at the call site. Open unban
confirmations are cleared when the section becomes disabled, and
submission checks eligibility.

Addresses
[FE-4483](https://linear.app/supabase/issue/FE-4483/disable-network-bans-for-v3-aws-k8s-projects).

## To test

- Open Database Settings on a v3 project. Check that Network bans shows
the v3 notice, hides the IP list and unban controls, and makes no
network-bans retrieval request on initial load or reload, including
while Advisor is mounted.
- Check that an HA project still shows its existing notice and makes no
network-bans retrieval request on initial load or reload.
- Navigate from a supported project to a v3 or HA project and check that
no banned-IP request is sent for the unsupported project and no
banned-IP signals from the previous project appear in Advisor.
- If project details fail without cached data, check that Network bans
shows an error after retries finish and does not retrieve bans. A
successful retry should restore normal behavior.
- Open an unban confirmation on a supported project, then navigate to a
v3 or HA project. Check that the dialog closes without sending an unban
request and stays closed when returning. A newly opened confirmation
should still work.
- On a supported project, check the empty state and banned IP list.
Confirm that users with permission can unban an IP and users without
permission see a disabled button with the permissions tooltip.

Validation: 17 focused tests passed, covering automatic and manual
request suppression, project-detail error display and recovery, and
navigation between supported and unsupported projects. Changed-file
ESLint, Prettier, and full Studio typecheck (without the incremental
cache) passed. Earlier local browser checks on `9912c6c` confirmed no
retrieval requests for an HA project on AWS_K8S across reloads and
Advisor, and a successful empty state on a supported project. The local
failed-project case redirected to the organization after retries, so the
inline error remains verified by the component test only. The latest
preview, standalone v3 notice, populated bans/unban, and no-permission
tooltip still need browser verification.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Banned IP settings now show an unsupported-project notice for AWS
Kubernetes projects and hide ban lists and unban actions for AWS
Kubernetes and High Availability projects.
* Banned IP data loads only after project details are available and only
for supported projects; unsupported projects do not display cached ban
data.
  * Project-detail errors are shown separately from ban-list errors.
* Unban confirmations close when a project becomes unsupported or an
unban succeeds. Unbanning is unavailable when you lack update permission
or the project is unsupported.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
This commit is contained in:
Alaister YoungandAlaister Young authored and GitHub committed 2026-09-29 17:51:14 +00:00
1 parent ed6217a169
commit 995f6f65c7
4 files changed
+470 -71

No files matched your search

@@ -0,0 +1,134 @@
import { QueryClient } from '@tanstack/react-query'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { mockAnimationsApi } from 'jsdom-testing-mocks'
import { HttpResponse } from 'msw'
import { beforeEach, expect, test, vi } from 'vitest'
import { BannedIPs } from './BannedIPs'
import type { deleteBannedIPs } from '@/data/banned-ips/banned-ips-delete-mutations'
import type { IPData } from '@/data/banned-ips/banned-ips-query'
import type { ProjectDetail } from '@/data/projects/project-detail-query'
import { customRender } from '@/tests/lib/custom-render'
import { addAPIMock, type APIErrorBody } from '@/tests/lib/msw'
mockAnimationsApi()
const routeParams = vi.hoisted(() => ({ ref: 'default' }))
beforeEach(() => {
routeParams.ref = 'default'
})
vi.mock('common', async (importOriginal) => ({
...(await importOriginal<typeof import('common')>()),
IS_PLATFORM: true,
useParams: () => routeParams,
}))
vi.mock('@/lib/constants', async (importOriginal) => ({
...(await importOriginal<typeof import('@/lib/constants')>()),
IS_PLATFORM: true,
}))
vi.mock('@/hooks/misc/useCheckPermissions', () => ({
useAsyncCheckPermissions: () => ({ can: true }),
}))
vi.mock('@/lib/telemetry/track', () => ({ useTrack: () => vi.fn() }))
test('shows a project-details error instead of leaving Network bans loading', async () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () =>
HttpResponse.json<APIErrorBody>({ message: 'Project unavailable' }, { status: 500 }),
})
customRender(<BannedIPs />)
expect(await screen.findByText('Failed to retrieve project details')).toBeVisible()
expect(screen.getByText('Error: Project unavailable')).toBeVisible()
expect(screen.getByRole('link', { name: 'Contact support' })).toHaveAttribute(
'href',
expect.stringContaining('projectRef=default')
)
expect(screen.queryByRole('button', { name: 'Unban IP' })).not.toBeInTheDocument()
expect(
screen.queryByText('There are no banned IP addresses for your project')
).not.toBeInTheDocument()
})
const PROJECT: ProjectDetail = {
cloud_provider: 'AWS',
connectionString: 'postgresql://postgres:password@db.default.supabase.co:5432/postgres',
db_host: 'db.default.supabase.co',
dbVersion: 'supabase-postgres-15.1.0',
high_availability: false,
id: 1,
infra_compute_size: 'micro',
inserted_at: '2026-01-01T00:00:00.000Z',
integration_source: null,
is_branch_enabled: false,
is_physical_backups_enabled: false,
name: 'Test project',
organization_id: 1,
ref: 'default',
region: 'us-east-1',
restUrl: 'https://default.supabase.co',
status: 'ACTIVE_HEALTHY',
subscription_id: 'subscription-1',
updated_at: '2026-01-01T00:00:00.000Z',
}
test.each([
{ name: 'v3', cloud_provider: 'AWS_K8S', high_availability: false },
{ name: 'HA', cloud_provider: 'AWS', high_availability: true },
])('clears an open unban confirmation when navigating to $name', async (unsupported) => {
let bannedIPs: IPData = { banned_ipv4_addresses: ['203.0.113.10'] }
const unbanRequests: unknown[] = []
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: ({ params }) =>
HttpResponse.json<ProjectDetail>(
params.ref === 'default' ? PROJECT : { ...PROJECT, ...unsupported, ref: String(params.ref) }
),
})
addAPIMock({
method: 'post',
path: '/v1/projects/:ref/network-bans/retrieve',
response: () => HttpResponse.json<IPData>(bannedIPs),
})
addAPIMock({
method: 'delete',
path: '/v1/projects/:ref/network-bans',
response: async ({ request }) => {
unbanRequests.push(await request.json())
bannedIPs = { banned_ipv4_addresses: [] }
return HttpResponse.json<Awaited<ReturnType<typeof deleteBannedIPs>>>(null)
},
})
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
const { rerender } = customRender(<BannedIPs />, { queryClient })
fireEvent.click(await screen.findByRole('button', { name: 'Unban IP' }))
expect(await screen.findByRole('dialog', { name: 'Confirm Unban IP' })).toBeVisible()
routeParams.ref = 'unsupported'
rerender(<BannedIPs />)
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument())
expect(screen.queryByRole('button', { name: 'Unban IP' })).not.toBeInTheDocument()
expect(unbanRequests).toEqual([])
routeParams.ref = 'default'
rerender(<BannedIPs />)
const unbanButton = await screen.findByRole('button', { name: 'Unban IP' })
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
expect(unbanRequests).toEqual([])
fireEvent.click(unbanButton)
fireEvent.click(await screen.findByRole('button', { name: 'Confirm Unban' }))
expect(await screen.findByText('There are no banned IP addresses for your project')).toBeVisible()
expect(unbanRequests).toEqual([{ ipv4_addresses: ['203.0.113.10'] }])
})
@@ -1,9 +1,10 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { Globe } from 'lucide-react'
import { useState } from 'react'
import { useMemo, useState } from 'react'
import { toast } from 'sonner'
import { Badge, Card, CardContent, Skeleton } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import {
PageSection,
@@ -23,52 +24,56 @@ import { useBannedIPsQuery } from '@/data/banned-ips/banned-ips-query'
import { useUserIPAddressQuery } from '@/data/misc/user-ip-address-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useHighAvailability } from '@/hooks/misc/useHighAvailability'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useIsAwsK8sCloudProvider, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { DOCS_URL } from '@/lib/constants'
const HA_DISABLED_TITLE = 'Network bans unavailable on High Availability projects'
const HA_DISABLED_DESCRIPTION =
"We're working to bring network bans to High Availability projects. Contact support if this is blocking your work."
const V3_DISABLED_TITLE = 'Network bans unavailable on v3 projects'
export const BannedIPs = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: project, error: projectError } = useSelectedProjectQuery()
const { isHighAvailability } = useHighAvailability()
const isAwsK8s = useIsAwsK8sCloudProvider()
const [selectedIPToUnban, setSelectedIPToUnban] = useState<string | null>(null) // Track the selected IP for unban
const [selectedIPToUnban, setSelectedIPToUnban] = useState<string | null>(null)
const {
isPending: isLoadingIPList,
isFetching: isFetchingIPList,
data: ipList,
error: ipListError,
} = useBannedIPsQuery({
projectRef: ref,
})
} = useBannedIPsQuery({ projectRef: ref })
const { data: userIPAddress } = useUserIPAddressQuery()
const hasProjectError = !project && !!projectError
const ipListLoading = isLoadingIPList || isFetchingIPList
const [showUnban, setShowUnban] = useState(false)
const [confirmingIP, setConfirmingIP] = useState<string | null>(null) // Track the IP being confirmed for unban
const { can: canUnbanNetworks } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', {
resource: {
project_id: project?.id,
},
})
const isSectionDisabled = isHighAvailability || !canUnbanNetworks
const sectionDisabledReason = isHighAvailability
? HA_DISABLED_TITLE
: 'You need additional permissions to unban networks'
const isSectionDisabled = isHighAvailability || isAwsK8s || !canUnbanNetworks
if (isSectionDisabled && selectedIPToUnban !== null) {
setSelectedIPToUnban(null)
}
const sectionDisabledReason = useMemo(() => {
if (isHighAvailability) return HA_DISABLED_TITLE
if (isAwsK8s) return V3_DISABLED_TITLE
return 'You need additional permissions to unban networks'
}, [isHighAvailability, isAwsK8s])
const { mutate: unbanIPs, isPending: isUnbanning } = useBannedIPsDeleteMutation({
onSuccess: () => {
toast.success('IP address successfully unbanned')
setSelectedIPToUnban(null) // Reset the selected IP for unban
setShowUnban(false)
setSelectedIPToUnban(null)
},
onError: (error) => {
toast.error(`Failed to unban IP: ${error?.message}`)
@@ -76,19 +81,13 @@ export const BannedIPs = () => {
})
const onConfirmUnbanIP = () => {
if (confirmingIP == null || !ref) return
if (selectedIPToUnban === null || !ref || isSectionDisabled) return
unbanIPs({
projectRef: ref,
ips: [confirmingIP], // Pass the IP as an array
ips: [selectedIPToUnban], // Pass the IP as an array
})
}
const openConfirmationModal = (ip: string) => {
setSelectedIPToUnban(ip) // Set the selected IP for unban
setConfirmingIP(ip) // Set the IP being confirmed for unban
setShowUnban(true)
}
return (
<>
<PageSection id="banned-ips">
@@ -102,6 +101,13 @@ export const BannedIPs = () => {
<DocsButton href={`${DOCS_URL}/reference/cli/supabase-network-bans`} />
</PageSectionMeta>
<PageSectionContent>
{hasProjectError && (
<AlertError
error={projectError}
subject="Failed to retrieve project details"
projectRef={ref}
/>
)}
{isHighAvailability && (
<div className="mb-4">
<HighAvailabilityDisabledSectionNotice
@@ -110,47 +116,63 @@ export const BannedIPs = () => {
/>
</div>
)}
{!isHighAvailability &&
(ipListLoading ? (
<Card>
<CardContent className="space-y-4">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
</CardContent>
</Card>
) : ipListError ? (
<AlertError error={ipListError} subject="Failed to retrieve banned IP addresses" />
) : ipList.banned_ipv4_addresses.length > 0 ? (
<Card>
{ipList.banned_ipv4_addresses.map((ip) => (
<CardContent key={ip} className="flex items-center justify-between">
<div className="flex items-center space-x-5">
<Globe size={16} className="text-foreground-lighter" />
<p className="text-sm font-mono">{ip}</p>
{ip === userIPAddress && <Badge>Your IP address</Badge>}
</div>
<ButtonTooltip
disabled={isSectionDisabled}
onClick={() => openConfirmationModal(ip)}
tooltip={{
content: {
side: 'bottom',
text: isSectionDisabled ? sectionDisabledReason : undefined,
},
}}
>
Unban IP
</ButtonTooltip>
{!isHighAvailability && isAwsK8s && (
<Admonition
type="default"
title={V3_DISABLED_TITLE}
description="Fail2Ban is not supported on v3 projects."
/>
)}
{!hasProjectError && !isHighAvailability && !isAwsK8s && (
<>
{ipListError && (
<AlertError
error={ipListError}
subject="Failed to retrieve banned IP addresses"
projectRef={ref}
/>
)}
{!ipListError && ipListLoading && (
<Card>
<CardContent className="space-y-4">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full" />
</CardContent>
))}
</Card>
) : (
<Card>
<CardContent className="text-foreground text-sm">
There are no banned IP addresses for your project
</CardContent>
</Card>
))}
</Card>
)}
{!ipListError && !ipListLoading && ipList && (
<Card>
{ipList.banned_ipv4_addresses.length > 0 ? (
ipList.banned_ipv4_addresses.map((ip) => (
<CardContent key={ip} className="flex items-center justify-between">
<div className="flex items-center space-x-5">
<Globe size={16} className="text-foreground-lighter" />
<p className="text-sm font-mono">{ip}</p>
{ip === userIPAddress && <Badge>Your IP address</Badge>}
</div>
<ButtonTooltip
disabled={isSectionDisabled}
onClick={() => setSelectedIPToUnban(ip)}
tooltip={{
content: {
side: 'bottom',
text: isSectionDisabled ? sectionDisabledReason : undefined,
},
}}
>
Unban IP
</ButtonTooltip>
</CardContent>
))
) : (
<CardContent className="text-foreground text-sm">
There are no banned IP addresses for your project
</CardContent>
)}
</Card>
)}
</>
)}
</PageSectionContent>
</PageSection>
@@ -158,11 +180,11 @@ export const BannedIPs = () => {
variant="destructive"
size="medium"
loading={isUnbanning}
visible={showUnban}
visible={selectedIPToUnban !== null && !isSectionDisabled}
title="Confirm Unban IP"
confirmLabel="Confirm Unban"
confirmLabelLoading="Unbanning..."
onCancel={() => setShowUnban(false)}
onCancel={() => setSelectedIPToUnban(null)}
onConfirm={onConfirmUnbanIP}
alert={{
title: 'This action cannot be undone',
@@ -0,0 +1,233 @@
import { QueryClient } from '@tanstack/react-query'
import { act, waitFor } from '@testing-library/react'
import { HttpResponse } from 'msw'
import { describe, expect, test, vi } from 'vitest'
import { useBannedIPsQuery, type IPData } from './banned-ips-query'
import { useAdvisorSignals } from '@/components/ui/AdvisorPanel/useAdvisorSignals'
import { useProjectDetailQuery, type ProjectDetail } from '@/data/projects/project-detail-query'
import { customRenderHook } from '@/tests/lib/custom-render'
import { addAPIMock, type APIErrorBody } from '@/tests/lib/msw'
vi.mock('common', async (importOriginal) => ({
...(await importOriginal<typeof import('common')>()),
IS_PLATFORM: true,
}))
const PROJECT: ProjectDetail = {
cloud_provider: 'AWS',
connectionString: 'postgresql://postgres:password@db.default.supabase.co:5432/postgres',
db_host: 'db.default.supabase.co',
dbVersion: 'supabase-postgres-15.1.0',
high_availability: false,
id: 1,
infra_compute_size: 'micro',
inserted_at: '2026-01-01T00:00:00.000Z',
integration_source: null,
is_branch_enabled: false,
is_physical_backups_enabled: false,
name: 'Test project',
organization_id: 1,
ref: 'default',
region: 'us-east-1',
restUrl: 'https://default.supabase.co',
status: 'ACTIVE_HEALTHY',
subscription_id: 'subscription-1',
updated_at: '2026-01-01T00:00:00.000Z',
}
const BANNED_IPS: IPData = { banned_ipv4_addresses: ['203.0.113.10'] }
const mockBannedIPs = () => {
const requests: string[] = []
addAPIMock({
method: 'post',
path: '/v1/projects/:ref/network-bans/retrieve',
response: ({ request }) => {
requests.push(request.url)
return HttpResponse.json<IPData>(BANNED_IPS)
},
})
return requests
}
describe('useBannedIPsQuery', () => {
test.each([
{ name: 'v3', cloud_provider: 'AWS_K8S', high_availability: false },
{ name: 'HA', cloud_provider: 'AWS', high_availability: true },
{ name: 'v3 HA', cloud_provider: 'AWS_K8S', high_availability: true },
])('does not retrieve bans for $name with Settings and Advisor enabled', async (project) => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () => HttpResponse.json<ProjectDetail>({ ...PROJECT, ...project }),
})
const requests = mockBannedIPs()
const { result } = customRenderHook(() => ({
project: useProjectDetailQuery({ ref: 'default' }),
settings: useBannedIPsQuery({ projectRef: 'default' }),
advisor: useAdvisorSignals({ projectRef: 'default' }),
}))
await waitFor(() => expect(result.current.project.isSuccess).toBe(true))
expect(result.current.settings.fetchStatus).toBe('idle')
expect(result.current.advisor.data).toEqual([])
expect(requests).toEqual([])
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
try {
await act(async () => {
await expect(result.current.settings.refetch({ throwOnError: true })).rejects.toThrow()
})
expect(requests).toEqual([])
} finally {
consoleError.mockRestore()
}
})
test('shares banned IPs with Advisor on a supported project', async () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () => HttpResponse.json<ProjectDetail>(PROJECT),
})
const requests = mockBannedIPs()
const { result } = customRenderHook(() => ({
settings: useBannedIPsQuery({ projectRef: 'default' }),
advisor: useAdvisorSignals({ projectRef: 'default' }),
}))
await waitFor(() => expect(result.current.advisor.data).toHaveLength(1))
expect(result.current.settings.data).toEqual(BANNED_IPS)
expect(requests).toHaveLength(1)
})
test('waits for project details before retrieving bans', async () => {
const { promise, resolve } = Promise.withResolvers<void>()
let hasRequestedProject = false
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: async () => {
hasRequestedProject = true
await promise
return HttpResponse.json<ProjectDetail>(PROJECT)
},
})
const requests = mockBannedIPs()
const { result } = customRenderHook(() => useBannedIPsQuery({ projectRef: 'default' }))
try {
await waitFor(() => expect(hasRequestedProject).toBe(true))
expect(result.current.fetchStatus).toBe('idle')
expect(requests).toEqual([])
} finally {
resolve()
}
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(requests).toHaveLength(1)
})
test('waits for project details to recover before retrieving bans', async () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () =>
HttpResponse.json<APIErrorBody>({ message: 'Project unavailable' }, { status: 500 }),
})
const requests = mockBannedIPs()
const { result } = customRenderHook(() => ({
project: useProjectDetailQuery({ ref: 'default' }),
bans: useBannedIPsQuery({ projectRef: 'default' }),
}))
await waitFor(() => expect(result.current.project.isError).toBe(true))
expect(result.current.bans.fetchStatus).toBe('idle')
expect(requests).toEqual([])
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () => HttpResponse.json<ProjectDetail>(PROJECT),
})
await act(() => result.current.project.refetch())
await waitFor(() => expect(result.current.bans.isSuccess).toBe(true))
expect(result.current.bans.data).toEqual(BANNED_IPS)
expect(requests).toHaveLength(1)
})
test('keeps bans visible when project details fail to refresh with cached supported data', async () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () => HttpResponse.json<ProjectDetail>(PROJECT),
})
mockBannedIPs()
const { result } = customRenderHook(() => ({
project: useProjectDetailQuery({ ref: 'default' }),
bans: useBannedIPsQuery({ projectRef: 'default' }),
advisor: useAdvisorSignals({ projectRef: 'default' }),
}))
await waitFor(() => expect(result.current.advisor.data).toHaveLength(1))
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: () =>
HttpResponse.json<APIErrorBody>({ message: 'Project unavailable' }, { status: 500 }),
})
await act(async () => {
const projectResult = await result.current.project.refetch()
expect(projectResult.isError).toBe(true)
})
expect(result.current.bans.data).toEqual(BANNED_IPS)
expect(result.current.advisor.data).toHaveLength(1)
})
test.each([
{ projectRef: 'default', enabled: false },
{ projectRef: undefined, enabled: true },
])('does not fetch with $projectRef and enabled=$enabled', ({ projectRef, enabled }) => {
const { result } = customRenderHook(() => useBannedIPsQuery({ projectRef }, { enabled }))
expect(result.current.fetchStatus).toBe('idle')
})
test('checks the requested project when navigating from supported to v3', async () => {
addAPIMock({
method: 'get',
path: '/platform/projects/:ref',
response: ({ params }) =>
HttpResponse.json<ProjectDetail>({
...PROJECT,
ref: String(params.ref),
cloud_provider: params.ref === 'v3' ? 'AWS_K8S' : 'AWS',
}),
})
const requests = mockBannedIPs()
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
let projectRef = 'default'
const { result, rerender } = customRenderHook(
() => ({
project: useProjectDetailQuery({ ref: projectRef }),
bans: useBannedIPsQuery({ projectRef }),
advisor: useAdvisorSignals({ projectRef }),
}),
{ queryClient }
)
await waitFor(() => expect(result.current.bans.isSuccess).toBe(true))
act(() => {
projectRef = 'v3'
rerender()
})
await waitFor(() => expect(result.current.project.data?.ref).toBe('v3'))
expect(result.current.bans.fetchStatus).toBe('idle')
expect(result.current.advisor.data).toEqual([])
expect(requests).toHaveLength(1)
expect(requests[0]).toContain('/projects/default/network-bans/retrieve')
})
})
@@ -1,8 +1,10 @@
import { useQuery } from '@tanstack/react-query'
import { skipToken, useQuery } from '@tanstack/react-query'
import { IS_PLATFORM } from 'common'
import { BannedIPKeys } from './keys'
import { handleError, post } from '@/data/fetchers'
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
import { PROVIDERS } from '@/lib/constants'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
type BannedIPVariables = { projectRef?: string }
@@ -25,13 +27,21 @@ export type IPError = ResponseError
export const useBannedIPsQuery = <TData = IPData>(
{ projectRef }: BannedIPVariables,
{ enabled = true, ...options }: UseCustomQueryOptions<IPData, IPError, TData> = {}
) =>
useQuery<IPData, IPError, TData>({
) => {
const { data: project } = useProjectDetailQuery(
{ ref: projectRef },
{ enabled: enabled && IS_PLATFORM }
)
const isSupported =
!!project && !project.high_availability && project.cloud_provider !== PROVIDERS.AWS_K8S.id
return useQuery<IPData, IPError, TData>({
queryKey: BannedIPKeys.list(projectRef),
queryFn: ({ signal }) => getBannedIPs({ projectRef }, signal),
enabled: enabled && IS_PLATFORM && typeof projectRef !== 'undefined',
queryFn: isSupported ? ({ signal }) => getBannedIPs({ projectRef }, signal) : skipToken,
enabled: enabled && IS_PLATFORM && typeof projectRef !== 'undefined' && isSupported,
retry: false,
refetchOnWindowFocus: false,
staleTime: 60_000,
...options,
})
}