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.
+
+ )}
-