mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 10:25:06 +03:00
## Context Original intention was to fix the PITR UI for smaller viewports, but realised that the layout of the UI could be improved as well. (Tbh this UI could do with a bit of revisiting, but just making patches for now to improve what's existing) Fixes also apply to the restore to new project page since they share the same component ## Changes involved - Am opting to change the layout of the UI a little such that date selection is on the left, and time selection is up top - Fits the user flow a bit better - you select the parameters you want, and the final message at the bottom is the summary <img width="1033" height="525" alt="image" src="https://github.com/user-attachments/assets/614c0327-f5db-4e1f-a2a1-ae8a9ea89978" /> - In the confirmation dialog, we were originally showing the top label as "Local time" which I feel is inaccurate especially if the user has selected a different timezone from where they're located at. - Opting to display the full name of the selected timezone instead <img width="546" height="261" alt="image" src="https://github.com/user-attachments/assets/cd8838a1-8476-4492-bc86-cac229685e5a" /> - RE mobile layout - am currently just opting to have them in a column fashion although I feel like this isn't ideal either (requires revisiting of the UI as a whole to adjust the layout on desktop too) - e.g the Date picker here could be a popover like Unified Logs to streamline what is essentially a form <img width="507" height="831" alt="image" src="https://github.com/user-attachments/assets/c8e055bd-49c3-4d0a-9d8c-e54f53e9fcb4" /> - Also fixed the CTA URL for read replicas, was still pointing to /settings/infrastructure, should point to database/replication <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Point-in-time recovery now includes the selected timezone throughout the restore workflow. * Restore confirmations display the chosen timezone and a clear summary of the target recovery date and time. * Recovery details now show the earliest and latest available backups for the selected date, including the two-minute matching window. * **Bug Fixes** * Updated the read-replica management link to direct users to database replication settings. * **Style** * Improved restore form layout, calendar presentation, and timezone selector alignment. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
180 lines
6.8 KiB
TypeScript
180 lines
6.8 KiB
TypeScript
import { useParams } from 'common'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { useState } from 'react'
|
|
import {
|
|
Alert,
|
|
AlertDescription,
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
AlertTitle,
|
|
Button,
|
|
DialogSection,
|
|
DialogSectionSeparator,
|
|
WarningIcon,
|
|
} from 'ui'
|
|
|
|
import { BackupsEmpty } from '../BackupsEmpty'
|
|
import { BackupsStorageAlert } from '../BackupsStorageAlert'
|
|
import type { Timezone } from './PITR.types'
|
|
import { getClientTimezone } from './PITR.utils'
|
|
import { PITRForm } from './PITRForm'
|
|
import PITRStatus from './PITRStatus'
|
|
import { FormHeader } from '@/components/ui/Forms/FormHeader'
|
|
import { useBackupsQuery } from '@/data/database/backups-query'
|
|
import { usePitrRestoreMutation } from '@/data/database/pitr-restore-mutation'
|
|
import { useSetProjectStatus } from '@/data/projects/project-detail-query'
|
|
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
|
|
import { PROJECT_STATUS } from '@/lib/constants'
|
|
|
|
export const PITRSelection = () => {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
|
|
const { data: backups } = useBackupsQuery({ projectRef: ref })
|
|
const { data: databases } = useReadReplicasQuery({ projectRef: ref })
|
|
const { setProjectStatus } = useSetProjectStatus()
|
|
|
|
const [showConfiguration, setShowConfiguration] = useState(false)
|
|
const [showConfirmation, setShowConfirmation] = useState(false)
|
|
const [selectedTimezone, setSelectedTimezone] = useState<Timezone>(getClientTimezone())
|
|
const [selectedRecoveryPoint, setSelectedRecoveryPoint] = useState<{
|
|
selectedTimezone: Timezone
|
|
recoveryTimeTargetUnix: number
|
|
recoveryTimeString: string
|
|
recoveryTimeStringUtc: string
|
|
}>()
|
|
|
|
const hasReadReplicas = (databases ?? []).length > 1
|
|
|
|
const { mutateAsync: restoreFromPitr } = usePitrRestoreMutation({
|
|
onSuccess: (_, variables) => {
|
|
setTimeout(() => {
|
|
setShowConfirmation(false)
|
|
setProjectStatus({ ref: variables.ref, status: PROJECT_STATUS.RESTORING })
|
|
router.push(`/project/${variables.ref}`)
|
|
}, 3000)
|
|
},
|
|
})
|
|
|
|
const { earliestPhysicalBackupDateUnix, latestPhysicalBackupDateUnix } =
|
|
backups?.physicalBackupData ?? {}
|
|
const hasNoBackupsAvailable = !earliestPhysicalBackupDateUnix || !latestPhysicalBackupDateUnix
|
|
|
|
const onConfirmRestore = async () => {
|
|
if (!ref) return console.error('Project ref is required')
|
|
if (!selectedRecoveryPoint?.recoveryTimeTargetUnix)
|
|
return console.error('Recovery time target unix is required')
|
|
|
|
await restoreFromPitr({
|
|
ref,
|
|
recovery_time_target_unix: selectedRecoveryPoint.recoveryTimeTargetUnix,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<FormHeader
|
|
title="Restore your database from a backup"
|
|
description="Database changes are watched and recorded, so that you can restore your database to any point in time"
|
|
/>
|
|
<BackupsStorageAlert />
|
|
{hasNoBackupsAvailable ? (
|
|
<BackupsEmpty />
|
|
) : (
|
|
<>
|
|
{hasReadReplicas && (
|
|
<Alert variant="warning">
|
|
<WarningIcon />
|
|
<AlertTitle>
|
|
Unable to restore from PITR as project has read replicas enabled
|
|
</AlertTitle>
|
|
<AlertDescription>
|
|
You will need to remove all read replicas first from your project's infrastructure
|
|
settings prior to starting a PITR restore.
|
|
</AlertDescription>
|
|
<div className="flex items-center gap-x-2 mt-2">
|
|
<Button asChild variant="default">
|
|
<Link href={`/project/${ref}/database/replication`}>Manage read replicas</Link>
|
|
</Button>
|
|
</div>
|
|
</Alert>
|
|
)}
|
|
{!showConfiguration ? (
|
|
<PITRStatus
|
|
selectedTimezone={selectedTimezone}
|
|
onUpdateTimezone={setSelectedTimezone}
|
|
onSetConfiguration={() => setShowConfiguration(true)}
|
|
/>
|
|
) : (
|
|
<PITRForm
|
|
earliestAvailableBackupUnix={earliestPhysicalBackupDateUnix}
|
|
latestAvailableBackupUnix={latestPhysicalBackupDateUnix}
|
|
onSubmit={(recoveryPoint) => {
|
|
setSelectedRecoveryPoint(recoveryPoint)
|
|
setShowConfirmation(true)
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
<AlertDialog open={showConfirmation} onOpenChange={(open) => setShowConfirmation(open)}>
|
|
<AlertDialogContent size="medium">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Point in time recovery review</AlertDialogTitle>
|
|
<AlertDialogDescription asChild>
|
|
<div className="flex flex-col space-y-2">
|
|
<p className="text-sm text-foreground-light">Your database will be restored to:</p>
|
|
<div className="py-2 flex flex-col gap-3">
|
|
<div>
|
|
<p className="text-sm font-mono text-foreground-lighter">
|
|
{selectedRecoveryPoint?.selectedTimezone.text}
|
|
</p>
|
|
<p className="text-2xl">{selectedRecoveryPoint?.recoveryTimeString}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-mono text-foreground-lighter">(UTC+00:00)</p>
|
|
<p className="text-2xl">{selectedRecoveryPoint?.recoveryTimeStringUtc}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AlertDialogDescription>
|
|
<DialogSectionSeparator />
|
|
<DialogSection>
|
|
<Alert variant="warning">
|
|
<WarningIcon />
|
|
<AlertTitle>This action cannot be undone, not canceled once started</AlertTitle>
|
|
<AlertDescription>
|
|
Any changes made to your database after this point in time will be lost. This
|
|
includes any changes to your project's storage and authentication.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</DialogSection>
|
|
<DialogSectionSeparator />
|
|
<DialogSection>
|
|
<p className="text-sm text-foreground-light">
|
|
Restores may take from a few minutes up to several hours depending on the size of
|
|
your database. During this period, your project will not be available, until the
|
|
restoration is completed.
|
|
</p>
|
|
</DialogSection>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction variant="warning" onClick={onConfirmRestore}>
|
|
I understand, begin restore
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogHeader>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</>
|
|
)
|
|
}
|