diff --git a/app/components/StopInstancePrompt.tsx b/app/components/StopInstancePrompt.tsx new file mode 100644 index 000000000..162bb1d9f --- /dev/null +++ b/app/components/StopInstancePrompt.tsx @@ -0,0 +1,64 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright Oxide Computer Company + */ +import { type ReactNode } from 'react' + +import { apiQueryClient, useApiMutation, type Instance } from '@oxide/api' + +import { HL } from '~/components/HL' +import { addToast } from '~/stores/toast' +import { Button } from '~/ui/lib/Button' +import { Message } from '~/ui/lib/Message' + +type StopInstancePromptProps = { + instance: Instance + children: ReactNode +} + +export function StopInstancePrompt({ instance, children }: StopInstancePromptProps) { + const isStoppingInstance = instance.runState === 'stopping' + + const stopInstance = useApiMutation('instanceStop', { + onSuccess: () => { + // trigger polling by the top level InstancePage one + apiQueryClient.invalidateQueries('instanceView') + addToast(<>Stopping instance {instance.name}) // prettier-ignore + }, + onError: (error) => { + addToast({ + variant: 'error', + title: `Error stopping instance '${instance.name}'`, + content: error.message, + }) + }, + }) + + return ( + + {children}{' '} + + + } + /> + ) +} diff --git a/app/forms/disk-attach.tsx b/app/forms/disk-attach.tsx index f3f07d841..d9cf9808b 100644 --- a/app/forms/disk-attach.tsx +++ b/app/forms/disk-attach.tsx @@ -8,10 +8,11 @@ import { useMemo } from 'react' import { useForm } from 'react-hook-form' -import { useApiQuery, type ApiError } from '@oxide/api' +import { instanceCan, useApiQuery, type ApiError, type Instance } from '@oxide/api' import { ComboboxField } from '~/components/form/fields/ComboboxField' import { ModalForm } from '~/components/form/ModalForm' +import { StopInstancePrompt } from '~/components/StopInstancePrompt' import { useProjectSelector } from '~/hooks/use-params' import { toComboboxItems } from '~/ui/lib/Combobox' import { ALL_ISH } from '~/util/consts' @@ -25,6 +26,7 @@ type AttachDiskProps = { diskNamesToExclude?: string[] loading?: boolean submitError?: ApiError | null + instance?: Instance } /** @@ -37,6 +39,7 @@ export function AttachDiskModalForm({ diskNamesToExclude = [], loading = false, submitError = null, + instance, }: AttachDiskProps) { const { project } = useProjectSelector() @@ -65,7 +68,13 @@ export function AttachDiskModalForm({ loading={loading} title="Attach disk" onSubmit={onSubmit} + submitDisabled={instance && !instanceCan.attachDisk(instance)} > + {instance && ['stopping', 'running'].includes(instance.runState) && ( + + An instance must be stopped to attach a disk. + + )} -