LogicalBackupCliInstructions.tsx 4.3 KB

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