PauseDisabledState.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { useParams } from 'common'
  2. import { Database, Storage } from 'icons'
  3. import { ChevronDown, Download, ExternalLink } from 'lucide-react'
  4. import { useEffect, useState } from 'react'
  5. import { toast } from 'sonner'
  6. import {
  7. Button,
  8. DropdownMenu,
  9. DropdownMenuContent,
  10. DropdownMenuItem,
  11. DropdownMenuTrigger,
  12. } from 'ui'
  13. import { Admonition, TimestampInfo } from 'ui-patterns'
  14. import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
  15. import { InlineLink } from '@/components/ui/InlineLink'
  16. import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation'
  17. import { useProjectPauseStatusQuery } from '@/data/projects/project-pause-status-query'
  18. import { useStorageArchiveCreateMutation } from '@/data/storage/storage-archive-create-mutation'
  19. import { useStorageArchiveQuery } from '@/data/storage/storage-archive-query'
  20. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  21. import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants'
  22. export const PauseDisabledState = () => {
  23. const { ref } = useParams()
  24. const { data: project } = useSelectedProjectQuery()
  25. const [toastId, setToastId] = useState<string | number>()
  26. const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
  27. const dbVersion = project?.dbVersion?.replace('briven-postgres-', '')
  28. const { data: pauseStatus } = useProjectPauseStatusQuery(
  29. { ref },
  30. { enabled: project?.status === PROJECT_STATUS.INACTIVE }
  31. )
  32. const latestBackup = pauseStatus?.latest_downloadable_backup_id
  33. const { data: storageArchive, isSuccess: isStorageArchiveSuccess } = useStorageArchiveQuery(
  34. { projectRef: ref },
  35. {
  36. refetchInterval,
  37. refetchOnWindowFocus: false,
  38. }
  39. )
  40. useEffect(() => {
  41. if (!isStorageArchiveSuccess) return
  42. if (storageArchive.fileUrl && refetchInterval !== false) {
  43. toast.success('Downloading storage objects', { id: toastId })
  44. setToastId(undefined)
  45. setRefetchInterval(false)
  46. downloadStorageArchive(storageArchive.fileUrl)
  47. }
  48. }, [isStorageArchiveSuccess, storageArchive, refetchInterval])
  49. const storageArchiveUrl = storageArchive?.fileUrl
  50. const { mutate: downloadBackup } = useBackupDownloadMutation({
  51. onSuccess: (res) => {
  52. const { fileUrl } = res
  53. // Trigger browser download by create,trigger and remove tempLink
  54. const tempLink = document.createElement('a')
  55. tempLink.href = fileUrl
  56. document.body.appendChild(tempLink)
  57. tempLink.click()
  58. document.body.removeChild(tempLink)
  59. },
  60. })
  61. const { mutate: createStorageArchive } = useStorageArchiveCreateMutation({
  62. onSuccess: () => {
  63. const toastId = toast.loading(
  64. 'Retrieving storage archive. This may take a few minutes depending on the size of your storage objects.'
  65. )
  66. setToastId(toastId)
  67. setRefetchInterval(5000)
  68. },
  69. })
  70. const onSelectDownloadBackup = () => {
  71. if (ref === undefined) return console.error('Project ref is required')
  72. if (!latestBackup) return toast.error('No backups available for download')
  73. const toastId = toast.loading('Fetching database backup')
  74. downloadBackup(
  75. {
  76. ref,
  77. backup: {
  78. id: latestBackup,
  79. },
  80. },
  81. {
  82. onSuccess: () => {
  83. toast.success('Downloading database backup', { id: toastId })
  84. },
  85. }
  86. )
  87. }
  88. const downloadStorageArchive = (url: string) => {
  89. const tempLink = document.createElement('a')
  90. tempLink.href = url
  91. document.body.appendChild(tempLink)
  92. tempLink.click()
  93. document.body.removeChild(tempLink)
  94. }
  95. const onSelectDownloadStorageArchive = () => {
  96. if (!storageArchiveUrl) {
  97. createStorageArchive({ projectRef: ref })
  98. } else {
  99. toast.success('Downloading storage objects')
  100. downloadStorageArchive(storageArchiveUrl)
  101. }
  102. }
  103. return (
  104. <>
  105. <Admonition
  106. showIcon={false}
  107. type="warning"
  108. className="rounded-none border-0 px-6 [&>div>div>div]:flex [&>div>div>div]:flex-col [&>div>div>div]:gap-y-3"
  109. title="Project can no longer be restored through the dashboard"
  110. >
  111. <p className="leading-normal!">
  112. This project has been paused for over{' '}
  113. <span className="text-foreground">
  114. {pauseStatus?.max_days_till_restore_disabled ?? 90} days
  115. </span>{' '}
  116. and cannot be restored through the dashboard. However, your data remains intact and can be
  117. downloaded as a backup.
  118. </p>
  119. {!!pauseStatus?.last_paused_on && (
  120. <p className="text-foreground-lighter text-sm">
  121. Project last paused on{' '}
  122. <TimestampInfo
  123. className="text-sm"
  124. labelFormat="DD MMM YYYY"
  125. utcTimestamp={pauseStatus.last_paused_on}
  126. />
  127. </p>
  128. )}
  129. <div>
  130. <p className="leading-normal! mb-1!">Recovery options:</p>
  131. <ul className="flex flex-col gap-y-0.5">
  132. <li className="flex items-center gap-x-2">
  133. <ExternalLink size={14} />
  134. <InlineLink
  135. href={`${DOCS_URL}/guides/platform/migrating-within-briven/dashboard-restore`}
  136. >
  137. Restore the backup to a new Briven project
  138. </InlineLink>
  139. </li>
  140. <li className="flex items-center gap-x-2">
  141. <ExternalLink size={14} />
  142. <InlineLink href={`${DOCS_URL}/guides/local-development/restoring-downloaded-backup`}>
  143. Restore the backup on your local machine
  144. </InlineLink>
  145. </li>
  146. </ul>
  147. </div>
  148. </Admonition>
  149. <div className="border-t flex flex-col gap-3 sm:flex-row sm:justify-between sm:items-center px-6 py-4 bg-alternative">
  150. <div>
  151. <p className="text-sm">Export your data</p>
  152. <p className="text-sm text-foreground-lighter">
  153. Download backups for your database and storage objects
  154. </p>
  155. </div>
  156. <DropdownMenu>
  157. <DropdownMenuTrigger asChild>
  158. <Button type="default" icon={<Download />} iconRight={<ChevronDown />}>
  159. Download backups
  160. </Button>
  161. </DropdownMenuTrigger>
  162. <DropdownMenuContent className="w-60" align="end">
  163. <DropdownMenuItemTooltip
  164. className="gap-x-2"
  165. disabled={!latestBackup}
  166. onClick={() => onSelectDownloadBackup()}
  167. tooltip={{
  168. content: {
  169. side: 'right',
  170. text: 'No backups available, please reach out via support for assistance',
  171. },
  172. }}
  173. >
  174. <Database size={16} />
  175. Database backup (PG: {dbVersion})
  176. </DropdownMenuItemTooltip>
  177. <DropdownMenuItem className="gap-x-2" onClick={() => onSelectDownloadStorageArchive()}>
  178. <Storage size={16} />
  179. Storage objects
  180. </DropdownMenuItem>
  181. {/* [Joshen] Once storage object download is supported, can just use the below component */}
  182. {/* <DropdownMenuItem className="gap-x-2" onClick={() => onSelectDownloadStorageArchive()}>
  183. <Storage size={16} />
  184. Download storage objects
  185. </DropdownMenuItem> */}
  186. </DropdownMenuContent>
  187. </DropdownMenu>
  188. </div>
  189. </>
  190. )
  191. }