LogicalBackupCliInstructions.tsx130 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { useRouter } from 'next/router'
4import { cn } from 'ui'
5import { CodeBlock } from 'ui-patterns/CodeBlock'
6import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
7
8import {
9 buildDirectPostgresConnectionUri,
10 buildLogicalBackupShellScript,
11 DB_PASSWORD_PLACEHOLDER,
12} from './LogicalBackupCliInstructions.utils'
13import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
14import { InlineLink } from '@/components/ui/InlineLink'
15import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
16import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
17import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
18import { DOCS_URL } from '@/lib/constants'
19
20export type LogicalBackupCliInstructionsProps = {
21 enabled?: boolean
22 className?: string
23 showResetPassword?: boolean
24 note?: string
25}
26
27export const LogicalBackupCliInstructions = ({
28 enabled = true,
29 className,
30 showResetPassword = true,
31 note,
32}: LogicalBackupCliInstructionsProps) => {
33 const router = useRouter()
34 const { ref } = useParams()
35 const { data: project } = useSelectedProjectQuery()
36 const { can: canResetDbPassword } = useAsyncCheckPermissions(
37 PermissionAction.UPDATE,
38 'projects',
39 {
40 resource: {
41 project_id: project?.id,
42 },
43 }
44 )
45
46 const {
47 data: settings,
48 isSuccess,
49 isError,
50 } = useProjectSettingsV2Query({ projectRef: ref }, { enabled: enabled && Boolean(ref) })
51
52 const connectionUri =
53 isSuccess && settings
54 ? buildDirectPostgresConnectionUri({
55 db_user: settings.db_user,
56 db_host: settings.db_host,
57 db_port: settings.db_port,
58 db_name: settings.db_name,
59 })
60 : null
61
62 const shellScript = connectionUri ? buildLogicalBackupShellScript(connectionUri) : ''
63
64 const resetPasswordHref = ref ? `/project/${ref}/database/settings#database-password` : '#'
65 const resetDisabled = !canResetDbPassword
66
67 return (
68 <div className={cn('space-y-3', className)}>
69 <div className="space-y-1">
70 <h4 className="text-sm font-medium">Back up your database with the Briven CLI</h4>
71 <p className="text-sm text-foreground-light">
72 Use your direct connection string — replace {DB_PASSWORD_PLACEHOLDER} with your database
73 password.{' '}
74 <InlineLink href={`${DOCS_URL}/guides/platform/backups`}>Backup documentation</InlineLink>
75 .
76 </p>
77 <p className="text-sm text-foreground-light">
78 Any reserved character in your password must be percent-encoded in the URL (e.g.{' '}
79 <code>@</code>&nbsp;→&nbsp;<code>%40</code>, <code>:</code>&nbsp;→&nbsp;<code>%3A</code>,{' '}
80 <code>/</code>&nbsp;→&nbsp;<code>%2F</code>, <code>#</code>&nbsp;→&nbsp;<code>%23</code>).
81 Encode <code>%</code> as <code>%25</code> first.
82 </p>
83 </div>
84
85 {showResetPassword && (
86 <ButtonTooltip
87 type="default"
88 disabled={resetDisabled}
89 onClick={() => {
90 if (!resetDisabled && ref) {
91 void router.push(`/project/${ref}/database/settings#database-password`)
92 }
93 }}
94 tooltip={{
95 content: {
96 side: 'bottom',
97 text: !canResetDbPassword
98 ? 'You need additional permissions to reset the database password'
99 : undefined,
100 },
101 }}
102 >
103 Reset database password
104 </ButtonTooltip>
105 )}
106
107 {note && <p className="text-sm text-foreground-light">{note}</p>}
108
109 {isError && (
110 <p className="text-sm text-foreground-light">
111 Could not load connection details. Open{' '}
112 <InlineLink href={resetPasswordHref}>Database settings</InlineLink> to copy your
113 connection string manually.
114 </p>
115 )}
116
117 {!isError && !connectionUri && <ShimmeringLoader className="py-4" />}
118
119 {connectionUri ? (
120 <CodeBlock
121 language="bash"
122 value={shellScript}
123 hideLineNumbers
124 className="[&_code]:text-[12px] [&_code]:text-foreground"
125 wrapperClassName="[&_pre]:px-4 [&_pre]:py-3"
126 />
127 ) : null}
128 </div>
129 )
130}