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,4 +1,4 @@
import { useEffect, useLayoutEffect, useMemo, useState } from "react";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { ChevronDown, ChevronUp, Info, Loader2, Plus, RefreshCw, Trash2, X } from "lucide-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -49,6 +49,10 @@ import { ApiGatewayRoutePanel } from "@/components/ApiGatewayRoutePanel";
import { S3LambdaTriggerPanel } from "@/components/S3LambdaTriggerPanel";
import { ScheduledLambdaTriggerPanel } from "@/components/ScheduledLambdaTriggerPanel";
import { SqsLambdaTriggerPanel } from "@/components/SqsLambdaTriggerPanel";
import { SqsQueuePanel } from "@/components/SqsQueuePanel";
import { CloudFormationStackPanel } from "@/components/CloudFormationStackPanel";
import { CloudFormationCreatePanel } from "@/components/CloudFormationCreatePanel";
import { CloudFormationUpdatePanel } from "@/components/CloudFormationUpdatePanel";
import { EventBridgeRulePanel } from "@/components/EventBridgeRulePanel";
import { LambdaInvocationHistoryPanel } from "@/components/LambdaInvocationHistoryPanel";
import { LambdaCreatePanel } from "@/components/LambdaCreatePanel";
@@ -62,7 +66,7 @@ import { WorkflowExecutionDebugger } from "@/components/WorkflowExecutionDebugge
import {WorkflowCreatePanel} from "@/components/WorkflowCreatePanel";
import {workflowCreationDestinationPath, type WorkflowCreationDestination} from "@/lib/workflowCreate";
import {workflowStudioPath} from "@/lib/workflowStudio";
import {useNavigate} from "react-router-dom";
import {useNavigate, useSearchParams} from "react-router-dom";
interface DynamicResourceViewProps {
cloud: CloudProvider;
@@ -90,6 +94,7 @@ export function DynamicResourceView({
onOpenInfo,
}: DynamicResourceViewProps) {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const qc = useQueryClient();
const [search, setSearch] = useState("");
const [state, setState] = useState("");
@@ -105,6 +110,7 @@ export function DynamicResourceView({
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [bulkDeleteError, setBulkDeleteError] = useState<string | null>(null);
const [deleteWarning, setDeleteWarning] = useState<string | null>(null);
const openedResourceRef = useRef<string | undefined>(undefined);
useEffect(() => {
if (cloud === "aws" && service === "compute") {
@@ -139,6 +145,16 @@ export function DynamicResourceView({
cloudStatus?.runtime === "reachable",
});
useEffect(() => {
const resourceId = searchParams.get("resource");
if (!resourceId || !resourcesQuery.data) return;
const key = `${cloud}:${service}:${resourceId}`;
if (openedResourceRef.current === key) return;
const resource = resourcesQuery.data.find((candidate) => candidate.id === resourceId);
if (resource) setSelected(resource);
openedResourceRef.current = key;
}, [cloud, resourcesQuery.data, searchParams, service]);
// Automatically reconcile selected items when resources query data changes
useEffect(() => {
setSelectedItems((prev) => {
@@ -284,6 +300,10 @@ export function DynamicResourceView({
const warning = bucketDeleteWarning(contents);
if (warning) throw new Error(warning);
}
if (cloud === "aws" && service === "iac") {
const current = await getCloudResource(cloud, service, resource.id);
if (current?.metadata.enableTerminationProtection === true) throw new Error("Termination protection is enabled. Disable it from Stack actions before deleting this stack.");
}
await deleteCloudResource(cloud, service, resource.id);
},
onSuccess: (_, resource) => {
@@ -797,13 +817,20 @@ export function DynamicResourceView({
onCancel={() => setCreateOpen(false)}
onSubmit={(values) => createMut.mutate({values})}
/>
) : service === "workflows" && cloud === "aws" ? (
) : service === "workflows" && cloud === "aws" ? (
<WorkflowCreatePanel
isSubmitting={createMut.isPending}
submitError={createMut.error instanceof Error ? createMut.error.message : null}
onCancel={() => setCreateOpen(false)}
onSubmit={(submission) => createMut.mutate(submission)}
/>
) : service === "iac" && cloud === "aws" ? (
<CloudFormationCreatePanel
isSubmitting={createMut.isPending}
submitError={createMut.error instanceof Error ? createMut.error.message : null}
onCancel={() => setCreateOpen(false)}
onSubmit={(values) => createMut.mutate({values})}
/>
) : (
<DynamicFormRenderer
key={`${cloud}:${service}:create`}
@@ -886,6 +913,8 @@ export function DynamicResourceView({
{cloud === "aws" && <S3BucketWebsitePanel resource={selected} runtimeReachable={canUseRuntime} />}
</>
)}
{service === "messaging" && cloud === "aws" && <SqsQueuePanel resource={activeSelected} runtimeReachable={canUseRuntime} />}
{service === "iac" && cloud === "aws" && <CloudFormationStackPanel resource={activeSelected} />}
{service === "ecr" && cloud === "aws" && (
<EcrImageBrowser resource={activeSelected} runtimeReachable={canUseRuntime} />
)}
@@ -1015,7 +1044,7 @@ export function DynamicResourceView({
onCancel={() => setEditingResource(null)}
onSubmit={handleEditSubmit}
/>
) : <DynamicFormRenderer
) : service === "iac" && cloud === "aws" ? <CloudFormationUpdatePanel initialTemplate={String(initialEditValues.templateBody ?? '')} isSubmitting={updateMut.isPending} submitError={updateError} onCancel={() => setEditingResource(null)} onSubmit={handleEditSubmit}/> : <DynamicFormRenderer
key={editingResource.id}
schema={schema}
fields={schema.updateFields}