BannedIPs.tsx 5.4 KB

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