feat: add SQS + EventBridge
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import {useState} from 'react'
|
||||
import {Braces, Boxes} from 'lucide-react'
|
||||
import {DynamicFormRenderer} from './DynamicFormRenderer'
|
||||
import type {ServiceSchema} from '@/types/schema'
|
||||
|
||||
interface LambdaCreatePanelProps {
|
||||
schema: ServiceSchema
|
||||
isSubmitting: boolean
|
||||
submitError: string | null
|
||||
onCancel: () => void
|
||||
onSubmit: (values: Record<string, unknown>) => void
|
||||
}
|
||||
|
||||
export function LambdaCreatePanel({schema, isSubmitting, submitError, onCancel, onSubmit}: LambdaCreatePanelProps) {
|
||||
const [mode, setMode] = useState<'choose' | 'catalog' | 'scratch' | 's3' | 'api' | 'scheduled' | 'sqs'>('choose')
|
||||
const [bucketName, setBucketName] = useState('')
|
||||
const [apiName, setApiName] = useState('')
|
||||
const [apiPath, setApiPath] = useState('/hello')
|
||||
const [apiMethod, setApiMethod] = useState('GET')
|
||||
const [apiStage, setApiStage] = useState('dev')
|
||||
const [ruleName, setRuleName] = useState('')
|
||||
const [scheduleExpression, setScheduleExpression] = useState('rate(5 minutes)')
|
||||
const [queueName, setQueueName] = useState('')
|
||||
const [batchSize, setBatchSize] = useState('10')
|
||||
const [blueprintError, setBlueprintError] = useState<string | null>(null)
|
||||
if (mode === 'choose') return <div className="resource-create-inline">
|
||||
<p className="eyebrow">Create Lambda</p><h3>How do you want to start?</h3>
|
||||
<div className="apigateway-request-flow">
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('scratch')}><Braces size={18}/><div><strong>Start from scratch</strong><small>Choose runtime, handler, source, and settings.</small></div></button>
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('catalog')}><Boxes size={18}/><div><strong>Blueprints</strong><small>Choose a reviewed multi-resource starter.</small></div></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (mode === 'catalog') return <div className="resource-create-inline">
|
||||
<div className="dynamic-stage-header"><div><p className="eyebrow">Lambda Blueprints</p><h3>Choose a starting point</h3><p className="muted compact-text">Each blueprint displays its source, resources, permissions, and deployment plan before it creates anything.</p></div><button className="button" type="button" onClick={() => setMode('choose')}>Back</button></div>
|
||||
<div className="apigateway-request-flow">
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('s3')}><Boxes size={18}/><div><strong>S3 upload processor</strong><small>Create a bucket and invoke Lambda for new objects.</small></div></button>
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('api')}><Braces size={18}/><div><strong>REST Lambda API</strong><small>Create and deploy an API Gateway Lambda proxy route.</small></div></button>
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('scheduled')}><Boxes size={18}/><div><strong>Scheduled Lambda</strong><small>Invoke Lambda on an EventBridge schedule.</small></div></button>
|
||||
<button className="apigateway-flow-step" type="button" onClick={() => setMode('sqs')}><Boxes size={18}/><div><strong>SQS Worker</strong><small>Create a queue and process messages in Lambda batches.</small></div></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const blueprint = mode === 's3' ? {
|
||||
runtime: 'nodejs22.x', handler: 'index.handler', description: 'Processes S3 object-created events.',
|
||||
code: S3_UPLOAD_PROCESSOR_SOURCE,
|
||||
} : mode === 'api' ? {
|
||||
runtime: 'nodejs22.x', handler: 'index.handler', description: 'Handles API Gateway Lambda proxy requests.',
|
||||
code: REST_LAMBDA_API_SOURCE,
|
||||
} : mode === 'scheduled' ? {
|
||||
runtime: 'nodejs22.x', handler: 'index.handler', description: 'Runs on an EventBridge schedule.',
|
||||
code: SCHEDULED_LAMBDA_SOURCE,
|
||||
} : mode === 'sqs' ? {
|
||||
runtime: 'nodejs22.x', handler: 'index.handler', description: 'Processes messages from an SQS queue.',
|
||||
code: SQS_WORKER_SOURCE,
|
||||
} : {}
|
||||
return <div className="resource-create-inline">
|
||||
<div className="dynamic-stage-header"><div><p className="eyebrow">{mode === 's3' ? 'Blueprint: S3 upload processor' : mode === 'api' ? 'Blueprint: REST Lambda API' : mode === 'scheduled' ? 'Blueprint: Scheduled Lambda' : mode === 'sqs' ? 'Blueprint: SQS Worker' : 'Start from scratch'}</p><p className="muted compact-text">{mode === 's3' ? 'Review the Lambda, bucket, trigger, and permission before creating them.' : mode === 'api' ? 'Review the Lambda, REST API, route, deployment, and permission before creating them.' : mode === 'scheduled' ? 'Review the Lambda and EventBridge schedule before creating them.' : mode === 'sqs' ? 'Review the Lambda, SQS queue, and event source mapping before creating them.' : 'Create a Lambda with your own source and configuration.'}</p></div><button className="button" type="button" disabled={isSubmitting} onClick={() => setMode('choose')}>Back</button></div>
|
||||
{mode === 's3' && <label className="dynamic-field"><span>S3 Bucket Name<em className="field-required">*</em></span><input className="input" value={bucketName} required disabled={isSubmitting} onChange={(event) => { setBucketName(event.target.value); setBlueprintError(null) }} placeholder="my-upload-bucket"/><small>Creates this bucket, then invokes the Lambda for every object-created event.</small></label>}
|
||||
{mode === 's3' && <div className="apigateway-route-explorer"><p className="eyebrow">Creation plan</p><p className="muted compact-text">Create Lambda, create bucket <code>{bucketName || 'your-bucket'}</code>, grant S3 permission to invoke the Lambda, then add an <code>s3:ObjectCreated:*</code> trigger.</p></div>}
|
||||
{mode === 'api' && <div className="dynamic-form">
|
||||
<label className="dynamic-field"><span>API Name<em className="field-required">*</em></span><input className="input" value={apiName} disabled={isSubmitting} onChange={(event) => setApiName(event.target.value)} placeholder="orders-api"/><small>Creates an API Gateway REST API.</small></label>
|
||||
<label className="dynamic-field"><span>Route Path<em className="field-required">*</em></span><input className="input" value={apiPath} disabled={isSubmitting} onChange={(event) => setApiPath(event.target.value)} placeholder="/orders"/><small>One top-level Lambda proxy path.</small></label>
|
||||
<label className="dynamic-field"><span>Method</span><select className="input" value={apiMethod} disabled={isSubmitting} onChange={(event) => setApiMethod(event.target.value)}>{['GET', 'POST', 'PUT', 'DELETE'].map((value) => <option key={value} value={value}>{value}</option>)}</select></label>
|
||||
<label className="dynamic-field"><span>Stage<em className="field-required">*</em></span><input className="input" value={apiStage} disabled={isSubmitting} onChange={(event) => setApiStage(event.target.value)} placeholder="dev"/></label>
|
||||
</div>}
|
||||
{mode === 'api' && <div className="apigateway-route-explorer"><p className="eyebrow">Creation plan</p><p className="muted compact-text">Create Lambda, create REST API <code>{apiName || 'your-api'}</code>, add <code>{apiMethod} {apiPath || '/route'}</code>, grant API Gateway permission, and deploy stage <code>{apiStage || 'dev'}</code>.</p></div>}
|
||||
{mode === 'scheduled' && <div className="dynamic-form">
|
||||
<label className="dynamic-field"><span>Rule Name<em className="field-required">*</em></span><input className="input" value={ruleName} disabled={isSubmitting} onChange={(event) => setRuleName(event.target.value)} placeholder="hourly-report"/><small>Creates this EventBridge rule.</small></label>
|
||||
<label className="dynamic-field"><span>Schedule Expression<em className="field-required">*</em></span><input className="input" value={scheduleExpression} disabled={isSubmitting} onChange={(event) => setScheduleExpression(event.target.value)} placeholder="rate(5 minutes)"/><small>Use a rate or cron expression.</small></label>
|
||||
</div>}
|
||||
{mode === 'scheduled' && <div className="apigateway-route-explorer"><p className="eyebrow">Creation plan</p><p className="muted compact-text">Create Lambda, create EventBridge rule <code>{ruleName || 'your-rule'}</code>, then invoke it on <code>{scheduleExpression || 'your schedule'}</code>.</p></div>}
|
||||
{mode === 'sqs' && <div className="dynamic-form">
|
||||
<label className="dynamic-field"><span>SQS Queue Name<em className="field-required">*</em></span><input className="input" value={queueName} required disabled={isSubmitting} onChange={(event) => { setQueueName(event.target.value); setBlueprintError(null) }} placeholder="orders-queue"/><small>Creates this SQS queue.</small></label>
|
||||
<label className="dynamic-field"><span>Batch Size<em className="field-required">*</em></span><input className="input" type="number" min="1" max="10" value={batchSize} required disabled={isSubmitting} onChange={(event) => { setBatchSize(event.target.value); setBlueprintError(null) }}/><small>Messages sent to Lambda per invocation, from 1 to 10.</small></label>
|
||||
</div>}
|
||||
{mode === 'sqs' && <div className="apigateway-route-explorer"><p className="eyebrow">Creation plan</p><p className="muted compact-text">Create Lambda, create SQS queue <code>{queueName || 'your-queue'}</code>, then create an event source mapping with a batch size of <code>{batchSize || '10'}</code>.</p></div>}
|
||||
{blueprintError && <p className="form-error" role="alert">{blueprintError}</p>}
|
||||
<DynamicFormRenderer schema={schema} initialValues={blueprint} isSubmitting={isSubmitting} submitLabel={mode === 's3' ? 'Create blueprint' : 'Create Lambda'} pendingLabel="Creating Lambda" submitError={submitError} onCancel={onCancel} onSubmit={(values) => {
|
||||
if (mode === 's3' && !bucketName.trim()) {
|
||||
setBlueprintError('S3 bucket name is required.')
|
||||
return
|
||||
}
|
||||
if (mode === 'api' && (!apiName.trim() || !apiPath.trim() || !apiStage.trim())) {
|
||||
setBlueprintError('API name, route path, and stage are required.')
|
||||
return
|
||||
}
|
||||
if (mode === 'scheduled' && (!ruleName.trim() || !scheduleExpression.trim())) {
|
||||
setBlueprintError('Rule name and schedule expression are required.')
|
||||
return
|
||||
}
|
||||
const parsedBatchSize = Number(batchSize)
|
||||
if (mode === 'sqs' && (!queueName.trim() || !Number.isInteger(parsedBatchSize) || parsedBatchSize < 1 || parsedBatchSize > 10)) {
|
||||
setBlueprintError('SQS queue name and a batch size from 1 to 10 are required.')
|
||||
return
|
||||
}
|
||||
onSubmit(mode === 's3' ? {...values, blueprintBucketName: bucketName} : mode === 'api' ? {...values, blueprintApiName: apiName, blueprintApiPath: apiPath, blueprintApiMethod: apiMethod, blueprintApiStage: apiStage} : mode === 'scheduled' ? {...values, blueprintRuleName: ruleName, blueprintScheduleExpression: scheduleExpression} : mode === 'sqs' ? {...values, blueprintQueueName: queueName, blueprintSqsBatchSize: parsedBatchSize} : values)
|
||||
}}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
const S3_UPLOAD_PROCESSOR_SOURCE = `exports.handler = async (event) => {
|
||||
for (const record of event.Records ?? []) {
|
||||
console.log("S3 object created:", {
|
||||
bucket: record.s3.bucket.name,
|
||||
key: record.s3.object.key,
|
||||
event: record.eventName,
|
||||
});
|
||||
}
|
||||
|
||||
return { statusCode: 200, body: JSON.stringify({message: "Processed S3 event"}) };
|
||||
};`
|
||||
|
||||
const REST_LAMBDA_API_SOURCE = `exports.handler = async (event) => {
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
message: "Hello from Lambda",
|
||||
method: event.httpMethod,
|
||||
path: event.path,
|
||||
}),
|
||||
};
|
||||
};`
|
||||
|
||||
const SCHEDULED_LAMBDA_SOURCE = `exports.handler = async (event) => {
|
||||
console.log("Scheduled event received:", {
|
||||
id: event.id,
|
||||
time: event.time,
|
||||
source: event.source,
|
||||
});
|
||||
|
||||
return { statusCode: 200, body: JSON.stringify({message: "Scheduled task completed"}) };
|
||||
};`
|
||||
|
||||
const SQS_WORKER_SOURCE = `exports.handler = async (event) => {
|
||||
for (const record of event.Records ?? []) {
|
||||
console.log("SQS message received:", {
|
||||
messageId: record.messageId,
|
||||
body: record.body,
|
||||
attributes: record.attributes,
|
||||
});
|
||||
}
|
||||
|
||||
return { statusCode: 200, body: JSON.stringify({message: "Processed SQS batch"}) };
|
||||
};`
|
||||
Reference in New Issue
Block a user