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'
910import { useForm } from 'react-hook-form'
1011import { Outlet , useNavigate , type LoaderFunctionArgs } from 'react-router-dom'
1112
@@ -26,8 +27,9 @@ import { confirmAction } from '~/stores/confirm-action'
2627import { confirmDelete } from '~/stores/confirm-delete'
2728import { addToast } from '~/stores/toast'
2829import { 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'
3133import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3234import { Listbox } from '~/ui/lib/Listbox'
3335import { 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
0 commit comments