BannedIPs.tsx156 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { Globe } from 'lucide-react'
4import { useState } from 'react'
5import { toast } from 'sonner'
6import { Badge, Card, CardContent, Skeleton } from 'ui'
7import {
8 PageSection,
9 PageSectionContent,
10 PageSectionDescription,
11 PageSectionMeta,
12 PageSectionSummary,
13 PageSectionTitle,
14} from 'ui-patterns'
15import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
16
17import AlertError from '@/components/ui/AlertError'
18import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
19import { DocsButton } from '@/components/ui/DocsButton'
20import { useBannedIPsDeleteMutation } from '@/data/banned-ips/banned-ips-delete-mutations'
21import { useBannedIPsQuery } from '@/data/banned-ips/banned-ips-query'
22import { useUserIPAddressQuery } from '@/data/misc/user-ip-address-query'
23import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
24import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
25import { DOCS_URL } from '@/lib/constants'
26
27export const BannedIPs = () => {
28 const { ref } = useParams()
29 const { data: project } = useSelectedProjectQuery()
30
31 const [selectedIPToUnban, setSelectedIPToUnban] = useState<string | null>(null) // Track the selected IP for unban
32
33 const {
34 isPending: isLoadingIPList,
35 isFetching: isFetchingIPList,
36 data: ipList,
37 error: ipListError,
38 } = useBannedIPsQuery({
39 projectRef: ref,
40 })
41
42 const { data: userIPAddress } = useUserIPAddressQuery()
43
44 const ipListLoading = isLoadingIPList || isFetchingIPList
45
46 const [showUnban, setShowUnban] = useState(false)
47 const [confirmingIP, setConfirmingIP] = useState<string | null>(null) // Track the IP being confirmed for unban
48
49 const { can: canUnbanNetworks } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', {
50 resource: {
51 project_id: project?.id,
52 },
53 })
54
55 const { mutate: unbanIPs, isPending: isUnbanning } = useBannedIPsDeleteMutation({
56 onSuccess: () => {
57 toast.success('IP address successfully unbanned')
58 setSelectedIPToUnban(null) // Reset the selected IP for unban
59 setShowUnban(false)
60 },
61 onError: (error) => {
62 toast.error(`Failed to unban IP: ${error?.message}`)
63 },
64 })
65
66 const onConfirmUnbanIP = () => {
67 if (confirmingIP == null || !ref) return
68 unbanIPs({
69 projectRef: ref,
70 ips: [confirmingIP], // Pass the IP as an array
71 })
72 }
73
74 const openConfirmationModal = (ip: string) => {
75 setSelectedIPToUnban(ip) // Set the selected IP for unban
76 setConfirmingIP(ip) // Set the IP being confirmed for unban
77 setShowUnban(true)
78 }
79
80 return (
81 <>
82 <PageSection id="banned-ips">
83 <PageSectionMeta>
84 <PageSectionSummary>
85 <PageSectionTitle>Network bans</PageSectionTitle>
86 <PageSectionDescription>
87 IP addresses temporarily blocked due to suspicious traffic
88 </PageSectionDescription>
89 </PageSectionSummary>
90 <DocsButton href={`${DOCS_URL}/reference/cli/briven-network-bans`} />
91 </PageSectionMeta>
92 <PageSectionContent>
93 {ipListLoading ? (
94 <Card>
95 <CardContent className="space-y-4">
96 <Skeleton className="h-4 w-full" />
97 <Skeleton className="h-4 w-full" />
98 </CardContent>
99 </Card>
100 ) : ipListError ? (
101 <AlertError error={ipListError} subject="Failed to retrieve banned IP addresses" />
102 ) : ipList.banned_ipv4_addresses.length > 0 ? (
103 <Card>
104 {ipList.banned_ipv4_addresses.map((ip) => (
105 <CardContent key={ip} className="flex items-center justify-between">
106 <div className="flex items-center space-x-5">
107 <Globe size={16} className="text-foreground-lighter" />
108 <p className="text-sm font-mono">{ip}</p>
109 {ip === userIPAddress && <Badge>Your IP address</Badge>}
110 </div>
111 <ButtonTooltip
112 type="default"
113 disabled={!canUnbanNetworks}
114 onClick={() => openConfirmationModal(ip)}
115 tooltip={{
116 content: {
117 side: 'bottom',
118 text: !canUnbanNetworks
119 ? 'You need additional permissions to unban networks'
120 : undefined,
121 },
122 }}
123 >
124 Unban IP
125 </ButtonTooltip>
126 </CardContent>
127 ))}
128 </Card>
129 ) : (
130 <Card>
131 <CardContent className="text-foreground text-sm">
132 There are no banned IP addresses for your project
133 </CardContent>
134 </Card>
135 )}
136 </PageSectionContent>
137 </PageSection>
138
139 <ConfirmationModal
140 variant="destructive"
141 size="medium"
142 loading={isUnbanning}
143 visible={showUnban}
144 title="Confirm Unban IP"
145 confirmLabel="Confirm Unban"
146 confirmLabelLoading="Unbanning..."
147 onCancel={() => setShowUnban(false)}
148 onConfirm={onConfirmUnbanIP}
149 alert={{
150 title: 'This action cannot be undone',
151 description: `Are you sure you want to unban this IP address ${selectedIPToUnban}?`,
152 }}
153 />
154 </>
155 )
156}