diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index d76a1526aa0be..71aab9991c1bf 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -53,7 +53,11 @@ import { IOPSField } from './fields/IOPSField' import { StorageTypeField } from './fields/StorageTypeField' import { ThroughputField } from './fields/ThroughputField' import { DiskCountdownRadial } from './ui/DiskCountdownRadial' -import { DiskType, RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3 } from './ui/DiskManagement.constants' +import { + DISK_LIMITS, + DiskType, + RESTRICTED_COMPUTE_FOR_THROUGHPUT_ON_GP3, +} from './ui/DiskManagement.constants' import { NoticeBar } from './ui/NoticeBar' import { SpendCapDisabledSection } from './ui/SpendCapDisabledSection' import { useIsAwsCloudProvider, useIsAwsK8sCloudProvider } from 'hooks/misc/useSelectedProject' @@ -142,6 +146,10 @@ export function DiskManagementForm() { /** * Handle default values */ + const computeSize = project?.infra_compute_size + ? mapComputeSizeNameToAddonVariantId(project?.infra_compute_size) + : undefined + // @ts-ignore const { type, iops, throughput_mbps, size_gb } = data?.attributes ?? { size_gb: 0, iops: 0 } const { growth_percent, max_size_gb, min_increment_gb } = diskAutoscaleConfig ?? {} @@ -150,9 +158,7 @@ export function DiskManagementForm() { provisionedIOPS: iops, throughput: throughput_mbps, totalSize: size_gb, - computeSize: project?.infra_compute_size - ? mapComputeSizeNameToAddonVariantId(project?.infra_compute_size) - : undefined, + computeSize, growthPercent: growth_percent, minIncrementGb: min_increment_gb, maxSizeGb: max_size_gb, @@ -167,6 +173,20 @@ export function DiskManagementForm() { reValidateMode: 'onChange', }) + const { computeSize: modifiedComputeSize } = form.watch() + + // We only support disk configurations for >=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, 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? +

+
+ ) +}