BackupsList.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { useParams } from 'common'
  2. import dayjs from 'dayjs'
  3. import { Clock } from 'lucide-react'
  4. import { useRouter } from 'next/router'
  5. import { useState } from 'react'
  6. import { toast } from 'sonner'
  7. import { TimestampInfo } from 'ui-patterns'
  8. import { Admonition } from 'ui-patterns/admonition'
  9. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  10. import { BackupItem } from './BackupItem'
  11. import { BackupsEmpty } from './BackupsEmpty'
  12. import { BackupsStorageAlert } from './BackupsStorageAlert'
  13. import Panel from '@/components/ui/Panel'
  14. import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
  15. import { useBackupRestoreMutation } from '@/data/database/backup-restore-mutation'
  16. import { DatabaseBackup, useBackupsQuery } from '@/data/database/backups-query'
  17. import { useSetProjectStatus } from '@/data/projects/project-detail-query'
  18. import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
  19. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  20. import { PROJECT_STATUS } from '@/lib/constants'
  21. export const BackupsList = () => {
  22. const router = useRouter()
  23. const { ref: projectRef } = useParams()
  24. const [selectedBackup, setSelectedBackup] = useState<DatabaseBackup>()
  25. const { hasAccess: hasAccessToBackups } = useCheckEntitlements('backup.retention_days')
  26. const { setProjectStatus } = useSetProjectStatus()
  27. const { data: selectedProject } = useSelectedProjectQuery()
  28. const isHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY
  29. const { data: backups } = useBackupsQuery({ projectRef })
  30. const {
  31. mutate: restoreFromBackup,
  32. isPending: isRestoring,
  33. isSuccess: isSuccessBackup,
  34. } = useBackupRestoreMutation({
  35. onSuccess: () => {
  36. if (projectRef) {
  37. setTimeout(() => {
  38. setProjectStatus({ ref: projectRef, status: PROJECT_STATUS.RESTORING })
  39. toast.success(
  40. `Restoring database back to ${dayjs(selectedBackup?.inserted_at).format(
  41. 'DD MMM YYYY HH:mm:ss'
  42. )}`
  43. )
  44. router.push(`/project/${projectRef}`)
  45. }, 3000)
  46. }
  47. },
  48. })
  49. const sortedBackups = (backups?.backups ?? []).sort(
  50. (a, b) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf()
  51. )
  52. const isPitrEnabled = backups?.pitr_enabled
  53. if (!hasAccessToBackups) {
  54. return (
  55. <UpgradeToPro
  56. addon="pitr"
  57. source="backups"
  58. featureProposition="have up to 7 days of scheduled backups"
  59. icon={<Clock size={20} />}
  60. primaryText="Free Plan does not include project backups."
  61. secondaryText="Upgrade to the Pro Plan for up to 7 days of scheduled backups."
  62. buttonText="Upgrade"
  63. />
  64. )
  65. }
  66. if (isPitrEnabled) return null
  67. return (
  68. <>
  69. <div className="space-y-6">
  70. {sortedBackups.length === 0 ? (
  71. <BackupsEmpty />
  72. ) : (
  73. <>
  74. <BackupsStorageAlert />
  75. <Panel>
  76. {sortedBackups?.map((x, i: number) => {
  77. return (
  78. <BackupItem
  79. key={x.id}
  80. backup={x}
  81. index={i}
  82. isHealthy={isHealthy}
  83. onSelectBackup={() => setSelectedBackup(x)}
  84. />
  85. )
  86. })}
  87. </Panel>
  88. </>
  89. )}
  90. </div>
  91. <ConfirmationModal
  92. size="small"
  93. confirmLabel="Restore"
  94. confirmLabelLoading="Restoring..."
  95. variant="warning"
  96. visible={selectedBackup !== undefined}
  97. title="Restore from backup"
  98. loading={isRestoring || isSuccessBackup}
  99. onCancel={() => setSelectedBackup(undefined)}
  100. onConfirm={() => {
  101. if (projectRef === undefined) return console.error('Project ref required')
  102. if (selectedBackup === undefined) return console.error('Backup required')
  103. restoreFromBackup({ ref: projectRef, backup: selectedBackup })
  104. }}
  105. >
  106. <div className="space-y-3">
  107. {!!selectedBackup && (
  108. <p className="text-sm">
  109. This will restore your database to the backup made on{' '}
  110. <TimestampInfo
  111. displayAs="utc"
  112. utcTimestamp={selectedBackup.inserted_at}
  113. labelFormat="DD MMM YYYY HH:mm:ss (ZZ)"
  114. className="text-sm!"
  115. />
  116. </p>
  117. )}
  118. <Admonition
  119. showIcon={false}
  120. type="warning"
  121. title="This action cannot be undone"
  122. description={
  123. <ul className="list-disc list-inside">
  124. <li>Your project will be offline during restoration</li>
  125. <li>Any new data since this backup will be lost</li>
  126. </ul>
  127. }
  128. />
  129. </div>
  130. </ConfirmationModal>
  131. </>
  132. )
  133. }