@@ -14,7 +14,6 @@ import {
1414 genName ,
1515 INSTANCE_MAX_CPU ,
1616 INSTANCE_MAX_RAM_GiB ,
17- MAX_DISK_SIZE_GiB ,
1817 useApiMutation ,
1918 useApiQueryClient ,
2019 usePrefetchedApiQuery ,
@@ -137,9 +136,11 @@ export function CreateInstanceForm() {
137136
138137 const imageInput = useWatch ( { control : control , name : 'image' } )
139138 const image = allImages . find ( ( i ) => i . id === imageInput )
139+ const imageSize = image ?. size ? Math . ceil ( image . size / GiB ) : undefined
140140
141141 return (
142142 < FullPageForm
143+ submitDisabled = { allImages . length ? undefined : 'Image required' }
143144 id = "create-instance-form"
144145 form = { form }
145146 title = "Create instance"
@@ -151,9 +152,10 @@ export function CreateInstanceForm() {
151152 values . presetId === 'custom'
152153 ? { memory : values . memory , ncpus : values . ncpus }
153154 : { memory : preset . memory , ncpus : preset . ncpus }
154-
155- // we should also never have an image ID that's not in the list
156155 const image = allImages . find ( ( i ) => values . image === i . id )
156+ // There should always be an image present, because …
157+ // - The form is disabled unless there are images available.
158+ // - The form defaults to including at least one image.
157159 invariant ( image , 'Expected image to be defined' )
158160
159161 const bootDiskName = values . bootDiskName || genName ( values . name , image . name )
@@ -287,37 +289,51 @@ export function CreateInstanceForm() {
287289 < FormDivider />
288290
289291 < Form . Heading id = "boot-disk" > Boot disk</ Form . Heading >
290- < Tabs . Root id = "boot-disk-tabs" className = "full-width" defaultValue = "project" >
292+ < Tabs . Root
293+ id = "boot-disk-tabs"
294+ className = "full-width"
295+ // default to the project images tab if there are only project images
296+ defaultValue = {
297+ projectImages . length > 0 && siloImages . length === 0 ? 'project' : 'silo'
298+ }
299+ >
291300 < Tabs . List aria-describedby = "boot-disk" >
292- < Tabs . Trigger value = "project" > Project images</ Tabs . Trigger >
293301 < Tabs . Trigger value = "silo" > Silo images</ Tabs . Trigger >
302+ < Tabs . Trigger value = "project" > Project images</ Tabs . Trigger >
294303 </ Tabs . List >
295- < Tabs . Content value = "project" className = "space-y-4" >
296- { projectImages . length === 0 ? (
304+ { allImages . length === 0 && (
305+ < Message
306+ className = "mb-8 ml-10 max-w-lg"
307+ variant = "notice"
308+ content = "Images are required to create a boot disk."
309+ />
310+ ) }
311+ < Tabs . Content value = "silo" className = "space-y-4" >
312+ { siloImages . length === 0 ? (
297313 < div className = "flex max-w-lg items-center justify-center rounded-lg border p-6 border-default" >
298314 < EmptyMessage
299315 icon = { < Images16Icon /> }
300- title = "No project images found"
301- body = "An image needs to be uploaded to be seen here"
302- buttonText = "Upload image"
303- onClick = { ( ) => navigate ( pb . projectImageNew ( projectSelector ) ) }
316+ title = "No silo images found"
317+ body = "Project images need to be promoted to be seen here"
304318 />
305319 </ div >
306320 ) : (
307- < ImageSelectField images = { projectImages } control = { control } />
321+ < ImageSelectField images = { siloImages } control = { control } />
308322 ) }
309323 </ Tabs . Content >
310- < Tabs . Content value = "silo " className = "space-y-4" >
311- { siloImages . length === 0 ? (
324+ < Tabs . Content value = "project " className = "space-y-4" >
325+ { projectImages . length === 0 ? (
312326 < div className = "flex max-w-lg items-center justify-center rounded-lg border p-6 border-default" >
313327 < EmptyMessage
314328 icon = { < Images16Icon /> }
315- title = "No silo images found"
316- body = "Project images need to be promoted to be seen here"
329+ title = "No project images found"
330+ body = "An image needs to be uploaded to be seen here"
331+ buttonText = "Upload image"
332+ onClick = { ( ) => navigate ( pb . projectImageNew ( projectSelector ) ) }
317333 />
318334 </ div >
319335 ) : (
320- < ImageSelectField images = { siloImages } control = { control } />
336+ < ImageSelectField images = { projectImages } control = { control } />
321337 ) }
322338 </ Tabs . Content >
323339 </ Tabs . Root >
@@ -328,15 +344,9 @@ export function CreateInstanceForm() {
328344 label = "Disk size"
329345 name = "bootDiskSize"
330346 control = { control }
331- // Imitate API logic: only require that the disk is big enough to fit the image
332- validate = { ( diskSizeGiB ) => {
333- if ( ! image ) return true
334- if ( diskSizeGiB < image . size / GiB ) {
335- const minSize = Math . ceil ( image . size / GiB )
336- return `Must be as large as selected image (min. ${ minSize } GiB)`
337- }
338- if ( diskSizeGiB > MAX_DISK_SIZE_GiB ) {
339- return `Can be at most ${ MAX_DISK_SIZE_GiB } GiB`
347+ validate = { ( diskSizeGiB : number ) => {
348+ if ( imageSize && diskSizeGiB < imageSize ) {
349+ return `Must be as large as selected image (min. ${ imageSize } GiB)`
340350 }
341351 } }
342352 />
0 commit comments