import {useEffect, useState, type CSSProperties} from 'react' import {ArrowRight, Copy, Link2, ListTree, Loader2, Rocket, ScrollText, Send, Trash2, X} from 'lucide-react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {createApiGatewayLambdaRoute, deleteApiGatewayRoute, enableApiGatewayCors, listApiGatewayRoutes, listCloudResources, testApiGatewayMethod, testApiGatewayRoute} from '@/api/cloudProxyClient' import type {ApiGatewayMethodTestResult, ApiGatewayResourceOverview} from '@/api/cloudProxyClient' import type {CloudProvider} from '@/types/cloud' import type {CloudResource} from '@/types/resource' import {CopyValueButton} from './CopyValueButton' import {LogsQueryPanel} from './LogsQueryPanel' import {ServiceIcon} from '@/components/serviceIcons' interface ApiGatewayRoutePanelProps { cloud: CloudProvider resource?: CloudResource runtimeReachable: boolean runtimeEndpoint?: string } export function ApiGatewayRoutePanel({cloud, resource, runtimeReachable, runtimeEndpoint}: ApiGatewayRoutePanelProps) { const [path, setPath] = useState('/hello') const [parentResourceId, setParentResourceId] = useState('') const [pathPart, setPathPart] = useState('hello') const [method, setMethod] = useState('GET') const [anyTestMethod, setAnyTestMethod] = useState('GET') const [stage, setStage] = useState('dev') const [functionName, setFunctionName] = useState('') const [testBody, setTestBody] = useState('{}') const [testHeaders, setTestHeaders] = useState('{\n "content-type": "application/json"\n}') const [testQuery, setTestQuery] = useState('') const [stageVariables, setStageVariables] = useState('{}') const [selectedResourceId, setSelectedResourceId] = useState(null) const [curlCopied, setCurlCopied] = useState(false) const [bodyFormatError, setBodyFormatError] = useState(null) const [logsRefreshKey, setLogsRefreshKey] = useState(0) const [deleteConfirmation, setDeleteConfirmation] = useState(null) const [corsConfirmation, setCorsConfirmation] = useState<{resourceId: string; path: string} | null>(null) const [corsOrigin, setCorsOrigin] = useState('*') const [corsHeaders, setCorsHeaders] = useState('Content-Type') const [corsMethods, setCorsMethods] = useState([]) const [corsProxyMethods, setCorsProxyMethods] = useState([]) const queryClient = useQueryClient() const functionsQuery = useQuery({ queryKey: ['cloud-resources', cloud, 'serverless'], queryFn: ({signal}) => listCloudResources(cloud, 'serverless', undefined, signal), enabled: cloud === 'aws' && Boolean(resource) && runtimeReachable, }) const routesQuery = useQuery({ queryKey: ['api-gateway-routes', cloud, resource?.id], queryFn: ({signal}) => listApiGatewayRoutes(cloud, resource!.id, signal), enabled: cloud === 'aws' && Boolean(resource) && runtimeReachable, }) useEffect(() => { setPath('/hello') setParentResourceId('') setPathPart('hello') setMethod('GET') setAnyTestMethod('GET') setStage('dev') setFunctionName('') setTestBody('{}') setTestHeaders('{\n "content-type": "application/json"\n}') setTestQuery('') setStageVariables('{}') setSelectedResourceId(null) setCurlCopied(false) setBodyFormatError(null) setLogsRefreshKey(0) setDeleteConfirmation(null) setCorsConfirmation(null) setCorsOrigin('*') setCorsHeaders('Content-Type') setCorsMethods([]) setCorsProxyMethods([]) }, [resource?.id]) const routeMutation = useMutation({ mutationFn: () => createApiGatewayLambdaRoute(cloud, resource!.id, { path: createPath, parentResourceId: selectedParent?.resourceId, pathPart, method, stage, functionName, }), onSuccess: () => queryClient.invalidateQueries({queryKey: ['api-gateway-routes', cloud, resource!.id]}), }) const testMutation = useMutation({ mutationFn: () => testApiGatewayRoute(cloud, resource!.id, { path, method: deployedHttpMethod, stage, body: testBody, headers: parseHeaders(testHeaders), query: testQuery, }), onSuccess: () => setLogsRefreshKey((value) => value + 1), }) const methodTestMutation = useMutation({ mutationFn: () => { const headers = parseHeaders(testHeaders) return testApiGatewayMethod(cloud, resource!.id, { resourceId: selectedResourceId!, httpMethod: method, pathWithQueryString: `${path}${testQuery ? `?${testQuery.replace(/^\?/, '')}` : ''}`, body: testBody, headers, multiValueHeaders: Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, [value]])), stageVariables: parseHeaders(stageVariables), }) }, onSuccess: () => setLogsRefreshKey((value) => value + 1), }) const deleteRouteMutation = useMutation({ mutationFn: ({resourceId, path, method}: {resourceId: string; path: string; method: string}) => deleteApiGatewayRoute(cloud, resource!.id, {resourceId, path, method, stage}), onSuccess: () => { setDeleteConfirmation(null) testMutation.reset() queryClient.invalidateQueries({queryKey: ['api-gateway-routes', cloud, resource!.id]}) }, }) const corsMutation = useMutation({ mutationFn: () => enableApiGatewayCors(cloud, resource!.id, { resourceId: corsConfirmation!.resourceId, stage, origin: corsOrigin, headers: corsHeaders, methods: corsMethods, }), onSuccess: (result) => { setCorsProxyMethods(result.proxyMethods) setCorsConfirmation(null) queryClient.invalidateQueries({queryKey: ['api-gateway-routes', cloud, resource!.id]}) }, }) if (!resource || resource.service !== 'apigateway') { return (

