From e4c50cefce19b383dcf4b37b8d89d3ec7b84f566 Mon Sep 17 00:00:00 2001 From: Lakshan Perera Date: Tue, 1 Jul 2025 19:15:39 +1000 Subject: [PATCH 1/2] feat: Add duplicate key check with confirmation dialog for Edge Function secrets (#36793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created DuplicateSecretWarningModal component following the pattern of DeployEdgeFunctionWarningModal - Added duplicate key detection logic to AddNewSecretForm before secret creation - Shows confirmation dialog when attempting to create a secret with an existing name - Allows users to proceed with replacing the existing secret or cancel the operation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- .../EdgeFunctionSecrets/AddNewSecretForm.tsx | 39 +++++++++++++++++++ .../DuplicateSecretWarningModal.tsx | 36 +++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/DuplicateSecretWarningModal.tsx diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/AddNewSecretForm.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/AddNewSecretForm.tsx index 12d4aaa98413a..ae512e6e27575 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/AddNewSecretForm.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/AddNewSecretForm.tsx @@ -7,7 +7,9 @@ import z from 'zod' import { useParams } from 'common' import Panel from 'components/ui/Panel' import { useSecretsCreateMutation } from 'data/secrets/secrets-create-mutation' +import { useSecretsQuery } from 'data/secrets/secrets-query' import { Eye, EyeOff, MinusCircle } from 'lucide-react' +import { DuplicateSecretWarningModal } from './DuplicateSecretWarningModal' import { Button, Form_Shadcn_, @@ -44,6 +46,8 @@ const defaultValues = { const AddNewSecretForm = () => { const { ref: projectRef } = useParams() const [showSecretValue, setShowSecretValue] = useState(false) + const [duplicateSecretName, setDuplicateSecretName] = useState('') + const [pendingSecrets, setPendingSecrets] = useState | null>(null) const form = useForm({ resolver: zodResolver(FormSchema), @@ -55,6 +59,10 @@ const AddNewSecretForm = () => { name: 'secrets', }) + const { data: existingSecrets } = useSecretsQuery({ + projectRef: projectRef, + }) + function handlePaste(e: ClipboardEvent) { e.preventDefault() const text = e.clipboardData?.getData('text') @@ -114,9 +122,32 @@ const AddNewSecretForm = () => { }) const onSubmit: SubmitHandler> = async (data) => { + // Check for duplicate secret names + const existingSecretNames = existingSecrets?.map((secret) => secret.name) || [] + const duplicateSecret = data.secrets.find((secret) => existingSecretNames.includes(secret.name)) + + if (duplicateSecret) { + setDuplicateSecretName(duplicateSecret.name) + setPendingSecrets(data) + return + } + createSecret({ projectRef, secrets: data.secrets }) } + const handleConfirmDuplicate = () => { + if (pendingSecrets) { + createSecret({ projectRef, secrets: pendingSecrets.secrets }) + setDuplicateSecretName('') + setPendingSecrets(null) + } + } + + const handleCancelDuplicate = () => { + setDuplicateSecretName('') + setPendingSecrets(null) + } + return ( @@ -202,6 +233,14 @@ const AddNewSecretForm = () => { + + ) } diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/DuplicateSecretWarningModal.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/DuplicateSecretWarningModal.tsx new file mode 100644 index 0000000000000..024aa129b3de3 --- /dev/null +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/DuplicateSecretWarningModal.tsx @@ -0,0 +1,36 @@ +import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' + +interface DuplicateSecretWarningModalProps { + visible: boolean + onCancel: () => void + onConfirm: () => void + isCreating: boolean + secretName: string +} + +export const DuplicateSecretWarningModal = ({ + visible, + onCancel, + onConfirm, + isCreating, + secretName, +}: DuplicateSecretWarningModalProps) => { + return ( + +

+ A secret with the name "{secretName}" already exists. Continuing will replace the existing + secret with the new value. This action cannot be undone. Are you sure you want to proceed? +

+
+ ) +} From 9765a70a73099a339286896c9b0f3e622d4aebf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 1 Jul 2025 17:25:11 +0800 Subject: [PATCH 2/2] fix: disk attributes on compute down size (#36788) When downgrading from a larger compute size to =Large instances + // If a customer downgrades back to { + if (modifiedComputeSize && project?.infra_compute_size && isDialogOpen) { + if (RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3.includes(modifiedComputeSize)) { + form.setValue('storageType', DiskType.GP3) + form.setValue('throughput', DISK_LIMITS['gp3'].minThroughput) + form.setValue('provisionedIOPS', DISK_LIMITS['gp3'].minIops) + } + } + }, [modifiedComputeSize, isDialogOpen, project]) + /** * State handling */ diff --git a/apps/studio/components/interfaces/DiskManagement/fields/IOPSField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/IOPSField.tsx index db4e473cb28f1..f7fc5456c3775 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/IOPSField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/IOPSField.tsx @@ -13,11 +13,7 @@ import { } from '../DiskManagement.utils' import { BillingChangeBadge } from '../ui/BillingChangeBadge' import { ComputeSizeRecommendationSection } from '../ui/ComputeSizeRecommendationSection' -import { - COMPUTE_BASELINE_IOPS, - DiskType, - RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3, -} from '../ui/DiskManagement.constants' +import { DiskType, RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3 } from '../ui/DiskManagement.constants' import { DiskManagementIOPSReadReplicas } from '../ui/DiskManagementReadReplicas' import FormMessage from '../ui/FormMessage' import { InputPostTab } from '../ui/InputPostTab' @@ -117,13 +113,7 @@ export function IOPSField({ form, disableInput }: IOPSFieldProps) { type="number" className="flex-grow font-mono rounded-r-none max-w-32" {...field} - value={ - disableIopsInput - ? COMPUTE_BASELINE_IOPS[ - watchedComputeSize as keyof typeof COMPUTE_BASELINE_IOPS - ] - : field.value - } + value={field.value} disabled={disableInput || disableIopsInput || isError} onChange={(e) => { setValue('provisionedIOPS', e.target.valueAsNumber, { diff --git a/apps/studio/components/interfaces/DiskManagement/fields/ThroughputField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/ThroughputField.tsx index 1aa93f0df82dc..f7c925e8f581b 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/ThroughputField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/ThroughputField.tsx @@ -11,7 +11,6 @@ import { DiskStorageSchemaType } from '../DiskManagement.schema' import { calculateThroughputPrice } from '../DiskManagement.utils' import { BillingChangeBadge } from '../ui/BillingChangeBadge' import { - COMPUTE_BASELINE_THROUGHPUT, DISK_LIMITS, DiskType, RESTRICTED_COMPUTE_FOR_IOPS_ON_GP3, @@ -129,13 +128,7 @@ export function ThroughputField({ form, disableInput }: ThroughputFieldProps) { { setValue('throughput', e.target.valueAsNumber, { shouldDirty: true,