feat: Cloudformation + RDS

This commit is contained in:
Zakaria
2026-09-30 17:43:42 -04:00
parent 548a15d7ec
commit 7764ea0dc1
411 changed files with 10182 additions and 1063 deletions
@@ -1,9 +1,10 @@
import {useEffect, useState} from 'react'
import {CalendarClock, Loader2, Plus, Radio, Trash2} from 'lucide-react'
import {CalendarClock, Loader2, Plus, Send, Trash2} from 'lucide-react'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {createEventBridgeRule, deleteEventBridgeRule, listCloudResources, listEventBridgeRules, type EventBridgeRuleOverview} from '@/api/cloudProxyClient'
import {createEventBridgeRule, deleteEventBridgeRule, listCloudResources, listEventBridgeRules, putEventBridgeEvent, type EventBridgeRuleOverview} from '@/api/cloudProxyClient'
import type {CloudProvider, CloudServiceType} from '@/types/cloud'
import type {CloudResource} from '@/types/resource'
import {ServiceIcon} from '@/components/serviceIcons'
interface EventBridgeRulePanelProps {
cloud: CloudProvider
@@ -30,6 +31,9 @@ export function EventBridgeRulePanel({cloud, resource, runtimeReachable}: EventB
const [targetService, setTargetService] = useState<TargetService>('lambda')
const [targetArn, setTargetArn] = useState('')
const [deleteTarget, setDeleteTarget] = useState<EventBridgeRuleOverview | null>(null)
const [eventSource, setEventSource] = useState('orders')
const [eventDetailType, setEventDetailType] = useState('order.created')
const [eventDetail, setEventDetail] = useState('{\n "orderId": "example-123"\n}')
const queryClient = useQueryClient()
const targetQueries = {
serverless: useQuery({
@@ -69,6 +73,9 @@ export function EventBridgeRulePanel({cloud, resource, runtimeReachable}: EventB
setTargetService('lambda')
setTargetArn('')
setDeleteTarget(null)
setEventSource('orders')
setEventDetailType('order.created')
setEventDetail('{\n "orderId": "example-123"\n}')
}, [resource?.id])
const resourcesByTargetService = (Object.keys(targetServiceDetails) as TargetService[]).reduce<Record<TargetService, CloudResource[]>>((resources, service) => {
@@ -118,6 +125,11 @@ export function EventBridgeRulePanel({cloud, resource, runtimeReachable}: EventB
void queryClient.invalidateQueries({queryKey: ['eventbridge-rules', cloud, resource?.id]})
},
})
const publishMutation = useMutation({
mutationFn: () => putEventBridgeEvent(cloud, resource!.id, {source: eventSource.trim(), detailType: eventDetailType.trim(), detail: eventDetail.trim()}),
})
const eventDetailValid = isJsonObject(eventDetail)
const canPublish = Boolean(eventSource.trim() && eventDetailType.trim() && eventDetail.trim() && eventDetailValid) && !publishMutation.isPending
if (!resource || resource.service !== 'events' || resource.type !== 'event-bus') return null
@@ -125,7 +137,7 @@ export function EventBridgeRulePanel({cloud, resource, runtimeReachable}: EventB
<div className="dynamic-stage-header">
<div>
<p className="eyebrow">EventBridge Rules</p>
<h3><Radio size={15}/> Add rule to {resource.name}</h3>
<h3><ServiceIcon cloud="aws" iconKey="events" size={18}/> Add rule to {resource.name}</h3>
<p className="muted compact-text">Route a schedule or matching events from this event bus to one existing target.</p>
</div>
</div>
@@ -146,6 +158,17 @@ export function EventBridgeRulePanel({cloud, resource, runtimeReachable}: EventB
</form>
{createMutation.isError && <p className="error-text compact-text">{createMutation.error instanceof Error ? createMutation.error.message : 'Unable to create EventBridge rule.'}</p>}
</div>
<div className="resource-create-inline">
<div className="dynamic-stage-header"><div><p className="eyebrow">Test event delivery</p><h3><Send size={15}/> Publish to {resource.name}</h3><p className="muted compact-text">Sends one real EventBridge PutEvents entry to this bus.</p></div></div>
<form className="inspector-action-form apigateway-route-form" onSubmit={(event) => { event.preventDefault(); if (canPublish) publishMutation.mutate() }}>
<label className="dynamic-field"><span>Source</span><input className="input" value={eventSource} onChange={(event) => setEventSource(event.target.value)} required/></label>
<label className="dynamic-field"><span>Detail type</span><input className="input" value={eventDetailType} onChange={(event) => setEventDetailType(event.target.value)} required/></label>
<label className="dynamic-field dynamic-field--span"><span>Detail (JSON)</span><textarea className="textarea code-textarea small" value={eventDetail} onChange={(event) => setEventDetail(event.target.value)} spellCheck={false} required/>{!eventDetailValid && <small className="error-text">Detail must be a JSON object.</small>}</label>
<button className="button primary apigateway-route-submit" type="submit" disabled={!canPublish}>{publishMutation.isPending ? <Loader2 size={13}/> : <Send size={13}/>}{publishMutation.isPending ? 'Publishing event' : 'Publish event'}</button>
</form>
{publishMutation.isSuccess && <p className={publishMutation.data.errorCode ? 'error-text compact-text' : 'success-text compact-text'}>{publishMutation.data.errorCode ? `${publishMutation.data.errorCode}: ${publishMutation.data.errorMessage}` : `Published event ${publishMutation.data.eventId}`}</p>}
{publishMutation.isError && <p className="error-text compact-text">{publishMutation.error instanceof Error ? publishMutation.error.message : 'Unable to publish EventBridge event.'}</p>}
</div>
<div className="apigateway-route-explorer">
<div className="dynamic-stage-header"><div><p className="eyebrow">Configured Rules</p><h3><CalendarClock size={15}/> Rules and targets</h3></div></div>
{rulesQuery.isLoading && <p className="muted compact-text">Loading EventBridge rules...</p>}