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$/), + }) +})