diff --git a/app/components/TimeAgo.tsx b/app/components/TimeAgo.tsx
index 0f93874212..2aa463f834 100644
--- a/app/components/TimeAgo.tsx
+++ b/app/components/TimeAgo.tsx
@@ -26,10 +26,8 @@ export const TimeAgo = ({
)
return (
-
-
- {timeAgoAbbr(datetime)}
-
-
+
+ {timeAgoAbbr(datetime)}
+
)
}
diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx
index a00ae3eb35..073807b0db 100644
--- a/app/pages/project/instances/InstancesPage.tsx
+++ b/app/pages/project/instances/InstancesPage.tsx
@@ -6,6 +6,7 @@
* Copyright Oxide Computer Company
*/
import { createColumnHelper } from '@tanstack/react-table'
+import { filesize } from 'filesize'
import { useMemo } from 'react'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
@@ -20,7 +21,6 @@ import { Instances16Icon, Instances24Icon } from '@oxide/design-system/icons/rea
import { DocsPopover } from '~/components/DocsPopover'
import { RefreshButton } from '~/components/RefreshButton'
import { getProjectSelector, useProjectSelector, useQuickActions } from '~/hooks'
-import { InstanceResourceCell } from '~/table/cells/InstanceResourceCell'
import { InstanceStatusCell } from '~/table/cells/InstanceStatusCell'
import { makeLinkCell } from '~/table/cells/LinkCell'
import { getActionsCol } from '~/table/columns/action-col'
@@ -99,21 +99,32 @@ export function InstancesPage() {
colHelper.accessor('name', {
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
}),
- colHelper.accessor((i) => ({ ncpus: i.ncpus, memory: i.memory }), {
- header: 'CPU, RAM',
- cell: (info) => ,
+ colHelper.accessor('ncpus', {
+ header: 'CPU',
+ cell: (info) => (
+ <>
+ {info.getValue()} vCPU
+ >
+ ),
+ }),
+ colHelper.accessor('memory', {
+ header: 'Memory',
+ cell: (info) => {
+ const memory = filesize(info.getValue(), { output: 'object', base: 2 })
+ return (
+ <>
+ {memory.value} {memory.unit}
+ >
+ )
+ },
}),
colHelper.accessor(
- (i) => ({
- runState: i.runState,
- timeRunStateUpdated: i.timeRunStateUpdated,
- }),
+ (i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
{
header: 'status',
cell: (info) => ,
}
),
- colHelper.accessor('hostname', {}),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol(makeActions),
],
@@ -137,7 +148,7 @@ export function InstancesPage() {
New Instance
-
} rowHeight="large" />
+ } />
>
)
}
diff --git a/app/pages/system/inventory/sled/SledInstancesTab.tsx b/app/pages/system/inventory/sled/SledInstancesTab.tsx
index 18117d37d9..2272d2a266 100644
--- a/app/pages/system/inventory/sled/SledInstancesTab.tsx
+++ b/app/pages/system/inventory/sled/SledInstancesTab.tsx
@@ -56,16 +56,17 @@ const staticCols = [
)
},
}),
+ // we don't show run state last update time like on project instances because
+ // it's not in this response
colHelper.accessor('state', {
header: 'status',
- cell: (info) => ,
+ cell: (info) => ,
}),
colHelper.accessor((i) => R.pick(i, ['memory', 'ncpus']), {
header: 'specs',
cell: (info) => ,
}),
colHelper.accessor('timeCreated', Columns.timeCreated),
- colHelper.accessor('timeModified', Columns.timeModified),
]
export function SledInstancesTab() {
diff --git a/app/table/cells/InstanceStatusCell.tsx b/app/table/cells/InstanceStatusCell.tsx
index 30b94b78a5..959cdc3457 100644
--- a/app/table/cells/InstanceStatusCell.tsx
+++ b/app/table/cells/InstanceStatusCell.tsx
@@ -14,8 +14,8 @@ type Props = { value: Pick }
export const InstanceStatusCell = ({ value }: Props) => {
return (
-
-
+
+
)
diff --git a/mock-api/instance.ts b/mock-api/instance.ts
index bfdfe5597f..c519297c02 100644
--- a/mock-api/instance.ts
+++ b/mock-api/instance.ts
@@ -7,14 +7,16 @@
*/
import type { Instance } from '@oxide/api'
+import { GiB } from '~/util/units'
+
import type { Json } from './json-type'
import { project } from './project'
export const instance: Json
= {
id: '935499b3-fd96-432a-9c21-83a3dc1eece4',
name: 'db1',
- ncpus: 7,
- memory: 1024 * 1024 * 256,
+ ncpus: 2,
+ memory: 4 * GiB,
description: 'an instance',
hostname: 'oxide.com',
project_id: project.id,
@@ -27,8 +29,8 @@ export const instance: Json = {
const failedInstance: Json = {
id: 'b5946edc-5bed-4597-88ab-9a8beb9d32a4',
name: 'you-fail',
- ncpus: 7,
- memory: 1024 * 1024 * 256,
+ ncpus: 4,
+ memory: 6 * GiB,
description: 'a failed instance',
hostname: 'oxide.com',
project_id: project.id,
@@ -41,8 +43,8 @@ const failedInstance: Json = {
const startingInstance: Json = {
id: '16737f54-1f76-4c96-8b7c-9d24971c1d62',
name: 'not-there-yet',
- ncpus: 7,
- memory: 1024 * 1024 * 256,
+ ncpus: 2,
+ memory: 8 * GiB,
description: 'a starting instance',
hostname: 'oxide.com',
project_id: project.id,
diff --git a/test/e2e/instance.e2e.ts b/test/e2e/instance.e2e.ts
index 973b44d006..204bd22bd9 100644
--- a/test/e2e/instance.e2e.ts
+++ b/test/e2e/instance.e2e.ts
@@ -5,7 +5,7 @@
*
* Copyright Oxide Computer Company
*/
-import { expect, refreshInstance, sleep, test } from './utils'
+import { expect, expectRowVisible, refreshInstance, sleep, test } from './utils'
test('can delete a failed instance', async ({ page }) => {
await page.goto('/projects/mock-project/instances')
@@ -78,3 +78,27 @@ test('delete from instance detail', async ({ page }) => {
await expect(page.getByRole('cell', { name: 'db1' })).toBeVisible()
await expect(page.getByRole('cell', { name: 'you-fail' })).toBeHidden()
})
+
+test('instance table', async ({ page }) => {
+ await page.goto('/projects/mock-project/instances')
+
+ const table = page.getByRole('table')
+ await expectRowVisible(table, {
+ name: 'db1',
+ CPU: '2 vCPU',
+ Memory: '4 GiB',
+ status: expect.stringMatching(/^running\d+s$/),
+ })
+ await expectRowVisible(table, {
+ name: 'you-fail',
+ CPU: '4 vCPU',
+ Memory: '6 GiB',
+ status: expect.stringMatching(/^failed\d+s$/),
+ })
+ await expectRowVisible(table, {
+ name: 'not-there-yet',
+ CPU: '2 vCPU',
+ Memory: '8 GiB',
+ status: expect.stringMatching(/^starting\d+s$/),
+ })
+})