feat: EC2 UI polished + storage persisting

This commit is contained in:
Zakaria
2026-09-25 18:18:33 -04:00
parent 1b129e9dcf
commit 093afb9b2b
40 changed files with 2586 additions and 102 deletions
+97
View File
@@ -620,6 +620,42 @@ export function createCloudRoutes(injectedService?: CloudProxyService) {
})
})
app.get('/:cloud/services/events/resources/:id/rules', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listEventBridgeRules(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/events/resources/:id/rules', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const rule = await svc(c).createEventBridgeRule(cloud, c.req.param('id'), {
name: typeof input.name === 'string' ? input.name : '',
scheduleExpression: typeof input.scheduleExpression === 'string' ? input.scheduleExpression : undefined,
eventPattern: typeof input.eventPattern === 'string' ? input.eventPattern : undefined,
state: input.state === 'DISABLED' ? 'DISABLED' : input.state === 'ENABLED' ? 'ENABLED' : undefined,
description: typeof input.description === 'string' ? input.description : undefined,
targets: Array.isArray(input.targets) ? input.targets.flatMap((target) => {
if (!target || typeof target !== 'object' || Array.isArray(target)) return []
const value = target as {id?: unknown; arn?: unknown}
return typeof value.id === 'string' && typeof value.arn === 'string' ? [{id: value.id, arn: value.arn}] : []
}) : [],
})
return c.json(rule, 201)
})
})
app.delete('/:cloud/services/events/resources/:id/rules/:ruleName', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteEventBridgeRule(cloud, c.req.param('id'), c.req.param('ruleName'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/serverless/resources/:id/sqs-event-source-mappings', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
@@ -867,6 +903,60 @@ export function createCloudRoutes(injectedService?: CloudProxyService) {
})
})
app.post('/:cloud/services/notifications/resources/:id/publish', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{message?: unknown; subject?: unknown; messageAttributes?: unknown}>(c)
if (typeof body.message !== 'string' || !body.message) {
throw new ValidationError('message is required')
}
if (body.subject !== undefined && typeof body.subject !== 'string') {
throw new ValidationError('subject must be a string')
}
if (body.messageAttributes !== undefined && !isStringRecord(body.messageAttributes)) {
throw new ValidationError('messageAttributes must be an object of string values')
}
const result = await svc(c).publishNotification(cloud, c.req.param('id'), {
message: body.message,
...(body.subject ? {subject: body.subject} : {}),
...(body.messageAttributes ? {messageAttributes: body.messageAttributes} : {}),
})
return c.json(result)
})
})
app.get('/:cloud/services/notifications/resources/:id/subscriptions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listSnsSubscriptions(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/notifications/resources/:id/subscriptions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{protocol?: unknown; endpoint?: unknown}>(c)
if (typeof body.protocol !== 'string' || !body.protocol.trim()) throw new ValidationError('protocol is required')
if (typeof body.endpoint !== 'string' || !body.endpoint.trim()) throw new ValidationError('endpoint is required')
const subscription = await svc(c).createSnsSubscription(cloud, c.req.param('id'), {
protocol: body.protocol,
endpoint: body.endpoint,
})
return c.json(subscription, 201)
})
})
app.delete('/:cloud/services/notifications/resources/:id/subscriptions/:subscriptionArn', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteSnsSubscription(cloud, c.req.param('subscriptionArn'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/kms/resources/:id/encrypt', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
@@ -984,6 +1074,13 @@ function encryptionContext(value: unknown): Record<string, string> | undefined {
return entries.length === 0 ? undefined : Object.fromEntries(entries) as Record<string, string>
}
function isStringRecord(value: unknown): value is Record<string, string> {
return typeof value === 'object'
&& value !== null
&& !Array.isArray(value)
&& Object.values(value).every((item) => typeof item === 'string')
}
function stringHeaders(value: unknown): Record<string, string> {
if (value === undefined) return {}
if (!value || typeof value !== 'object' || Array.isArray(value)) {