Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 67 additions & 60 deletions app/pages/project/instances/InstancesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
*
* Copyright Oxide Computer Company
*/
import { type UseQueryOptions } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { filesize } from 'filesize'
import { useMemo, useRef } from 'react'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery, type Instance } from '@oxide/api'
import {
apiQueryClient,
getListQFn,
queryClient,
type ApiError,
type Instance,
type InstanceResultsPage,
} from '@oxide/api'
import { Instances24Icon } from '@oxide/design-system/icons/react'

import { instanceTransitioning } from '~/api/util'
Expand All @@ -22,7 +30,7 @@ import { InstanceStateCell } from '~/table/cells/InstanceStateCell'
import { makeLinkCell } from '~/table/cells/LinkCell'
import { getActionsCol } from '~/table/columns/action-col'
import { Columns } from '~/table/columns/common'
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
import { useQueryTable } from '~/table/QueryTable2'
import { CreateLink } from '~/ui/lib/CreateButton'
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
Expand All @@ -46,11 +54,16 @@ const EmptyState = () => (

const colHelper = createColumnHelper<Instance>()

const instanceList = (
project: string,
// kinda gnarly, but we need refetchInterval in the component but not in the loader.
// pick refetchInterval to avoid annoying type conflicts on the full object
options?: Pick<UseQueryOptions<InstanceResultsPage, ApiError>, 'refetchInterval'>
) => getListQFn('instanceList', { query: { project } }, options)

InstancesPage.loader = async ({ params }: LoaderFunctionArgs) => {
const { project } = getProjectSelector(params)
await apiQueryClient.prefetchQuery('instanceList', {
query: { project, limit: PAGE_SIZE },
})
await queryClient.prefetchQuery(instanceList(project).optionsFn())
return null
}

Expand All @@ -69,6 +82,46 @@ export function InstancesPage() {
{ onSuccess: refetchInstances, onDelete: refetchInstances }
)

const columns = useMemo(
() => [
colHelper.accessor('name', {
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
}),
colHelper.accessor('ncpus', {
header: 'CPU',
cell: (info) => (
<>
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
</>
),
}),
colHelper.accessor('memory', {
header: 'Memory',
cell: (info) => {
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
return (
<>
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
</>
)
},
}),
colHelper.accessor(
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
{
header: 'state',
cell: (info) => <InstanceStateCell value={info.getValue()} />,
}
),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol((instance: Instance) => [
...makeButtonActions(instance),
...makeMenuActions(instance),
]),
],
[project, makeButtonActions, makeMenuActions]
)

// this is a whole thing. sit down.

// We initialize this set as empty because we don't have the instances on hand
Expand All @@ -77,10 +130,8 @@ export function InstancesPage() {
const transitioningInstances = useRef<Set<string>>(new Set())
const pollingStartTime = useRef<number>(Date.now())

const { data: instances, dataUpdatedAt } = usePrefetchedApiQuery(
'instanceList',
{ query: { project, limit: PAGE_SIZE } },
{
const { table, query } = useQueryTable({
query: instanceList(project, {
// The point of all this is to poll quickly for a certain amount of time
// after some instance in the current page enters a transitional state
// like starting or stopping. After that, it will keep polling, but more
Expand Down Expand Up @@ -122,8 +173,12 @@ export function InstancesPage() {
? POLL_INTERVAL_FAST
: POLL_INTERVAL_SLOW
},
}
)
}),
columns,
emptyState: <EmptyState />,
})

const { data: instances, dataUpdatedAt } = query

const navigate = useNavigate()
useQuickActions(
Expand All @@ -143,54 +198,6 @@ export function InstancesPage() {
)
)

const { Table } = useQueryTable(
'instanceList',
{ query: { project } },
{ placeholderData: (x) => x }
)

const columns = useMemo(
() => [
colHelper.accessor('name', {
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
}),
colHelper.accessor('ncpus', {
header: 'CPU',
cell: (info) => (
<>
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
</>
),
}),
colHelper.accessor('memory', {
header: 'Memory',
cell: (info) => {
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
return (
<>
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
</>
)
},
}),
colHelper.accessor(
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
{
header: 'state',
cell: (info) => <InstanceStateCell value={info.getValue()} />,
}
),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol((instance: Instance) => [
...makeButtonActions(instance),
...makeMenuActions(instance),
]),
],
[project, makeButtonActions, makeMenuActions]
)

if (!instances) return null

return (
<>
<PageHeader>
Expand All @@ -213,7 +220,7 @@ export function InstancesPage() {
</div>
<CreateLink to={pb.instancesNew({ project })}>New Instance</CreateLink>
</TableActions>
<Table columns={columns} emptyState={<EmptyState />} />
{table}
</>
)
}