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