Select a REST API

Select an API Gateway REST API to connect a route to a Lambda function.

) } const functions = functionsQuery.data?.filter((item) => item.type === 'lambda') ?? [] const apiResources = routesQuery.data?.resources ?? [] const rootResource = apiResources.find((item) => item.path === '/') const selectedParent = apiResources.find((item) => item.resourceId === parentResourceId) ?? rootResource const createPath = selectedParent ? childResourcePath(selectedParent.path, pathPart) : `/${pathPart}` const deployedHttpMethod = method === 'ANY' ? anyTestMethod : method const canCreate = cloud === 'aws' && runtimeReachable && Boolean(functionName && selectedParent && pathPart) && !routeMutation.isPending const existingPath = apiResources.some((item) => item.path === createPath) const curlCommand = buildCurlCommand(resource.id, stage, path, deployedHttpMethod, testHeaders, testQuery, testBody) const endpoint = invokeEndpoint(runtimeEndpoint) const baseInvokeUrl = endpoint ? `${endpoint}/execute-api/${resource.id}/${stage}` : null return (

REST API Route

Connect Lambda

Creates and deploys one Lambda proxy route to the selected REST API.

{functionName && Open Lambda logs}
{baseInvokeUrl &&
Base invoke URL
{baseInvokeUrl}
}
{ event.preventDefault() if (canCreate) routeMutation.mutate() }}>
{routeMutation.isError &&

{routeMutation.error instanceof Error ? routeMutation.error.message : 'Unable to create API route.'}

} {routeMutation.data && ( )}

Configured Methods

Route explorer

Select a configured method for Method Test, or to fill the deployed HTTP test workspace.

{routesQuery.isLoading &&

Loading configured methods...

} {routesQuery.isError &&

{routesQuery.error instanceof Error ? routesQuery.error.message : 'Unable to load API routes.'}

} {routesQuery.data && apiResources.length === 0 &&

API Gateway returned no resources.

} {routesQuery.data && apiResources.length > 0 && (
{resourceTree(apiResources).map(({resource: treeResource, children, depth}) => { const resourceMethods = treeResource.methods.filter((item) => CORS_METHODS.includes(item.method)).map((item) => item.method) return
{resourceMethods.length > 0 && }
{treeResource.methods.map((route) => { const selected = route.path === path && route.method === method const key = `${route.resourceId}-${route.method}` return
{deleteConfirmation === key ?
Delete {route.method}?
: }
})} {corsConfirmation?.resourceId === treeResource.resourceId &&
Enable CORS for {corsConfirmation.path}
Allowed methods{resourceMethods.map((value) => )}
Updates this resource's OPTIONS preflight and non-proxy method responses, then deploys {stage}. {corsMutation.isError &&

{corsMutation.error instanceof Error ? corsMutation.error.message : 'Unable to enable CORS.'}

}
} {children.length > 0 && {children.length} child resources}
})}
)} {corsProxyMethods.length > 0 && }

Method Test

Test configured method

Uses API Gateway TestInvokeMethod and does not require a deployed stage. Select a method above first.

{ event.preventDefault() if (selectedResourceId && !methodTestMutation.isPending) methodTestMutation.mutate() }}>