import { type FormEvent, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Loader2, Plus } from "lucide-react"; import { createCloudResource, listDatabaseOrderableClasses, } from "@/api/cloudProxyClient"; import { listEc2SecurityGroups, type Ec2SecurityGroup, } from "@/api/aws/ec2.api"; import type { CloudProvider } from "@/types/cloud"; import type { CloudResource } from "@/types/resource"; import { DatabaseLaunchProgress } from "@/components/DatabaseLaunchProgress"; const IDENTIFIER_PATTERN = /^(?!.*--)[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/; const DATABASE_CREATION_MINIMUM_MS = 5_000; interface CreateRdsInstanceFormProps { cloud: CloudProvider; onSuccess: (resource: CloudResource) => void; onCancel: () => void; } export function CreateRdsInstanceForm({ cloud, onSuccess, onCancel, }: CreateRdsInstanceFormProps) { const qc = useQueryClient(); // Instance configuration const [dbInstanceIdentifier, setDbInstanceIdentifier] = useState(""); const [engine, setEngine] = useState("postgres"); const [dbInstanceClass, setDbInstanceClass] = useState(""); const [masterUserPassword, setMasterUserPassword] = useState(""); // Networking settings const [selectedSgs, setSelectedSgs] = useState([]); // Additional settings const [masterUsername, setMasterUsername] = useState("root"); const [allocatedStorage, setAllocatedStorage] = useState("20"); const [dbName, setDbName] = useState(""); const [engineVersion, setEngineVersion] = useState(""); const [validationError, setValidationError] = useState(null); // Fetch available instance classes from AWS SDK via backend const classesQuery = useQuery({ queryKey: ["cloud-database-orderable-classes", cloud, engine], queryFn: ({ signal }) => listDatabaseOrderableClasses(cloud, engine, signal), enabled: Boolean(engine), }); const availableClasses = classesQuery.data ?? []; const selectedClass = dbInstanceClass && availableClasses.includes(dbInstanceClass) ? dbInstanceClass : (availableClasses[0] ?? ""); // Fetch available security groups (AWS) const sgsQuery = useQuery({ queryKey: ["ec2", "security-groups"], queryFn: ({ signal }) => listEc2SecurityGroups(undefined, signal), enabled: cloud === "aws", }); function toggleSg(groupId: string, checked: boolean) { setSelectedSgs((prev) => checked ? [...prev, groupId] : prev.filter((id) => id !== groupId), ); } const createMut = useMutation({ mutationFn: async (values: Record) => { const startedAt = Date.now(); const created = await createCloudResource(cloud, "database", values); const remaining = DATABASE_CREATION_MINIMUM_MS - (Date.now() - startedAt); if (remaining > 0) { await new Promise((resolve) => setTimeout(resolve, remaining)); } return created; }, onSuccess: (created) => { void qc.invalidateQueries({ queryKey: ["cloud-resources", cloud, "database"], }); onSuccess(created); }, }); function handleSubmit(e: FormEvent) { e.preventDefault(); setValidationError(null); const trimmedIdentifier = dbInstanceIdentifier.trim(); if (!trimmedIdentifier) { setValidationError("DB Instance Identifier is required."); return; } if (!IDENTIFIER_PATTERN.test(trimmedIdentifier)) { setValidationError( "DB Instance Identifier must start with a lowercase letter, contain only lowercase letters, digits, and hyphens, and cannot end with a hyphen or contain consecutive hyphens.", ); return; } if (!selectedClass) { setValidationError("DB Instance Class is required."); return; } if (masterUserPassword.length < 8) { setValidationError("Master User Password must be at least 8 characters."); return; } createMut.mutate({ dbInstanceIdentifier: trimmedIdentifier, engine, dbInstanceClass: selectedClass, masterUserPassword, masterUsername: masterUsername.trim() || "root", allocatedStorage: allocatedStorage.trim() || "20", dbName: dbName.trim() || undefined, engineVersion: engineVersion.trim() || undefined, securityGroupIds: selectedSgs.length ? selectedSgs : undefined, }); } const errorMessage = validationError ?? (createMut.error instanceof Error ? createMut.error.message : null); return (
{/* ── Required Instance Settings ─────────────────────────────── */}
Instance Configuration
{/* ── Networking — optional ─────────────────────────────────── */} {cloud === "aws" && ( <>
Networking — optional
Security Groups
{sgsQuery.isLoading && Loading…} {!sgsQuery.isLoading && (sgsQuery.data ?? []).length === 0 && ( No security groups found. )} {(sgsQuery.data ?? []).map((sg: Ec2SecurityGroup) => ( ))}
)} {/* ── Additional Database Settings ───────────────────────────── */}
Database Settings — optional
{/* ── Actions ────────────────────────────────────────────────── */} {createMut.isPending &&
}
{errorMessage && (
{errorMessage}
)}
); }