PITRSelection.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { useParams } from 'common'
  2. import Link from 'next/link'
  3. import { useRouter } from 'next/router'
  4. import { useState } from 'react'
  5. import { Alert, AlertDescription, AlertTitle, Button, Modal, WarningIcon } from 'ui'
  6. import { BackupsEmpty } from '../BackupsEmpty'
  7. import { BackupsStorageAlert } from '../BackupsStorageAlert'
  8. import type { Timezone } from './PITR.types'
  9. import { getClientTimezone } from './PITR.utils'
  10. import { PITRForm } from './PITRForm'
  11. import PITRStatus from './PITRStatus'
  12. import { FormHeader } from '@/components/ui/Forms/FormHeader'
  13. import { useBackupsQuery } from '@/data/database/backups-query'
  14. import { usePitrRestoreMutation } from '@/data/database/pitr-restore-mutation'
  15. import { useSetProjectStatus } from '@/data/projects/project-detail-query'
  16. import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
  17. import { PROJECT_STATUS } from '@/lib/constants'
  18. export const PITRSelection = () => {
  19. const router = useRouter()
  20. const { ref } = useParams()
  21. const { data: backups } = useBackupsQuery({ projectRef: ref })
  22. const { data: databases } = useReadReplicasQuery({ projectRef: ref })
  23. const { setProjectStatus } = useSetProjectStatus()
  24. const [showConfiguration, setShowConfiguration] = useState(false)
  25. const [showConfirmation, setShowConfirmation] = useState(false)
  26. const [selectedTimezone, setSelectedTimezone] = useState<Timezone>(getClientTimezone())
  27. const [selectedRecoveryPoint, setSelectedRecoveryPoint] = useState<{
  28. recoveryTimeTargetUnix: number
  29. recoveryTimeString: string
  30. recoveryTimeStringUtc: string
  31. }>()
  32. const hasReadReplicas = (databases ?? []).length > 1
  33. const {
  34. mutate: restoreFromPitr,
  35. isPending: isRestoring,
  36. isSuccess: isSuccessPITR,
  37. } = usePitrRestoreMutation({
  38. onSuccess: (_, variables) => {
  39. setTimeout(() => {
  40. setShowConfirmation(false)
  41. setProjectStatus({ ref: variables.ref, status: PROJECT_STATUS.RESTORING })
  42. router.push(`/project/${variables.ref}`)
  43. }, 3000)
  44. },
  45. })
  46. const { earliestPhysicalBackupDateUnix, latestPhysicalBackupDateUnix } =
  47. backups?.physicalBackupData ?? {}
  48. const hasNoBackupsAvailable = !earliestPhysicalBackupDateUnix || !latestPhysicalBackupDateUnix
  49. const onConfirmRestore = async () => {
  50. if (!ref) return console.error('Project ref is required')
  51. if (!selectedRecoveryPoint?.recoveryTimeTargetUnix)
  52. return console.error('Recovery time target unix is required')
  53. restoreFromPitr({
  54. ref,
  55. recovery_time_target_unix: selectedRecoveryPoint.recoveryTimeTargetUnix,
  56. })
  57. }
  58. return (
  59. <>
  60. <FormHeader
  61. title="Restore your database from a backup"
  62. description="Database changes are watched and recorded, so that you can restore your database to any point in time"
  63. />
  64. <BackupsStorageAlert />
  65. {hasNoBackupsAvailable ? (
  66. <BackupsEmpty />
  67. ) : (
  68. <>
  69. {hasReadReplicas && (
  70. <Alert variant="warning">
  71. <WarningIcon />
  72. <AlertTitle>
  73. Unable to restore from PITR as project has read replicas enabled
  74. </AlertTitle>
  75. <AlertDescription>
  76. You will need to remove all read replicas first from your project's infrastructure
  77. settings prior to starting a PITR restore.
  78. </AlertDescription>
  79. <div className="flex items-center gap-x-2 mt-2">
  80. {/* [Joshen] Ideally we have some links to a docs to explain why so */}
  81. <Button type="default">
  82. <Link href={`/project/${ref}/settings/infrastructure`}>
  83. Infrastructure settings
  84. </Link>
  85. </Button>
  86. </div>
  87. </Alert>
  88. )}
  89. {!showConfiguration ? (
  90. <PITRStatus
  91. selectedTimezone={selectedTimezone}
  92. onUpdateTimezone={setSelectedTimezone}
  93. onSetConfiguration={() => setShowConfiguration(true)}
  94. />
  95. ) : (
  96. <PITRForm
  97. earliestAvailableBackupUnix={earliestPhysicalBackupDateUnix}
  98. latestAvailableBackupUnix={latestPhysicalBackupDateUnix}
  99. onSubmit={(recoveryPoint) => {
  100. setSelectedRecoveryPoint(recoveryPoint)
  101. setShowConfirmation(true)
  102. }}
  103. />
  104. )}
  105. </>
  106. )}
  107. <Modal
  108. size="medium"
  109. visible={showConfirmation}
  110. onCancel={() => setShowConfirmation(false)}
  111. header="Point in time recovery review"
  112. customFooter={
  113. <div className="flex items-center justify-end space-x-2">
  114. <Button
  115. type="default"
  116. disabled={isRestoring || isSuccessPITR}
  117. onClick={() => setShowConfirmation(false)}
  118. >
  119. Cancel
  120. </Button>
  121. <Button
  122. type="warning"
  123. disabled={isRestoring || isSuccessPITR}
  124. loading={isRestoring || isSuccessPITR}
  125. onClick={onConfirmRestore}
  126. >
  127. I understand, begin restore
  128. </Button>
  129. </div>
  130. }
  131. >
  132. <Modal.Content>
  133. <div className="py-2 space-y-1">
  134. <p className="text-sm text-foreground-light">Your database will be restored to:</p>
  135. </div>
  136. <div className="py-2 flex flex-col gap-3">
  137. <div>
  138. <p className="text-sm font-mono text-foreground-lighter">Local Time</p>
  139. <p className="text-2xl">{selectedRecoveryPoint?.recoveryTimeString}</p>
  140. </div>
  141. <div>
  142. <p className="text-sm font-mono text-foreground-lighter">(UTC+00:00)</p>
  143. <p className="text-2xl">{selectedRecoveryPoint?.recoveryTimeStringUtc}</p>
  144. </div>
  145. </div>
  146. </Modal.Content>
  147. <Modal.Separator />
  148. <Modal.Content>
  149. <Alert variant="warning">
  150. <WarningIcon />
  151. <AlertTitle>This action cannot be undone, not canceled once started</AlertTitle>
  152. <AlertDescription>
  153. Any changes made to your database after this point in time will be lost. This includes
  154. any changes to your project's storage and authentication.
  155. </AlertDescription>
  156. </Alert>
  157. </Modal.Content>
  158. <Modal.Separator />
  159. <Modal.Content>
  160. <p className="text-sm text-foreground-light">
  161. Restores may take from a few minutes up to several hours depending on the size of your
  162. database. During this period, your project will not be available, until the restoration
  163. is completed.
  164. </p>
  165. </Modal.Content>
  166. </Modal>
  167. </>
  168. )
  169. }