Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/features/dashboard/sandbox/header/controls.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import KillButton from './kill-button'
import PauseButton from './pause-button'

export default function SandboxDetailsControls() {
return (
<div className="flex items-center gap-2 md:pb-2">
<PauseButton />
<KillButton />
</div>
)
Expand Down
5 changes: 2 additions & 3 deletions src/features/dashboard/sandbox/header/kill-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from 'react'
import { toast } from 'sonner'
import { cn } from '@/lib/utils/ui'
import { killSandboxAction } from '@/server/sandboxes/sandbox-actions'
import { AlertPopover } from '@/ui/alert-popover'
import { AlertDialog } from '@/ui/alert-dialog'
import { Button } from '@/ui/primitives/button'
import { TrashIcon } from '@/ui/primitives/icons'
import { useDashboard } from '../../context'
Expand Down Expand Up @@ -46,7 +46,7 @@ export default function KillButton({ className }: KillButtonProps) {
}

return (
<AlertPopover
<AlertDialog
open={open}
onOpenChange={setOpen}
title="Kill Sandbox"
Expand All @@ -68,7 +68,6 @@ export default function KillButton({ className }: KillButtonProps) {
loading: isExecuting,
}}
onConfirm={handleKill}
onCancel={() => setOpen(false)}
/>
)
}
61 changes: 61 additions & 0 deletions src/features/dashboard/sandbox/header/pause-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client'

import { Pause } from 'lucide-react'
import { useAction } from 'next-safe-action/hooks'
import { useState } from 'react'
import { toast } from 'sonner'
import { pauseSandboxAction } from '@/server/sandboxes/sandbox-actions'
import { AlertDialog } from '@/ui/alert-dialog'
import { Button } from '@/ui/primitives/button'
import { useDashboard } from '../../context'
import { useSandboxContext } from '../context'

export default function PauseButton() {
const [open, setOpen] = useState(false)
const { sandboxInfo, refetchSandboxInfo } = useSandboxContext()
const { team } = useDashboard()
const canPause = sandboxInfo?.state === 'running'

const { execute, isExecuting } = useAction(pauseSandboxAction, {
onSuccess: async () => {
toast.success('Sandbox paused successfully')
setOpen(false)
refetchSandboxInfo()
},
onError: ({ error }) => {
toast.error(
error.serverError || 'Failed to pause sandbox. Please try again.'
)
},
})

const handlePause = () => {
if (!canPause || !sandboxInfo?.sandboxID) return

execute({
teamIdOrSlug: team.id,
sandboxId: sandboxInfo.sandboxID,
})
}

return (
<AlertDialog
open={open}
onOpenChange={setOpen}
title="Pause Sandbox"
description="Are you sure you want to pause this sandbox? The sandbox will be suspended and can be resumed later."
confirm="Pause Sandbox"
trigger={
<Button variant="ghost" size="slate" disabled={!canPause}>
<Pause className="size-3.5" />
Pause
</Button>
}
confirmProps={{
disabled: isExecuting,
loading: isExecuting,
}}
onConfirm={handlePause}
/>
)
}
55 changes: 55 additions & 0 deletions src/server/sandboxes/sandbox-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,61 @@ export const killSandboxAction = authActionClient
}
})

const PauseSandboxSchema = z.object({
teamIdOrSlug: TeamIdOrSlugSchema,
sandboxId: z.string().min(1, 'Sandbox ID is required'),
})

export const pauseSandboxAction = authActionClient
.schema(PauseSandboxSchema)
.metadata({ actionName: 'pauseSandbox' })
.use(withTeamIdResolution)
.action(async ({ parsedInput, ctx }) => {
const { sandboxId } = parsedInput
const { session, teamId } = ctx

const res = await infra.POST('/sandboxes/{sandboxID}/pause', {
headers: {
...SUPABASE_AUTH_HEADERS(session.access_token, teamId),
},
params: {
path: {
sandboxID: sandboxId,
},
},
})

if (res.error) {
const status = res.response.status

l.error(
{
key: 'pause_sandbox_action:infra_error',
error: res.error,
user_id: session.user.id,
team_id: teamId,
sandbox_id: sandboxId,
context: {
status,
},
},
`Failed to pause sandbox: ${res.error.message}`
)

if (status === 404) {
return returnServerError('Sandbox not found')
}

if (status === 409) {
return returnServerError(
'Sandbox cannot be paused in its current state'
)
}

return returnServerError('Failed to pause sandbox')
}
})

const RevalidateSandboxesSchema = z.object({
teamIdOrSlug: TeamIdOrSlugSchema,
})
Expand Down
Loading