diff --git a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx index 19b8568eaf..e0ba3820df 100644 --- a/app/pages/project/instances/instance/tabs/NetworkingTab.tsx +++ b/app/pages/project/instances/instance/tabs/NetworkingTab.tsx @@ -18,6 +18,7 @@ import { usePrefetchedApiQuery, type ExternalIp, type InstanceNetworkInterface, + type InstanceState, } from '@oxide/api' import { IpGlobal24Icon, Networking24Icon } from '@oxide/design-system/icons/react' @@ -109,7 +110,15 @@ NetworkingTab.loader = async ({ params }: LoaderFunctionArgs) => { return null } -const colHelper = createColumnHelper() +// Bit of a hack: by putting the instance state in the row data, we can avoid +// remaking the row actions callback whenever the instance state changes, which +// causes the whole table to get re-rendered, which jarringly closes any open +// row actions menus +type NicRow = InstanceNetworkInterface & { + instanceState: InstanceState +} + +const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('name', { header: 'name', @@ -202,56 +211,58 @@ export function NetworkingTab() { path: { instance: instanceName }, query: { project }, }) - const canUpdateNic = instanceCan.updateNic(instance) const makeActions = useCallback( - (nic: InstanceNetworkInterface): MenuAction[] => [ - { - label: 'Make primary', - onActivate() { - editNic({ - path: { interface: nic.name }, - query: instanceSelector, - body: { ...nic, primary: true }, - }) - }, - disabled: nic.primary - ? 'This network interface is already set as primary' - : !canUpdateNic && ( - <> - The instance must be {updateNicStates} to change its primary network - interface - - ), - }, - { - label: 'Edit', - onActivate() { - setEditing(nic) - }, - disabled: !canUpdateNic && ( - <> - The instance must be {updateNicStates} before editing a network interface's - settings - - ), - }, - { - label: 'Delete', - onActivate: confirmDelete({ - doDelete: () => - deleteNic({ + (nic: NicRow): MenuAction[] => { + const canUpdateNic = instanceCan.updateNic({ runState: nic.instanceState }) + return [ + { + label: 'Make primary', + onActivate() { + editNic({ path: { interface: nic.name }, query: instanceSelector, - }), - label: nic.name, - }), - disabled: !canUpdateNic && ( - <>The instance must be {updateNicStates} to delete a network interface - ), - }, - ], - [canUpdateNic, deleteNic, editNic, instanceSelector] + body: { ...nic, primary: true }, + }) + }, + disabled: nic.primary + ? 'This network interface is already set as primary' + : !canUpdateNic && ( + <> + The instance must be {updateNicStates} to change its primary network + interface + + ), + }, + { + label: 'Edit', + onActivate() { + setEditing(nic) + }, + disabled: !canUpdateNic && ( + <> + The instance must be {updateNicStates} before editing a network + interface's settings + + ), + }, + { + label: 'Delete', + onActivate: confirmDelete({ + doDelete: () => + deleteNic({ + path: { interface: nic.name }, + query: instanceSelector, + }), + label: nic.name, + }), + disabled: !canUpdateNic && ( + <>The instance must be {updateNicStates} to delete a network interface + ), + }, + ] + }, + [deleteNic, editNic, instanceSelector] ) const columns = useColsWithActions(staticCols, makeActions) @@ -260,9 +271,14 @@ export function NetworkingTab() { query: { ...instanceSelector, limit: 1000 }, }).data.items + const nicRows = useMemo( + () => nics.map((nic) => ({ ...nic, instanceState: instance.runState })), + [nics, instance] + ) + const tableInstance = useReactTable({ columns, - data: nics || [], + data: nicRows, getCoreRowModel: getCoreRowModel(), }) @@ -414,7 +430,7 @@ export function NetworkingTab() { Network interfaces setCreateModalOpen(true)} - disabled={!canUpdateNic} + disabled={!instanceCan.updateNic(instance)} disabledReason={ <> A network interface cannot be created or edited unless the instance is{' '}