index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { SupportCategories } from '@supabase/shared-types/out/constants'
  2. import { DatabaseUpgradeProgress, DatabaseUpgradeStatus } from '@supabase/shared-types/out/events'
  3. import { useParams } from 'common'
  4. import dayjs from 'dayjs'
  5. import {
  6. AlertCircle,
  7. Check,
  8. CheckCircle,
  9. Circle,
  10. Loader,
  11. Maximize2,
  12. Minimize2,
  13. Settings,
  14. } from 'lucide-react'
  15. import { useSearchParams } from 'next/navigation'
  16. import { useState } from 'react'
  17. import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  18. import { DATABASE_UPGRADE_MESSAGES } from './UpgradingState.constants'
  19. import { SupportLink } from '@/components/interfaces/Support/SupportLink'
  20. import { useProjectUpgradingStatusQuery } from '@/data/config/project-upgrade-status-query'
  21. import { useInvalidateProjectDetailsQuery } from '@/data/projects/project-detail-query'
  22. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  23. import { IS_PLATFORM } from '@/lib/constants'
  24. export const UpgradingState = () => {
  25. const { ref } = useParams()
  26. const queryParams = useSearchParams()
  27. const { data: project } = useSelectedProjectQuery()
  28. const [loading, setLoading] = useState(false)
  29. const [isExpanded, setIsExpanded] = useState(false)
  30. const { invalidateProjectDetailsQuery } = useInvalidateProjectDetailsQuery()
  31. const { data } = useProjectUpgradingStatusQuery(
  32. {
  33. projectRef: ref,
  34. projectStatus: project?.status,
  35. trackingId: queryParams?.get('trackingId'),
  36. },
  37. {
  38. enabled: IS_PLATFORM,
  39. }
  40. )
  41. const { initiated_at, status, progress, target_version, error } =
  42. data?.databaseUpgradeStatus ?? {}
  43. const progressStage = Number((progress || '').split('_')[0])
  44. const isFailed = status === DatabaseUpgradeStatus.Failed
  45. const isCompleted = status === DatabaseUpgradeStatus.Upgraded
  46. const isPerformingFullPhysicalBackup =
  47. status === DatabaseUpgradeStatus.Upgrading &&
  48. progress === DatabaseUpgradeProgress.CompletedUpgrade
  49. const initiatedAtUTC = dayjs.utc(initiated_at ?? 0).format('DD MMM YYYY HH:mm:ss')
  50. const initiatedAt = dayjs
  51. .utc(initiated_at ?? 0)
  52. .local()
  53. .format('DD MMM YYYY HH:mm:ss (ZZ)')
  54. const refetchProjectDetails = async () => {
  55. setLoading(true)
  56. if (ref) await invalidateProjectDetailsQuery(ref)
  57. }
  58. const subject = 'Upgrade failed for project'
  59. const message = `Upgrade information:\n• Initiated at: ${initiated_at}\n• Target Version: ${target_version}\n• Error: ${error}`
  60. return (
  61. <div className="w-full mx-auto my-16 space-y-16 max-w-7xl">
  62. <div className="mx-6 space-y-16">
  63. <div className="flex flex-col space-y-4 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-6">
  64. <h1 className="text-3xl">{project?.name}</h1>
  65. </div>
  66. <div className="w-full mx-auto mt-8 mb-16 max-w-7xl">
  67. <div className="flex h-[500px] items-center justify-center rounded-sm border border-muted bg-surface-100 p-8">
  68. {isCompleted ? (
  69. <div className="grid gap-4">
  70. <div className="relative mx-auto max-w-[300px]">
  71. <CheckCircle className="text-brand" size={40} strokeWidth={1.5} />
  72. </div>
  73. <div className="space-y-2">
  74. <p className="text-center">Upgrade completed!</p>
  75. <p className="mt-4 text-center text-sm text-foreground-light w-[300px] mx-auto">
  76. Your project has been successfully upgraded to Postgres {target_version} and is
  77. now back online.
  78. </p>
  79. </div>
  80. <div className="mx-auto">
  81. <Button loading={loading} disabled={loading} onClick={refetchProjectDetails}>
  82. Return to project
  83. </Button>
  84. </div>
  85. </div>
  86. ) : isFailed ? (
  87. <div className="grid gap-4">
  88. <div className="relative mx-auto max-w-[300px]">
  89. <AlertCircle className="text-amber-900" size={40} strokeWidth={1.5} />
  90. </div>
  91. <div className="space-y-2">
  92. <p className="text-center">We ran into an issue while upgrading your project</p>
  93. <p className="mt-4 text-center text-sm text-foreground-light w-full md:w-[450px] mx-auto">
  94. Your project is back online and its data is not affected. Please reach out to us
  95. via our support form for assistance with the upgrade.
  96. </p>
  97. </div>
  98. <div className="flex items-center mx-auto space-x-2">
  99. <Button asChild type="default">
  100. <SupportLink
  101. queryParams={{
  102. category: SupportCategories.DATABASE_UNRESPONSIVE,
  103. projectRef: ref,
  104. subject,
  105. message,
  106. }}
  107. >
  108. Contact support
  109. </SupportLink>
  110. </Button>
  111. <Button loading={loading} disabled={loading} onClick={refetchProjectDetails}>
  112. Return to project
  113. </Button>
  114. </div>
  115. </div>
  116. ) : (
  117. <div className="grid w-[480px] gap-4">
  118. <div className="relative mx-auto max-w-[300px]">
  119. <div className="absolute flex items-center justify-center w-full h-full">
  120. <Settings className="animate-spin" size={20} strokeWidth={2} />
  121. </div>
  122. <Circle className="text-foreground-lighter" size={50} strokeWidth={1.5} />
  123. </div>
  124. <div className="space-y-2">
  125. {isPerformingFullPhysicalBackup ? (
  126. <div>
  127. <p className="text-center">Performing a full backup</p>
  128. <p className="text-sm text-center text-foreground-light">
  129. Upgrade is now complete, and your project is online. A full backup is now
  130. being performed to ensure that there is a proper base backup available
  131. post-upgrade. This can take from a few minutes up to several hours depending
  132. on the size of your database.
  133. </p>
  134. </div>
  135. ) : (
  136. <div>
  137. <p className="text-center">Upgrading in progress</p>
  138. <p className="text-sm text-center text-foreground-light">
  139. Upgrades can take from a few minutes up to several hours depending on the
  140. size of your database. Your project will be offline while it is being
  141. upgraded.
  142. </p>
  143. </div>
  144. )}
  145. <div
  146. className="mt-4! mb-2! py-3 px-4 transition-all overflow-hidden border rounded-sm relative"
  147. style={{ maxHeight: isExpanded ? '500px' : '110px' }}
  148. >
  149. {isExpanded ? (
  150. <Minimize2
  151. size={14}
  152. strokeWidth={2}
  153. className="absolute z-10 cursor-pointer top-3 right-3"
  154. onClick={() => setIsExpanded(false)}
  155. />
  156. ) : (
  157. <Maximize2
  158. size={14}
  159. strokeWidth={2}
  160. className="absolute z-10 cursor-pointer top-3 right-3"
  161. onClick={() => setIsExpanded(true)}
  162. />
  163. )}
  164. <div
  165. className="space-y-2 transition-all"
  166. style={{
  167. translate: isExpanded
  168. ? '0px 0px'
  169. : `0px ${
  170. (progressStage - 2 <= 0
  171. ? 0
  172. : progressStage > 6
  173. ? 5
  174. : progressStage - 2) * -28
  175. }px`,
  176. }}
  177. >
  178. {DATABASE_UPGRADE_MESSAGES.map((message, idx: number) => {
  179. const isCurrent = message.key === progress
  180. const isCompleted = progressStage > idx
  181. return (
  182. <div key={message.key} className="flex items-center space-x-4">
  183. {isCurrent ? (
  184. <div className="flex items-center justify-center w-5 h-5 rounded-full">
  185. <Loader
  186. size={20}
  187. className="animate-spin text-foreground-light"
  188. strokeWidth={2}
  189. />
  190. </div>
  191. ) : isCompleted ? (
  192. <div className="flex items-center justify-center w-5 h-5 border rounded-full bg-brand border-brand">
  193. <Check size={12} className="text-white" strokeWidth={3} />
  194. </div>
  195. ) : (
  196. <div className="flex items-center justify-center w-5 h-5 border rounded-full bg-overlay-hover" />
  197. )}
  198. <p
  199. className={`text-sm ${
  200. isCurrent
  201. ? 'text-foreground'
  202. : isCompleted
  203. ? 'text-foreground-light'
  204. : 'text-foreground-lighter'
  205. } hover:text-foreground transition`}
  206. >
  207. {isCurrent
  208. ? message.progress
  209. : isCompleted
  210. ? message.completed
  211. : message.initial}
  212. </p>
  213. </div>
  214. )
  215. })}
  216. </div>
  217. </div>
  218. {initiated_at !== undefined && (
  219. <Tooltip>
  220. <TooltipTrigger>
  221. <p className="text-sm text-center text-foreground-light">
  222. Started on: {initiatedAtUTC} (UTC)
  223. </p>
  224. </TooltipTrigger>
  225. <TooltipContent side="bottom">{initiatedAt}</TooltipContent>
  226. </Tooltip>
  227. )}
  228. </div>
  229. </div>
  230. )}
  231. </div>
  232. </div>
  233. </div>
  234. </div>
  235. )
  236. }