diff --git a/src/app/sbx/new/route.ts b/src/app/sbx/new/route.ts index 26710beb1..5b15056b3 100644 --- a/src/app/sbx/new/route.ts +++ b/src/app/sbx/new/route.ts @@ -10,6 +10,9 @@ import { normalizeTerminalTemplate } from '@/features/dashboard/terminal/templat export const GET = async (req: NextRequest) => { try { const requestUrl = new URL(req.url) + const shouldUseTerminalCreateFlow = + requestUrl.searchParams.has('template') || + requestUrl.searchParams.has('command') const template = normalizeTerminalTemplate( requestUrl.searchParams.get('template') ?? undefined ) @@ -39,6 +42,22 @@ export const GET = async (req: NextRequest) => { return NextResponse.redirect(new URL(req.url).origin) } + const terminalParams = new URLSearchParams({ template }) + const command = requestUrl.searchParams.get('command')?.trim() + + if (command) { + terminalParams.set('command', command) + } + + if (shouldUseTerminalCreateFlow) { + return NextResponse.redirect( + new URL( + `${PROTECTED_URLS.TERMINAL(team.slug)}?${terminalParams.toString()}`, + req.url + ) + ) + } + const sbx = await Sandbox.create(template, { apiUrl: process.env.NEXT_PUBLIC_INFRA_API_URL, domain: process.env.NEXT_PUBLIC_E2B_DOMAIN, @@ -47,13 +66,6 @@ export const GET = async (req: NextRequest) => { }, }) - const terminalParams = new URLSearchParams({ template }) - const command = requestUrl.searchParams.get('command')?.trim() - - if (command) { - terminalParams.set('command', command) - } - const terminalUrl = PROTECTED_URLS.SANDBOX_TERMINAL( team.slug, sbx.sandboxId diff --git a/src/core/modules/feature-flags/definitions.ts b/src/core/modules/feature-flags/definitions.ts index c858ceca7..be6461ea6 100644 --- a/src/core/modules/feature-flags/definitions.ts +++ b/src/core/modules/feature-flags/definitions.ts @@ -1,3 +1,4 @@ +import { z } from 'zod' import type { FeatureFlagDefinition } from '@/core/modules/feature-flags/types' export const FEATURE_FLAGS = { @@ -31,6 +32,15 @@ export const FEATURE_FLAGS = { 'Disables provisioning of e2b access tokens via generateE2BUserAccessToken. When enabled, the legacy CLI flow shows an upgrade prompt and the createAccessToken tRPC mutation returns an error.', exposure: 'server', }, + trustedTemplateProviders: { + kind: 'payload', + key: 'trusted_template_providers', + defaultValue: [], + schema: z.array(z.string()), + description: + 'Template providers whose namespaced templates can auto-start dashboard terminals.', + exposure: 'both', + }, } as const satisfies Record export type FeatureFlagId = keyof typeof FEATURE_FLAGS diff --git a/src/core/modules/feature-flags/feature-flags.client.tsx b/src/core/modules/feature-flags/feature-flags.client.tsx index cd5d7a2b5..6e747ad2c 100644 --- a/src/core/modules/feature-flags/feature-flags.client.tsx +++ b/src/core/modules/feature-flags/feature-flags.client.tsx @@ -10,11 +10,13 @@ import { import { type BooleanFeatureFlagId, FEATURE_FLAGS, + type PayloadFeatureFlagId, } from '@/core/modules/feature-flags/definitions' import type { EvaluatedFeatureFlag } from '@/core/modules/feature-flags/types' type FeatureFlagsContextValue = { flags: EvaluatedFeatureFlag[] + getPayload(flagId: PayloadFeatureFlagId): unknown isEnabled(flagId: BooleanFeatureFlagId): boolean } @@ -47,9 +49,17 @@ export function FeatureFlagsProvider({ [flagsById] ) + const getPayload = useCallback( + (flagId: PayloadFeatureFlagId) => { + const evaluatedFlag = flagsById.get(flagId) + return evaluatedFlag?.value ?? FEATURE_FLAGS[flagId].defaultValue + }, + [flagsById] + ) + const value = useMemo( - () => ({ flags: initialFlags, isEnabled }), - [initialFlags, isEnabled] + () => ({ flags: initialFlags, getPayload, isEnabled }), + [initialFlags, getPayload, isEnabled] ) return ( @@ -72,3 +82,7 @@ export function useFeatureFlags() { export function useFeatureFlag(flagId: BooleanFeatureFlagId) { return useFeatureFlags().isEnabled(flagId) } + +export function useFeatureFlagPayload(flagId: PayloadFeatureFlagId) { + return useFeatureFlags().getPayload(flagId) +} diff --git a/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx b/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx index 34c6e902d..c4de9cd44 100644 --- a/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx +++ b/src/features/dashboard/terminal/dashboard-terminal-command-dialog.tsx @@ -17,7 +17,7 @@ import type { PendingTerminalLaunch } from './types' interface DashboardTerminalCommandDialogProps { launch: PendingTerminalLaunch | null onCancel: () => void - onConfirm: (command: string) => void + onConfirm: (command?: string) => void } export default function DashboardTerminalCommandDialog({ @@ -28,6 +28,8 @@ export default function DashboardTerminalCommandDialog({ const commandInputId = useId() const [command, setCommand] = useState('') const normalizedCommand = command.trim() + const hasCommand = launch?.command !== undefined + const untrustedTemplateProvider = launch?.untrustedTemplateProvider useEffect(() => { setCommand(launch?.command ?? '') @@ -40,10 +42,15 @@ export default function DashboardTerminalCommandDialog({
- Review terminal command + + {hasCommand + ? 'Review terminal command' + : 'Review terminal template'} + - This command will run inside a persistent E2B sandbox after the - terminal opens. + {hasCommand + ? 'This command will run inside a persistent E2B sandbox after the terminal opens.' + : 'This terminal will start from a template published by an untrusted provider.'} @@ -63,21 +70,33 @@ export default function DashboardTerminalCommandDialog({ {launch.target?.template ?? 'base'} -
- -