diff --git a/app/components/form/fields/ImageSelectField.tsx b/app/components/form/fields/ImageSelectField.tsx
index db2ff1f559..8d9a2f8021 100644
--- a/app/components/form/fields/ImageSelectField.tsx
+++ b/app/components/form/fields/ImageSelectField.tsx
@@ -9,7 +9,7 @@ import { useController, type Control } from 'react-hook-form'
import type { Image } from '@oxide/api'
import type { ListboxItem } from '@oxide/ui'
-import { GiB } from '@oxide/util'
+import { bytesToGiB, GiB } from '@oxide/util'
import type { InstanceCreateInput } from 'app/forms/instance-create'
@@ -41,22 +41,43 @@ export function ImageSelectField({ images, control }: ImageSelectFieldProps) {
)
}
-const Slash = () => /
+const Slash = () => (
+ /
+)
export function toListboxItem(i: Image, includeProjectSiloIndicator = false): ListboxItem {
- const projectSiloIndicator = includeProjectSiloIndicator ? (
- <>
- {i.projectId ? 'Project image' : 'Silo image'}
- >
- ) : null
+ const { name, os, projectId, size, version } = i
+ const formattedSize = `${bytesToGiB(size, 1)} GiB`
+
+ // filter out any undefined metadata and create a comma-separated list
+ // for the selected listbox item (shown in labelString)
+ const condensedImageMetadata = [os, version, formattedSize].filter((i) => !!i).join(', ')
+ const metadataForLabelString = condensedImageMetadata.length
+ ? ` (${condensedImageMetadata})`
+ : ''
+
+ // for metadata showing in the dropdown's options, include the project / silo indicator if requested
+ const projectSiloIndicator = includeProjectSiloIndicator
+ ? `${projectId ? 'Project' : 'Silo'} image`
+ : null
+ // filter out undefined metadata here, as well, and create a ``-separated list
+ // for the listbox item (shown for each item in the dropdown)
+ const metadataForLabel = [os, version, formattedSize, projectSiloIndicator]
+ .filter((i) => !!i)
+ .map((i, index) => (
+
+ {index > 0 ? : ''}
+ {i}
+
+ ))
return {
value: i.id,
- labelString: `${i.name} (${i.os}, ${i.version})`,
+ labelString: `${name}${metadataForLabelString}`,
label: (
<>
-
{i.name}
-
- {i.os}
{i.version} {projectSiloIndicator}
+
{name}
+
+ {metadataForLabel}
>
),
diff --git a/libs/util/math.spec.ts b/libs/util/math.spec.ts
index ee2fd81962..19c6f2b4d0 100644
--- a/libs/util/math.spec.ts
+++ b/libs/util/math.spec.ts
@@ -7,7 +7,20 @@
*/
import { expect, it } from 'vitest'
-import { splitDecimal } from './math'
+import { GiB } from '.'
+import { round, splitDecimal } from './math'
+
+it('rounds properly', () => {
+ expect(round(123.456, 0)).toEqual(123)
+ expect(round(123.456, 1)).toEqual(123.5)
+ expect(round(123.456, 2)).toEqual(123.46)
+ expect(round(123.456, 3)).toEqual(123.456)
+ expect(round(123.456, 4)).toEqual(123.456) // trailing zeros are culled
+ expect(round(1.9, 0)).toEqual(2)
+ expect(round(1.9, 1)).toEqual(1.9)
+ expect(round(5 / 2, 2)).toEqual(2.5) // math expressions are resolved
+ expect(round(1879048192 / GiB, 2)).toEqual(1.75) // constants can be evaluated
+})
it.each([
[1.23, ['1', '.23']],
diff --git a/libs/util/units.ts b/libs/util/units.ts
index 9ccdc1aab3..e88a5672a5 100644
--- a/libs/util/units.ts
+++ b/libs/util/units.ts
@@ -12,5 +12,5 @@ export const MiB = 1024 * KiB
export const GiB = 1024 * MiB
export const TiB = 1024 * GiB
-export const bytesToGiB = (b: number) => round(b / GiB, 2)
-export const bytesToTiB = (b: number) => round(b / TiB, 2)
+export const bytesToGiB = (b: number, digits = 2) => round(b / GiB, digits)
+export const bytesToTiB = (b: number, digits = 2) => round(b / TiB, digits)