feat: add SQS + EventBridge

This commit is contained in:
Zakaria
2026-09-24 22:34:04 -04:00
parent 9bbe80b5f2
commit 1b129e9dcf
37 changed files with 2902 additions and 66 deletions
@@ -0,0 +1,99 @@
import {useEffect, useState} from 'react'
import {Loader2, MessageSquare, Plus, Trash2, Zap} from 'lucide-react'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {createSqsLambdaEventSourceMapping, deleteSqsLambdaEventSourceMapping, listCloudResources, listSqsLambdaEventSourceMappings, type SqsLambdaEventSourceMappingOverview} from '@/api/cloudProxyClient'
import type {CloudProvider} from '@/types/cloud'
import type {CloudResource} from '@/types/resource'
interface SqsLambdaTriggerPanelProps {
cloud: CloudProvider
resource?: CloudResource
runtimeReachable: boolean
}
export function SqsLambdaTriggerPanel({cloud, resource, runtimeReachable}: SqsLambdaTriggerPanelProps) {
const [queueArn, setQueueArn] = useState('')
const [batchSize, setBatchSize] = useState('10')
const [deleteTarget, setDeleteTarget] = useState<SqsLambdaEventSourceMappingOverview | null>(null)
const queryClient = useQueryClient()
const queuesQuery = useQuery({
queryKey: ['cloud-resources', cloud, 'messaging'],
queryFn: ({signal}) => listCloudResources(cloud, 'messaging', undefined, signal),
enabled: cloud === 'aws' && Boolean(resource) && runtimeReachable,
})
const mappingsQuery = useQuery({
queryKey: ['sqs-lambda-event-source-mappings', cloud, resource?.id],
queryFn: ({signal}) => listSqsLambdaEventSourceMappings(cloud, resource!.name, signal),
enabled: cloud === 'aws' && Boolean(resource) && runtimeReachable,
})
useEffect(() => {
setQueueArn('')
setBatchSize('10')
setDeleteTarget(null)
}, [resource?.id])
const createMutation = useMutation({
mutationFn: () => createSqsLambdaEventSourceMapping(cloud, resource!.name, {queueArn, batchSize: Number(batchSize)}),
onSuccess: () => {
setQueueArn('')
queryClient.invalidateQueries({queryKey: ['sqs-lambda-event-source-mappings', cloud, resource?.id]})
},
})
const deleteMutation = useMutation({
mutationFn: (mapping: SqsLambdaEventSourceMappingOverview) => deleteSqsLambdaEventSourceMapping(cloud, resource!.name, mapping.id),
onSuccess: () => {
setDeleteTarget(null)
queryClient.invalidateQueries({queryKey: ['sqs-lambda-event-source-mappings', cloud, resource?.id]})
},
})
if (!resource || resource.service !== 'serverless' || resource.type !== 'lambda') return null
const queues = (queuesQuery.data ?? []).filter((queue) => (queue.type === 'queue' || queue.type === 'fifo-queue') && typeof queue.metadata.arn === 'string')
const parsedBatchSize = Number(batchSize)
const canCreate = Boolean(queueArn) && Number.isInteger(parsedBatchSize) && parsedBatchSize >= 1 && parsedBatchSize <= 10 && !createMutation.isPending
return <section className="table-panel">
<div className="dynamic-stage-header">
<div>
<p className="eyebrow">Event Source</p>
<h3><Zap size={15}/> Add SQS trigger</h3>
<p className="muted compact-text">Poll an SQS queue and invoke <code>{resource.name}</code> for each batch.</p>
</div>
</div>
<div className="resource-create-inline">
<form className="inspector-action-form apigateway-route-form" onSubmit={(event) => {
event.preventDefault()
if (canCreate) createMutation.mutate()
}}>
<label className="dynamic-field">
<span>SQS queue</span>
<select className="input" value={queueArn} onChange={(event) => setQueueArn(event.target.value)} required>
<option value="">Select a queue</option>
{queues.map((queue) => <option key={queue.id} value={String(queue.metadata.arn)}>{queue.name}</option>)}
</select>
<small>{queuesQuery.isLoading ? 'Loading queues...' : queues.length === 0 ? 'Create an SQS queue before adding a trigger.' : 'The queue Lambda polls for messages.'}</small>
</label>
<label className="dynamic-field">
<span>Batch size</span>
<input className="input" type="number" min="1" max="10" value={batchSize} onChange={(event) => setBatchSize(event.target.value)} required/>
<small>Messages sent to Lambda per invocation, from 1 to 10.</small>
</label>
<button className="button primary apigateway-route-submit" type="submit" disabled={!canCreate}>
{createMutation.isPending ? <Loader2 size={13}/> : <Plus size={13}/>} {createMutation.isPending ? 'Adding trigger' : 'Add trigger'}
</button>
</form>
{createMutation.isError && <p className="error-text compact-text">{createMutation.error instanceof Error ? createMutation.error.message : 'Unable to add SQS trigger.'}</p>}
</div>
<div className="apigateway-route-explorer">
<div className="dynamic-stage-header"><div><p className="eyebrow">Configured Triggers</p><h3><MessageSquare size={15}/> SQS event source mappings</h3></div></div>
{mappingsQuery.isLoading && <p className="muted compact-text">Loading SQS triggers...</p>}
{mappingsQuery.isError && <p className="error-text compact-text">{mappingsQuery.error instanceof Error ? mappingsQuery.error.message : 'Unable to load SQS triggers.'}</p>}
{mappingsQuery.data?.length === 0 && <p className="muted compact-text">No SQS triggers configured for this Lambda.</p>}
{mappingsQuery.data?.map((mapping) => <div className="apigateway-route-item" key={mapping.id}>
<div className="apigateway-route-select"><span className="apigateway-route-method">SQS</span><code>{mapping.queueArn}</code><small>Batch size: {mapping.batchSize ?? 'default'}{mapping.state ? ` · ${mapping.state}` : ''}</small></div>
{deleteTarget?.id === mapping.id ? <div className="apigateway-route-confirm"><span>Remove trigger?</span><button className="button danger compact" type="button" disabled={deleteMutation.isPending} onClick={() => deleteMutation.mutate(mapping)}>Remove</button><button className="button compact" type="button" disabled={deleteMutation.isPending} onClick={() => setDeleteTarget(null)}>Cancel</button></div> : <button className="icon-btn" type="button" aria-label={`Remove SQS trigger from ${mapping.queueArn}`} onClick={() => setDeleteTarget(mapping)}><Trash2 size={13}/></button>}
</div>)}
</div>
</section>
}