mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## Problem We need to rename orioledb in Dashboard. ## Solution - Update Studio copy/badges referencing OrioleDB from "Public Alpha" to "Public Beta" (project creation advanced config, restore-to-new-project, PITR empty state) - Remove the scheduled-backups block that hid backups for OrioleDB projects — OrioleDB now has WAL-G scheduled backups in beta, so that page should behave normally. PITR keeps its existing guard since PITR is not yet supported for OrioleDB. - Update the `useOrioleDb` telemetry property doc-comment to reflect the beta status - Update project-creation wizard test expectations/fixtures accordingly (`release_channel: 'beta'`) Marketing (`apps/www`) and docs (`apps/docs`) references to OrioleDB alpha status are being updated separately. <!-- ## Preview links If relevant, include links to changed pages for easy review access. Copy the preview base URL from the Vercel bot comment on this PR. Use the following table as an example template. | Site | Live | Preview | Search for | | -------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------- | | WWW | [/blog/your-post](https://supabase.com/blog/your-post) | [/blog/your-post](https://zone-www-dot-com-git-branch-name-supabase.vercel.app/blog/your-post) | unique phrase from the change | | Docs | [/docs/guides/your-page](https://supabase.com/docs/guides/your-page) | [/docs/guides/your-page](https://docs-git-branch-name-supabase.vercel.app/docs/guides/your-page) | unique phrase from the change | | Studio | [/dashboard](https://supabase.com/dashboard) | [/dashboard](https://studio-git-branch-name-supabase.vercel.app/dashboard) | unique phrase from the change | | Design system | [/design-system](https://supabase.com/design-system) | [/design-system](https://design-system-git-branch-name-supabase.vercel.app/design-system) | unique phrase from the change | | UI library | [/library](https://supabase.com/library) | [/library](https://ui-library-git-branch-name-supabase.vercel.app/library) | unique phrase from the change | | Knowledge base | [/kb/guides/your-page](https://supabase.com/kb/guides/your-page) | [/kb/guides/your-page](https://kb-git-branch-name-supabase.vercel.app/kb/guides/your-page) | unique phrase from the change | --> <!-- ## Additional context Optionally add any other context or screenshots. --> ## Review instructions ## Checklist Check all before review: - [x] I have read [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) - [x] If I wrote a new docs topic or edited an existing topic, I used the `/write-the-docs` or `/edit-the-docs` skill, which applies the docs [style guide](https://github.com/supabase/supabase/tree/master/apps/docs/style-guide) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Updates** * OrioleDB is now labeled as being in public beta rather than public alpha, and project creation selects the beta release channel. * Restore-to-new-project and Point-in-Time Recovery notices clarify that these features are unavailable for OrioleDB projects. * OrioleDB projects now follow the standard eligibility checks and page flow for scheduled backups. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
328 lines
11 KiB
TypeScript
328 lines
11 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { Loader2 } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useEffect, useState } from 'react'
|
|
import { Alert, AlertDescription, AlertTitle, Button } from 'ui'
|
|
import { Admonition } from 'ui-patterns/Admonition'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { PreviousRestoreItem } from './PreviousRestoreItem'
|
|
import { PITRForm } from '@/components/interfaces/Database/Backups/PITR/PITRForm'
|
|
import { BackupsList } from '@/components/interfaces/Database/Backups/RestoreToNewProject/BackupsList'
|
|
import { ConfirmRestoreDialog } from '@/components/interfaces/Database/Backups/RestoreToNewProject/ConfirmRestoreDialog'
|
|
import { CreateNewProjectDialog } from '@/components/interfaces/Database/Backups/RestoreToNewProject/CreateNewProjectDialog'
|
|
import { projectSpecToMonthlyPrice } from '@/components/interfaces/Database/Backups/RestoreToNewProject/RestoreToNewProject.utils'
|
|
import { DiskType } from '@/components/interfaces/DiskManagement/ui/DiskManagement.constants'
|
|
import { Markdown } from '@/components/interfaces/Markdown'
|
|
import { getServiceVersionsPath } from '@/components/interfaces/Settings/General/ServiceVersions/ServiceVersions.utils'
|
|
import { AlertError } from '@/components/ui/AlertError'
|
|
import { InlineLink } from '@/components/ui/InlineLink'
|
|
import { NoPermission } from '@/components/ui/NoPermission'
|
|
import Panel from '@/components/ui/Panel'
|
|
import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
|
|
import { useDiskAttributesQuery } from '@/data/config/disk-attributes-query'
|
|
import { useCloneBackupsQuery } from '@/data/projects/clone-query'
|
|
import { useCloneStatusQuery } from '@/data/projects/clone-status-query'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import {
|
|
useIsAwsK8sCloudProvider,
|
|
useIsHighAvailability,
|
|
useIsOrioleDb,
|
|
useSelectedProjectQuery,
|
|
} from '@/hooks/misc/useSelectedProject'
|
|
import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants'
|
|
import { getDatabaseMajorVersion } from '@/lib/helpers'
|
|
|
|
export const RestoreToNewProject = () => {
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const { data: organization } = useSelectedOrganizationQuery()
|
|
const { hasAccess: hasAccessToRestoreToNewProject, isLoading: isLoadingEntitlement } =
|
|
useCheckEntitlements('backup.restore_to_new_project')
|
|
const isOrioleDb = useIsOrioleDb()
|
|
const isAwsK8s = useIsAwsK8sCloudProvider()
|
|
const isHighAvailability = useIsHighAvailability()
|
|
|
|
const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
|
|
const [selectedBackupId, setSelectedBackupId] = useState<number | null>(null)
|
|
const [showConfirmationDialog, setShowConfirmationDialog] = useState(false)
|
|
const [showNewProjectDialog, setShowNewProjectDialog] = useState(false)
|
|
const [recoveryTimeTarget, setRecoveryTimeTarget] = useState<number | null>(null)
|
|
|
|
const {
|
|
data: cloneBackups,
|
|
error,
|
|
isPending: cloneBackupsLoading,
|
|
isError,
|
|
} = useCloneBackupsQuery(
|
|
{ projectRef: project?.ref },
|
|
{ enabled: hasAccessToRestoreToNewProject }
|
|
)
|
|
|
|
const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
|
|
|
|
const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
|
|
PermissionAction.READ,
|
|
'physical_backups'
|
|
)
|
|
const { can: canTriggerPhysicalBackups } = useAsyncCheckPermissions(
|
|
PermissionAction.INFRA_EXECUTE,
|
|
'queue_job.restore.prepare'
|
|
)
|
|
const PITR_ENABLED = cloneBackups?.pitr_enabled
|
|
const PHYSICAL_BACKUPS_ENABLED = project?.is_physical_backups_enabled
|
|
const dbVersion = getDatabaseMajorVersion(project?.dbVersion ?? '')
|
|
const IS_PG15_OR_ABOVE = dbVersion >= 15
|
|
const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb
|
|
const targetComputeSize = cloneBackups?.target_compute_size
|
|
const planId = organization?.plan?.id ?? 'free'
|
|
const { data } = useDiskAttributesQuery({ projectRef: project?.ref })
|
|
const storageType = data?.attributes?.type ?? 'gp3'
|
|
|
|
const {
|
|
data: cloneStatus,
|
|
refetch: refetchCloneStatus,
|
|
isPending: cloneStatusLoading,
|
|
isSuccess: isCloneStatusSuccess,
|
|
} = useCloneStatusQuery(
|
|
{
|
|
projectRef: project?.ref,
|
|
},
|
|
{
|
|
refetchInterval,
|
|
refetchOnWindowFocus: false,
|
|
enabled: PHYSICAL_BACKUPS_ENABLED || PITR_ENABLED,
|
|
}
|
|
)
|
|
const isLoading = !isPermissionsLoaded || cloneBackupsLoading || cloneStatusLoading
|
|
|
|
useEffect(() => {
|
|
if (!isCloneStatusSuccess) return
|
|
const hasTransientState = cloneStatus.clones.some((c) => c.status === 'IN_PROGRESS')
|
|
if (!hasTransientState) {
|
|
setRefetchInterval(false)
|
|
}
|
|
}, [cloneStatus?.clones, isCloneStatusSuccess])
|
|
|
|
const previousClones = cloneStatus?.clones
|
|
const isRestoring = previousClones?.some((c) => c.status === 'IN_PROGRESS')
|
|
const restoringClone = previousClones?.find((c) => c.status === 'IN_PROGRESS')
|
|
|
|
if (isLoadingEntitlement) {
|
|
return <GenericSkeletonLoader />
|
|
}
|
|
|
|
if (!hasAccessToRestoreToNewProject) {
|
|
return (
|
|
<UpgradeToPro
|
|
buttonText="Upgrade"
|
|
source="backupsRestoreToNewProject"
|
|
featureProposition="enable restoring to new project"
|
|
primaryText="Restore to a new project requires Pro Plan and above"
|
|
secondaryText="To restore to a new project, you need to upgrade to a Pro Plan and have physical backups enabled."
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isOrioleDb) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="Restoring to new projects are not available for OrioleDB"
|
|
description="OrioleDB is currently in public beta and restoring to a new project is unavailable on OrioleDB projects"
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isHighAvailability) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="Restoring to a new project is unavailable on High Availability projects"
|
|
description="We're working to bring restores to High Availability projects. Contact support if this is blocking your work."
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isAwsK8s) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
description="Restoring to new projects is temporarily not available for AWS (Revamped) projects."
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (!canReadPhysicalBackups) {
|
|
return <NoPermission resourceText="view backups" />
|
|
}
|
|
|
|
if (!canTriggerPhysicalBackups) {
|
|
return <NoPermission resourceText="restore backups" />
|
|
}
|
|
|
|
if (!IS_PG15_OR_ABOVE) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="Restore to new project is not available for this database version"
|
|
>
|
|
<Markdown
|
|
className="max-w-full"
|
|
content={`Restore to new project is only available for Postgres 15 and above.
|
|
Go to [Service versions](${getServiceVersionsPath(project?.ref)})
|
|
to upgrade your database version.
|
|
`}
|
|
/>
|
|
</Admonition>
|
|
)
|
|
}
|
|
|
|
if (!PHYSICAL_BACKUPS_ENABLED) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="Physical backups are required"
|
|
description={
|
|
<>
|
|
Physical backups must be enabled to restore your database to a new project.{' '}
|
|
<InlineLink href={`${DOCS_URL}/guides/platform/backups`}>Learn more</InlineLink>
|
|
</>
|
|
}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isLoading) {
|
|
return <GenericSkeletonLoader />
|
|
}
|
|
|
|
if (isError) {
|
|
return <AlertError error={error} subject="Failed to retrieve backups" />
|
|
}
|
|
|
|
if (!isActiveHealthy) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="Restore to new project is not available while project is offline"
|
|
description="Your project needs to be online to restore your database to a new project"
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (
|
|
!isLoading &&
|
|
PITR_ENABLED &&
|
|
!cloneBackups?.physicalBackupData.earliestPhysicalBackupDateUnix
|
|
) {
|
|
return (
|
|
<Admonition
|
|
type="default"
|
|
title="No backups found"
|
|
description="PITR is enabled, but no backups were found. Check again in a few minutes."
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (!isLoading && !PITR_ENABLED && cloneBackups?.backups.length === 0) {
|
|
return (
|
|
<>
|
|
<Admonition
|
|
type="default"
|
|
title="No backups found"
|
|
description="Backups are enabled, but no backups were found. Check again tomorrow."
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const additionalMonthlySpend = projectSpecToMonthlyPrice({
|
|
targetVolumeSizeGb: targetVolumeSizeGb ?? 0,
|
|
targetComputeSize: targetComputeSize ?? 'nano',
|
|
planId: planId ?? 'free',
|
|
storageType: storageType as DiskType,
|
|
})
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<ConfirmRestoreDialog
|
|
open={showConfirmationDialog}
|
|
onOpenChange={setShowConfirmationDialog}
|
|
onSelectContinue={() => {
|
|
setShowConfirmationDialog(false)
|
|
setShowNewProjectDialog(true)
|
|
}}
|
|
additionalMonthlySpend={additionalMonthlySpend}
|
|
/>
|
|
<CreateNewProjectDialog
|
|
open={showNewProjectDialog}
|
|
selectedBackupId={selectedBackupId}
|
|
recoveryTimeTarget={recoveryTimeTarget}
|
|
additionalMonthlySpend={additionalMonthlySpend}
|
|
hasAccess={hasAccessToRestoreToNewProject}
|
|
onOpenChange={setShowNewProjectDialog}
|
|
onCloneSuccess={() => {
|
|
refetchCloneStatus()
|
|
setRefetchInterval(5000)
|
|
setShowNewProjectDialog(false)
|
|
}}
|
|
/>
|
|
{isRestoring ? (
|
|
<Alert className="[&>svg]:bg-none! [&>svg]:text-foreground-light mb-6">
|
|
<Loader2 className="animate-spin" />
|
|
<AlertTitle>Restoration in progress</AlertTitle>
|
|
<AlertDescription>
|
|
<p>
|
|
The new project {(restoringClone?.target_project as any)?.name || ''} is currently
|
|
being created. You'll be able to restore again once the project is ready.
|
|
</p>
|
|
<Button asChild className="mt-2">
|
|
<Link href={`/project/${restoringClone?.target_project?.ref ?? '_'}`}>
|
|
Go to new project
|
|
</Link>
|
|
</Button>
|
|
</AlertDescription>
|
|
</Alert>
|
|
) : null}
|
|
{previousClones?.length ? (
|
|
<div className="flex flex-col gap-2">
|
|
<h3 className="text-sm font-medium">Previous restorations</h3>
|
|
<Panel className="flex flex-col divide-y divide-border">
|
|
{previousClones?.map((c) => (
|
|
<PreviousRestoreItem key={c.inserted_at} clone={c} />
|
|
))}
|
|
</Panel>
|
|
</div>
|
|
) : null}
|
|
{PITR_ENABLED ? (
|
|
<>
|
|
<PITRForm
|
|
disabled={isRestoring}
|
|
onSubmit={(v) => {
|
|
setShowConfirmationDialog(true)
|
|
setRecoveryTimeTarget(v.recoveryTimeTargetUnix)
|
|
}}
|
|
earliestAvailableBackupUnix={
|
|
cloneBackups?.physicalBackupData.earliestPhysicalBackupDateUnix || 0
|
|
}
|
|
latestAvailableBackupUnix={
|
|
cloneBackups?.physicalBackupData.latestPhysicalBackupDateUnix || 0
|
|
}
|
|
/>
|
|
</>
|
|
) : (
|
|
<BackupsList
|
|
disabled={isRestoring}
|
|
hasAccess={hasAccessToRestoreToNewProject}
|
|
onSelectRestore={(id) => {
|
|
setSelectedBackupId(id)
|
|
setShowConfirmationDialog(true)
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|