diff --git a/app/components/form/fields/DisksTableField.tsx b/app/components/form/fields/DisksTableField.tsx index ae3f341963..53751ebfb7 100644 --- a/app/components/form/fields/DisksTableField.tsx +++ b/app/components/form/fields/DisksTableField.tsx @@ -24,7 +24,13 @@ export type DiskTableItem = * Designed less for reuse, more to encapsulate logic that would otherwise * clutter the instance create form. */ -export function DisksTableField({ control }: { control: Control }) { +export function DisksTableField({ + control, + disabled, +}: { + control: Control + disabled: boolean +}) { const [showDiskCreate, setShowDiskCreate] = useState(false) const [showDiskAttach, setShowDiskAttach] = useState(false) @@ -81,10 +87,15 @@ export function DisksTableField({ control }: { control: Control - - diff --git a/app/components/form/fields/ImageSelectField.tsx b/app/components/form/fields/ImageSelectField.tsx index 8d9a2f8021..ffe5be6363 100644 --- a/app/components/form/fields/ImageSelectField.tsx +++ b/app/components/form/fields/ImageSelectField.tsx @@ -18,12 +18,14 @@ import { ListboxField } from './ListboxField' type ImageSelectFieldProps = { images: Image[] control: Control + disabled?: boolean } -export function ImageSelectField({ images, control }: ImageSelectFieldProps) { +export function ImageSelectField({ images, control, disabled }: ImageSelectFieldProps) { const diskSizeField = useController({ control, name: 'bootDiskSize' }).field return ( + disabled: boolean }) { const [showForm, setShowForm] = useState(false) @@ -58,6 +60,7 @@ export function NetworkInterfaceField({ ? onChange({ type: newType, params: oldParams }) : onChange({ type: newType }) }} + disabled={disabled} > None Default diff --git a/app/components/form/fields/NumberField.tsx b/app/components/form/fields/NumberField.tsx index d508ec8cd8..d09c0417b5 100644 --- a/app/components/form/fields/NumberField.tsx +++ b/app/components/form/fields/NumberField.tsx @@ -68,6 +68,7 @@ export const NumberFieldInner = < description, required, id: idProp, + disabled, }: TextFieldProps) => { const generatedId = useId() const id = idProp || generatedId @@ -87,6 +88,7 @@ export const NumberFieldInner = < [`${id}-help-text`]: !!description, })} aria-describedby={description ? `${id}-label-tip` : undefined} + isDisabled={disabled} {...field} /> diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index 2389e133a8..f5a52d0983 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -5,6 +5,7 @@ * * Copyright Oxide Computer Company */ +import { useEffect, useState } from 'react' import { useWatch } from 'react-hook-form' import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom' import type { SetRequired } from 'type-fest' @@ -97,6 +98,7 @@ CreateInstanceForm.loader = async ({ params }: LoaderFunctionArgs) => { } export function CreateInstanceForm() { + const [isSubmitting, setIsSubmitting] = useState(false) const queryClient = useApiQueryClient() const addToast = useToast() const projectSelector = useProjectSelector() @@ -138,6 +140,12 @@ export function CreateInstanceForm() { const image = allImages.find((i) => i.id === imageInput) const imageSize = image?.size ? Math.ceil(image.size / GiB) : undefined + useEffect(() => { + if (createInstance.error) { + setIsSubmitting(false) + } + }, [createInstance.error]) + return ( } onSubmit={(values) => { + setIsSubmitting(true) // we should never have a presetId that's not in the list const preset = PRESETS.find((option) => option.id === values.presetId)! const instance = @@ -194,9 +203,14 @@ export function CreateInstanceForm() { loading={createInstance.isPending} submitError={createInstance.error} > - - - + + + Start Instance @@ -224,13 +238,21 @@ export function CreateInstanceForm() { }} > - General Purpose - High CPU - High Memory - Custom + + General Purpose + + + High CPU + + + High Memory + + + Custom + - + {renderLargeRadioCards('general')} @@ -264,6 +286,7 @@ export function CreateInstanceForm() { return `CPUs capped to ${INSTANCE_MAX_CPU}` } }} + disabled={isSubmitting} /> @@ -298,8 +322,12 @@ export function CreateInstanceForm() { } > - Silo images - Project images + + Silo images + + + Project images + {allImages.length === 0 && ( ) : ( - + )} @@ -333,7 +365,11 @@ export function CreateInstanceForm() { /> ) : ( - + )} @@ -349,6 +385,7 @@ export function CreateInstanceForm() { return `Must be as large as selected image (min. ${imageSize} GiB)` } }} + disabled={isSubmitting} /> Additional disks - + Authentication @@ -370,12 +408,13 @@ export function CreateInstanceForm() { Networking - + diff --git a/libs/ui/lib/radio/Radio.tsx b/libs/ui/lib/radio/Radio.tsx index f225e44e7c..6e6e626353 100644 --- a/libs/ui/lib/radio/Radio.tsx +++ b/libs/ui/lib/radio/Radio.tsx @@ -43,7 +43,7 @@ const cardLabelStyles = ` peer-focus:ring-2 peer-focus:ring-accent-secondary peer-checked:bg-accent-secondary peer-checked:hover:border-accent peer-checked:border-accent-secondary peer-checked:text-accent peer-checked:[&>*_.text-secondary]:text-accent-secondary - peer-disabled:bg-disabled peer-disabled:text-secondary w-44 + peer-disabled:bg-disabled w-44 children:py-3 children:px-3 children:-mx-4 children:border-secondary first:children:-mt-2 last:children:-mb-2