66 * Copyright Oxide Computer Company
77 */
88import { format } from 'date-fns'
9+ import { filesize } from 'filesize'
910import { useMemo } from 'react'
1011import { useController , type Control } from 'react-hook-form'
1112import { useNavigate , type NavigateFunction } from 'react-router-dom'
@@ -94,9 +95,24 @@ export function CreateDiskSideModalForm({
9495 )
9596 const areImagesLoading = projectImages . isPending || siloImages . isPending
9697
97- const selectedImageId = form . watch ( 'diskSource.imageId' )
98- const selectedImageSize = images . find ( ( image ) => image . id === selectedImageId ) ?. size
99- const imageSizeGiB = selectedImageSize ? bytesToGiB ( selectedImageSize ) : undefined
98+ const snapshotsQuery = useApiQuery ( 'snapshotList' , { query : projectSelector } )
99+ const snapshots = snapshotsQuery . data ?. items || [ ]
100+
101+ // validate disk source size
102+ const diskSource = form . watch ( 'diskSource' ) . type
103+
104+ let validateSizeGiB : number | undefined = undefined
105+ if ( diskSource === 'snapshot' ) {
106+ const selectedSnapshotId = form . watch ( 'diskSource.snapshotId' )
107+ const selectedSnapshotSize = snapshots . find (
108+ ( snapshot ) => snapshot . id === selectedSnapshotId
109+ ) ?. size
110+ validateSizeGiB = selectedSnapshotSize ? bytesToGiB ( selectedSnapshotSize ) : undefined
111+ } else if ( diskSource === 'image' ) {
112+ const selectedImageId = form . watch ( 'diskSource.imageId' )
113+ const selectedImageSize = images . find ( ( image ) => image . id === selectedImageId ) ?. size
114+ validateSizeGiB = selectedImageSize ? bytesToGiB ( selectedImageSize ) : undefined
115+ }
100116
101117 return (
102118 < SideModalForm
@@ -123,8 +139,8 @@ export function CreateDiskSideModalForm({
123139 name = "size"
124140 control = { form . control }
125141 validate = { ( diskSizeGiB : number ) => {
126- if ( imageSizeGiB && diskSizeGiB < imageSizeGiB ) {
127- return `Must be as large as selected image (min. ${ imageSizeGiB } GiB)`
142+ if ( validateSizeGiB && diskSizeGiB < validateSizeGiB ) {
143+ return `Must be as large as selected ${ diskSource } (min. ${ validateSizeGiB } GiB)`
128144 }
129145 } }
130146 />
@@ -144,6 +160,7 @@ const DiskSourceField = ({
144160 const {
145161 field : { value, onChange } ,
146162 } = useController ( { control, name : 'diskSource' } )
163+ const diskSizeField = useController ( { control, name : 'size' } ) . field
147164
148165 return (
149166 < >
@@ -191,6 +208,14 @@ const DiskSourceField = ({
191208 isLoading = { areImagesLoading }
192209 items = { images . map ( ( i ) => toListboxItem ( i , true ) ) }
193210 required
211+ onChange = { ( id ) => {
212+ const image = images . find ( ( i ) => i . id === id ) ! // if it's selected, it must be present
213+ const imageSizeGiB = image . size / GiB
214+ if ( diskSizeField . value < imageSizeGiB ) {
215+ const nearest10 = Math . ceil ( imageSizeGiB / 10 ) * 10
216+ diskSizeField . onChange ( nearest10 )
217+ }
218+ } }
194219 />
195220 ) }
196221
@@ -218,6 +243,7 @@ const SnapshotSelectField = ({ control }: { control: Control<DiskCreate> }) => {
218243 const snapshotsQuery = useApiQuery ( 'snapshotList' , { query : projectSelector } )
219244
220245 const snapshots = snapshotsQuery . data ?. items || [ ]
246+ const diskSizeField = useController ( { control, name : 'size' } ) . field
221247
222248 return (
223249 < ListboxField
@@ -226,22 +252,33 @@ const SnapshotSelectField = ({ control }: { control: Control<DiskCreate> }) => {
226252 label = "Source snapshot"
227253 placeholder = "Select a snapshot"
228254 items = { snapshots . map ( ( i ) => {
255+ const formattedSize = filesize ( i . size , { base : 2 , output : 'object' } )
229256 return {
230257 value : i . id ,
231258 labelString : `${ i . name } ` ,
232259 label : (
233260 < >
234261 < div > { i . name } </ div >
235- < div className = "text-secondary" >
262+ < div className = "text-tertiary selected:text-accent- secondary" >
236263 Created on { format ( i . timeCreated , 'MMM d, yyyy' ) }
237- < DiskNameFromId disk = { i . diskId } />
264+ < DiskNameFromId disk = { i . diskId } /> { ' ' }
265+ < span className = "mx-1 text-quinary selected:text-accent-disabled" > /</ span > { ' ' }
266+ { formattedSize . value } { formattedSize . unit }
238267 </ div >
239268 </ >
240269 ) ,
241270 }
242271 } ) }
243272 isLoading = { snapshotsQuery . isPending }
244273 required
274+ onChange = { ( id ) => {
275+ const snapshot = snapshots . find ( ( i ) => i . id === id ) ! // if it's selected, it must be present
276+ const snapshotSizeGiB = snapshot . size / GiB
277+ if ( diskSizeField . value < snapshotSizeGiB ) {
278+ const nearest10 = Math . ceil ( snapshotSizeGiB / 10 ) * 10
279+ diskSizeField . onChange ( nearest10 )
280+ }
281+ } }
245282 />
246283 )
247284}
0 commit comments