mirror of
https://github.com/supabase/supabase.git
synced 2026-09-07 02:20:52 +08:00
Shows the restore deadline as a date on the paused-project screens, and offers backup downloads while a paused project is still restorable (previously only after the restore window ended). Depends on a backend change — keep as draft until that is live. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Extended the paused-project restore window from 90 days to up to 1 year. * Added clearer, downloadable options for database backups and storage objects while a project is paused. * Paused-project screens now show an “available until” date when applicable (and updated resume guidance). * **Documentation** * Updated platform and troubleshooting guides to reflect the new 1-year restore window and post-window recovery limitations. * **Bug Fixes** * Standardized restore-window wording across the pause confirmation and paused-state UI. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
import { useParams } from 'common'
|
|
import { Database, Storage } from 'icons'
|
|
import { ChevronDown, Download } from 'lucide-react'
|
|
import { useEffect, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from 'ui'
|
|
|
|
import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
|
|
import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation'
|
|
import { useProjectPauseStatusQuery } from '@/data/projects/project-pause-status-query'
|
|
import { useStorageArchiveCreateMutation } from '@/data/storage/storage-archive-create-mutation'
|
|
import { useStorageArchiveQuery } from '@/data/storage/storage-archive-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { PROJECT_STATUS } from '@/lib/constants'
|
|
|
|
export const DownloadBackupsSection = () => {
|
|
const { ref } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const [toastId, setToastId] = useState<string | number>()
|
|
const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
|
|
|
|
const dbVersion = project?.dbVersion?.replace('supabase-postgres-', '')
|
|
|
|
const { data: pauseStatus } = useProjectPauseStatusQuery(
|
|
{ ref },
|
|
{ enabled: project?.status === PROJECT_STATUS.INACTIVE }
|
|
)
|
|
const latestBackup = pauseStatus?.latest_downloadable_backup_id
|
|
|
|
const { data: storageArchive, isSuccess: isStorageArchiveSuccess } = useStorageArchiveQuery(
|
|
{ projectRef: ref },
|
|
{
|
|
refetchInterval,
|
|
refetchOnWindowFocus: false,
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (!isStorageArchiveSuccess) return
|
|
if (storageArchive.fileUrl && refetchInterval !== false) {
|
|
toast.success('Downloading storage objects', { id: toastId })
|
|
setToastId(undefined)
|
|
setRefetchInterval(false)
|
|
downloadStorageArchive(storageArchive.fileUrl)
|
|
}
|
|
}, [isStorageArchiveSuccess, storageArchive, refetchInterval])
|
|
|
|
const storageArchiveUrl = storageArchive?.fileUrl
|
|
|
|
const { mutate: downloadBackup } = useBackupDownloadMutation({
|
|
onSuccess: (res) => {
|
|
const { fileUrl } = res
|
|
|
|
// Trigger browser download by create,trigger and remove tempLink
|
|
const tempLink = document.createElement('a')
|
|
tempLink.href = fileUrl
|
|
document.body.appendChild(tempLink)
|
|
tempLink.click()
|
|
document.body.removeChild(tempLink)
|
|
},
|
|
})
|
|
|
|
const { mutate: createStorageArchive } = useStorageArchiveCreateMutation({
|
|
onSuccess: () => {
|
|
const toastId = toast.loading(
|
|
'Retrieving storage archive. This may take a few minutes depending on the size of your storage objects.'
|
|
)
|
|
setToastId(toastId)
|
|
setRefetchInterval(5000)
|
|
},
|
|
})
|
|
|
|
const onSelectDownloadBackup = () => {
|
|
if (ref === undefined) return console.error('Project ref is required')
|
|
if (!latestBackup) return toast.error('No backups available for download')
|
|
|
|
const toastId = toast.loading('Fetching database backup')
|
|
|
|
downloadBackup(
|
|
{
|
|
ref,
|
|
backup: {
|
|
id: latestBackup,
|
|
},
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
toast.success('Downloading database backup', { id: toastId })
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
const downloadStorageArchive = (url: string) => {
|
|
const tempLink = document.createElement('a')
|
|
tempLink.href = url
|
|
document.body.appendChild(tempLink)
|
|
tempLink.click()
|
|
document.body.removeChild(tempLink)
|
|
}
|
|
|
|
const onSelectDownloadStorageArchive = () => {
|
|
if (!storageArchiveUrl) {
|
|
createStorageArchive({ projectRef: ref })
|
|
} else {
|
|
toast.success('Downloading storage objects')
|
|
downloadStorageArchive(storageArchiveUrl)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="border-t flex flex-col gap-3 sm:flex-row sm:justify-between sm:items-center px-6 py-4 bg-alternative">
|
|
<div>
|
|
<p className="text-sm">Export your data</p>
|
|
<p className="text-sm text-foreground-lighter">
|
|
Download backups for your database and storage objects
|
|
</p>
|
|
</div>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="default" icon={<Download />} iconRight={<ChevronDown />}>
|
|
Download backups
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent className="w-60" align="end">
|
|
<DropdownMenuItemTooltip
|
|
className="gap-x-2"
|
|
disabled={!latestBackup}
|
|
onClick={() => onSelectDownloadBackup()}
|
|
tooltip={{
|
|
content: {
|
|
side: 'right',
|
|
text: 'No backups available, please reach out via support for assistance',
|
|
},
|
|
}}
|
|
>
|
|
<Database size={16} />
|
|
Database backup (PG: {dbVersion})
|
|
</DropdownMenuItemTooltip>
|
|
<DropdownMenuItem className="gap-x-2" onClick={() => onSelectDownloadStorageArchive()}>
|
|
<Storage size={16} />
|
|
Storage objects
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
)
|
|
}
|