RestartServerButton.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { useFlag } from 'common'
  3. import { ChevronDown, RefreshCw } from 'lucide-react'
  4. import { useRouter } from 'next/router'
  5. import { useState } from 'react'
  6. import { toast } from 'sonner'
  7. import {
  8. Button,
  9. cn,
  10. DropdownMenu,
  11. DropdownMenuContent,
  12. DropdownMenuItem,
  13. DropdownMenuTrigger,
  14. } from 'ui'
  15. import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
  16. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  17. import { useSetProjectStatus } from '@/data/projects/project-detail-query'
  18. import { useProjectRestartMutation } from '@/data/projects/project-restart-mutation'
  19. import { useProjectRestartServicesMutation } from '@/data/projects/project-restart-services-mutation'
  20. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  21. import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
  22. import {
  23. useIsAwsK8sCloudProvider,
  24. useIsProjectActive,
  25. useSelectedProjectQuery,
  26. } from '@/hooks/misc/useSelectedProject'
  27. import { PROJECT_STATUS } from '@/lib/constants'
  28. import { type ResponseError } from '@/types'
  29. const RestartServerButton = () => {
  30. const router = useRouter()
  31. const { data: project } = useSelectedProjectQuery()
  32. const isProjectActive = useIsProjectActive()
  33. const canRestart = isProjectActive || project?.status === PROJECT_STATUS.ACTIVE_UNHEALTHY
  34. const isAwsK8s = useIsAwsK8sCloudProvider()
  35. const { setProjectStatus } = useSetProjectStatus()
  36. const [serviceToRestart, setServiceToRestart] = useState<'project' | 'database'>()
  37. const { projectSettingsRestartProject } = useIsFeatureEnabled([
  38. 'project_settings:restart_project',
  39. ])
  40. const projectRef = project?.ref ?? ''
  41. const projectRegion = project?.region ?? ''
  42. const projectRestartDisabled = useFlag('disableProjectRestarts')
  43. const { can: canRestartProject } = useAsyncCheckPermissions(
  44. PermissionAction.INFRA_EXECUTE,
  45. 'reboot'
  46. )
  47. const { mutate: restartProject, isPending: isRestartingProject } = useProjectRestartMutation({
  48. onSuccess: () => {
  49. onRestartSuccess()
  50. },
  51. onError: (error) => {
  52. onRestartFailed(error, 'project')
  53. },
  54. })
  55. const { mutate: restartProjectServices, isPending: isRestartingServices } =
  56. useProjectRestartServicesMutation({
  57. onSuccess: () => {
  58. onRestartSuccess()
  59. },
  60. onError: (error) => {
  61. onRestartFailed(error, 'database')
  62. },
  63. })
  64. const isLoading = isRestartingProject || isRestartingServices
  65. const hasRestartDropdown = canRestartProject && canRestart && !projectRestartDisabled
  66. const requestProjectRestart = () => {
  67. if (!canRestartProject) {
  68. return toast.error('You do not have the required permissions to restart this project')
  69. }
  70. restartProject({ ref: projectRef })
  71. }
  72. const requestDatabaseRestart = async () => {
  73. if (!canRestartProject) {
  74. return toast.error('You do not have the required permissions to restart this project')
  75. }
  76. restartProjectServices({ ref: projectRef, region: projectRegion, services: ['postgresql'] })
  77. }
  78. const onRestartFailed = (error: ResponseError, type: string) => {
  79. toast.error(`Unable to restart ${type}: ${error.message}`)
  80. setServiceToRestart(undefined)
  81. }
  82. const onRestartSuccess = () => {
  83. setProjectStatus({ ref: projectRef, status: PROJECT_STATUS.RESTARTING })
  84. toast.success('Restarting server...')
  85. router.push(`/project/${projectRef}`)
  86. setServiceToRestart(undefined)
  87. }
  88. return (
  89. <>
  90. {projectSettingsRestartProject ? (
  91. <div className="flex w-full @lg:w-auto">
  92. <ButtonTooltip
  93. type="default"
  94. className={cn(
  95. 'flex-1 px-3 hover:z-10 @lg:flex-none',
  96. canRestartProject && canRestart ? 'rounded-r-none' : ''
  97. )}
  98. disabled={
  99. project === undefined ||
  100. !canRestartProject ||
  101. !canRestart ||
  102. projectRestartDisabled ||
  103. isAwsK8s
  104. }
  105. onClick={() => setServiceToRestart('project')}
  106. tooltip={{
  107. content: {
  108. side: 'bottom',
  109. text: projectRestartDisabled
  110. ? 'Project restart is currently disabled'
  111. : !canRestartProject
  112. ? 'You need additional permissions to restart this project'
  113. : !canRestart
  114. ? 'Unable to restart project as project is not active'
  115. : isAwsK8s
  116. ? 'Project restart is not supported for AWS (Revamped) projects'
  117. : undefined,
  118. },
  119. }}
  120. >
  121. Restart project
  122. </ButtonTooltip>
  123. {hasRestartDropdown && (
  124. <DropdownMenu>
  125. <DropdownMenuTrigger asChild>
  126. <Button
  127. type="default"
  128. className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
  129. icon={<ChevronDown />}
  130. disabled={!canRestartProject}
  131. />
  132. </DropdownMenuTrigger>
  133. <DropdownMenuContent align="end" side="bottom">
  134. <DropdownMenuItem
  135. key="database"
  136. disabled={isLoading}
  137. onClick={() => {
  138. setServiceToRestart('database')
  139. }}
  140. >
  141. <div className="space-y-0.5">
  142. <p className="block text-foreground">Fast database reboot</p>
  143. <p className="block text-foreground-light">
  144. Restarts only the database. Faster, but may not be able to recover from all
  145. failure modes.
  146. </p>
  147. </div>
  148. </DropdownMenuItem>
  149. </DropdownMenuContent>
  150. </DropdownMenu>
  151. )}
  152. </div>
  153. ) : (
  154. <Button
  155. type="default"
  156. icon={<RefreshCw />}
  157. className="w-full @lg:w-auto"
  158. disabled={isLoading}
  159. onClick={() => {
  160. setServiceToRestart('database')
  161. }}
  162. >
  163. Restart database
  164. </Button>
  165. )}
  166. <ConfirmationModal
  167. visible={serviceToRestart !== undefined}
  168. variant="destructive"
  169. title={`Restart ${serviceToRestart}`}
  170. description={
  171. <>
  172. Are you sure you want to restart your {serviceToRestart}? There will be a few minutes of
  173. downtime.
  174. </>
  175. }
  176. confirmLabel="Restart"
  177. confirmLabelLoading="Restarting"
  178. loading={isLoading}
  179. onCancel={() => setServiceToRestart(undefined)}
  180. onConfirm={async () => {
  181. if (serviceToRestart === 'project') {
  182. await requestProjectRestart()
  183. } else if (serviceToRestart === 'database') {
  184. await requestDatabaseRestart()
  185. }
  186. }}
  187. />
  188. </>
  189. )
  190. }
  191. export default RestartServerButton