Skip to content

Commit 4db8d83

Browse files
Convert more tables to useQueryTable2 (#2111)
* convert silo IP pools tab to useQueryTable2 * set defaultCell inside QueryTable * we don't even need defaultCell. default cell color in table.css is already text-secondary * Convert FloatingIps page * comment on makeLinkCell about static or memoize * tweak floating IP instance name logic in detach modal * rename value prop of InstanceLinkCell to instanceId --------- Co-authored-by: Charlie Park <charlie@oxidecomputer.com>
1 parent 9485ca2 commit 4db8d83

File tree

7 files changed

+208
-180
lines changed

7 files changed

+208
-180
lines changed

app/pages/project/disks/DisksPage.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { DiskStatusBadge } from '~/components/StatusBadge'
2323
import { getProjectSelector, useProjectSelector, useToast } from '~/hooks'
2424
import { confirmDelete } from '~/stores/confirm-delete'
2525
import { DateCell } from '~/table/cells/DateCell'
26-
import { defaultCell } from '~/table/cells/DefaultCell'
2726
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
2827
import { SizeCell } from '~/table/cells/SizeCell'
2928
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
@@ -74,13 +73,16 @@ DisksPage.loader = async ({ params }: LoaderFunctionArgs) => {
7473
const colHelper = createColumnHelper<Disk>()
7574

7675
const staticCols = [
77-
colHelper.accessor('name', { cell: defaultCell }),
76+
colHelper.accessor('name', {}),
7877
// sneaky: rather than looking at particular states, just look at
7978
// whether it has an instance field
80-
colHelper.accessor((disk) => ('instance' in disk.state ? disk.state.instance : null), {
81-
header: 'Attached to',
82-
cell: (props) => <InstanceLinkCell value={props.getValue()} />,
83-
}),
79+
colHelper.accessor(
80+
(disk) => ('instance' in disk.state ? disk.state.instance : undefined),
81+
{
82+
header: 'Attached to',
83+
cell: (props) => <InstanceLinkCell instanceId={props.getValue()} />,
84+
}
85+
),
8486
colHelper.accessor('size', { cell: (props) => <SizeCell value={props.getValue()} /> }),
8587
colHelper.accessor('state.state', {
8688
header: 'Status',

app/pages/project/floating-ips/FloatingIpsPage.tsx

Lines changed: 99 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import { useState } from 'react'
8+
import { createColumnHelper } from '@tanstack/react-table'
9+
import { useCallback, useState } from 'react'
910
import { useForm } from 'react-hook-form'
1011
import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
1112

@@ -26,8 +27,9 @@ import { confirmAction } from '~/stores/confirm-action'
2627
import { confirmDelete } from '~/stores/confirm-delete'
2728
import { addToast } from '~/stores/toast'
2829
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
29-
import type { MenuAction } from '~/table/columns/action-col'
30-
import { useQueryTable } from '~/table/QueryTable'
30+
import { makeLinkCell } from '~/table/cells/LinkCell'
31+
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
32+
import { useQueryTable2 } from '~/table/QueryTable2'
3133
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3234
import { Listbox } from '~/ui/lib/Listbox'
3335
import { Message } from '~/ui/lib/Message'
@@ -68,8 +70,6 @@ export function FloatingIpsPage() {
6870
query: { project },
6971
})
7072
const navigate = useNavigate()
71-
const getInstanceName = (instanceId: string) =>
72-
instances.items.find((i) => i.id === instanceId)?.name
7373

7474
const floatingIpDetach = useApiMutation('floatingIpDetach', {
7575
onSuccess() {
@@ -88,76 +88,102 @@ export function FloatingIpsPage() {
8888
},
8989
})
9090

91-
const makeActions = (floatingIp: FloatingIp): MenuAction[] => {
92-
const isAttachedToAnInstance = !!floatingIp.instanceId
93-
const attachOrDetachAction = isAttachedToAnInstance
94-
? {
95-
label: 'Detach',
96-
onActivate: () =>
97-
confirmAction({
98-
actionType: 'danger',
99-
doAction: () =>
100-
floatingIpDetach.mutateAsync({
101-
path: { floatingIp: floatingIp.name },
102-
query: { project },
103-
}),
104-
modalTitle: 'Detach Floating IP',
105-
modalContent: (
106-
<p>
107-
Are you sure you want to detach floating IP <HL>{floatingIp.name}</HL>{' '}
108-
from instance{' '}
109-
<HL>
110-
{
111-
// instanceId is guaranteed to be non-null here
112-
getInstanceName(floatingIp.instanceId!)
113-
}
114-
</HL>
115-
? The instance will no longer be reachable at <HL>{floatingIp.ip}</HL>.
116-
</p>
117-
),
118-
errorTitle: 'Error detaching floating IP',
119-
}),
120-
}
121-
: {
122-
label: 'Attach',
123-
onActivate() {
124-
setFloatingIpToModify(floatingIp)
125-
},
126-
}
127-
return [
128-
{
129-
label: 'Edit',
130-
onActivate: () => {
131-
apiQueryClient.setQueryData(
132-
'floatingIpView',
133-
{
134-
path: { floatingIp: floatingIp.name },
135-
query: { project },
91+
const colHelper = createColumnHelper<FloatingIp>()
92+
93+
const staticCols = [
94+
colHelper.accessor('name', {
95+
cell: makeLinkCell((name) => pb.floatingIp({ floatingIp: name, project })),
96+
}),
97+
colHelper.accessor('description', {}),
98+
colHelper.accessor('ip', {}),
99+
colHelper.accessor('instanceId', {
100+
cell: (props) => <InstanceLinkCell instanceId={props.getValue()} />,
101+
header: 'Attached to instance',
102+
}),
103+
]
104+
105+
const makeActions = useCallback(
106+
(floatingIp: FloatingIp): MenuAction[] => {
107+
const instanceName = floatingIp.instanceId
108+
? instances.items.find((i) => i.id === floatingIp.instanceId)?.name
109+
: undefined
110+
// handling the rather unlikely case where the instance is not in the 1000 we fetched
111+
const fromInstance = instanceName ? (
112+
<>
113+
{' ' /* important */}
114+
from instance <HL>{instanceName}</HL>
115+
</>
116+
) : null
117+
118+
const isAttachedToAnInstance = !!floatingIp.instanceId
119+
const attachOrDetachAction = isAttachedToAnInstance
120+
? {
121+
label: 'Detach',
122+
onActivate: () =>
123+
confirmAction({
124+
actionType: 'danger',
125+
doAction: () =>
126+
floatingIpDetach.mutateAsync({
127+
path: { floatingIp: floatingIp.name },
128+
query: { project },
129+
}),
130+
modalTitle: 'Detach Floating IP',
131+
// instanceName! non-null because we only see this if there is an instance
132+
modalContent: (
133+
<p>
134+
Are you sure you want to detach floating IP <HL>{floatingIp.name}</HL>
135+
{fromInstance}? The instance will no longer be reachable at{' '}
136+
<HL>{floatingIp.ip}</HL>.
137+
</p>
138+
),
139+
errorTitle: 'Error detaching floating IP',
140+
}),
141+
}
142+
: {
143+
label: 'Attach',
144+
onActivate() {
145+
setFloatingIpToModify(floatingIp)
136146
},
137-
floatingIp
138-
)
139-
navigate(pb.floatingIpEdit({ project, floatingIp: floatingIp.name }))
147+
}
148+
return [
149+
{
150+
label: 'Edit',
151+
onActivate: () => {
152+
apiQueryClient.setQueryData(
153+
'floatingIpView',
154+
{
155+
path: { floatingIp: floatingIp.name },
156+
query: { project },
157+
},
158+
floatingIp
159+
)
160+
navigate(pb.floatingIpEdit({ project, floatingIp: floatingIp.name }))
161+
},
162+
},
163+
attachOrDetachAction,
164+
{
165+
label: 'Delete',
166+
disabled: isAttachedToAnInstance
167+
? 'This floating IP must be detached from the instance before it can be deleted'
168+
: false,
169+
onActivate: confirmDelete({
170+
doDelete: () =>
171+
deleteFloatingIp.mutateAsync({
172+
path: { floatingIp: floatingIp.name },
173+
query: { project },
174+
}),
175+
label: floatingIp.name,
176+
}),
140177
},
141-
},
142-
attachOrDetachAction,
143-
{
144-
label: 'Delete',
145-
disabled: isAttachedToAnInstance
146-
? 'This floating IP must be detached from the instance before it can be deleted'
147-
: false,
148-
onActivate: confirmDelete({
149-
doDelete: () =>
150-
deleteFloatingIp.mutateAsync({
151-
path: { floatingIp: floatingIp.name },
152-
query: { project },
153-
}),
154-
label: floatingIp.name,
155-
}),
156-
},
157-
]
158-
}
178+
]
179+
},
180+
[deleteFloatingIp, floatingIpDetach, navigate, project, instances]
181+
)
182+
183+
const { Table } = useQueryTable2('floatingIpList', { query: { project } })
184+
185+
const columns = useColsWithActions(staticCols, makeActions)
159186

160-
const { Table, Column } = useQueryTable('floatingIpList', { query: { project } })
161187
return (
162188
<>
163189
<PageHeader>
@@ -173,16 +199,7 @@ export function FloatingIpsPage() {
173199
New Floating IP
174200
</TableControlsLink>
175201
</TableControls>
176-
<Table emptyState={<EmptyState />} makeActions={makeActions}>
177-
<Column accessor="name" />
178-
<Column accessor="description" />
179-
<Column accessor="ip" />
180-
<Column
181-
accessor="instanceId"
182-
header="Attached to instance"
183-
cell={InstanceLinkCell}
184-
/>
185-
</Table>
202+
<Table emptyState={<EmptyState />} columns={columns} />
186203
<Outlet />
187204
{floatingIpToModify && (
188205
<AttachFloatingIpModal

app/pages/system/networking/IpPoolsTab.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { IpUtilCell } from '~/components/IpPoolUtilization'
2323
import { useQuickActions } from '~/hooks'
2424
import { confirmDelete } from '~/stores/confirm-delete'
2525
import { DateCell } from '~/table/cells/DateCell'
26-
import { defaultCell } from '~/table/cells/DefaultCell'
2726
import { SkeletonCell } from '~/table/cells/EmptyCell'
2827
import { makeLinkCell } from '~/table/cells/LinkCell'
2928
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
@@ -53,7 +52,7 @@ const colHelper = createColumnHelper<IpPool>()
5352

5453
const staticColumns = [
5554
colHelper.accessor('name', { cell: makeLinkCell((pool) => pb.ipPool({ pool })) }),
56-
colHelper.accessor('description', { cell: defaultCell }),
55+
colHelper.accessor('description', {}),
5756
colHelper.accessor('name', {
5857
id: 'Utilization',
5958
header: 'Utilization',

0 commit comments

Comments
 (0)