@@ -19,7 +19,7 @@ import {
1919 type ExternalIp ,
2020 type InstanceNetworkInterface ,
2121} from '@oxide/api'
22- import { Networking24Icon } from '@oxide/design-system/icons/react'
22+ import { IpGlobal24Icon , Networking24Icon } from '@oxide/design-system/icons/react'
2323
2424import { HL } from '~/components/HL'
2525import { CreateNetworkInterfaceForm } from '~/forms/network-interface-create'
@@ -255,6 +255,16 @@ export function NetworkingTab() {
255255 } ) ,
256256 ]
257257
258+ const ephemeralIpDetach = useApiMutation ( 'instanceEphemeralIpDetach' , {
259+ onSuccess ( ) {
260+ queryClient . invalidateQueries ( 'instanceExternalIpList' )
261+ addToast ( { content : 'Your ephemeral IP has been detached' } )
262+ } ,
263+ onError : ( err ) => {
264+ addToast ( { title : 'Error' , content : err . message , variant : 'error' } )
265+ } ,
266+ } )
267+
258268 const floatingIpDetach = useApiMutation ( 'floatingIpDetach' , {
259269 onSuccess ( ) {
260270 queryClient . invalidateQueries ( 'floatingIpList' )
@@ -275,35 +285,50 @@ export function NetworkingTab() {
275285 } ,
276286 }
277287
278- if ( externalIp . kind === 'floating' ) {
279- return [
280- copyAction ,
281- {
282- label : 'Detach' ,
283- onActivate : ( ) =>
284- confirmAction ( {
285- actionType : 'danger' ,
286- doAction : ( ) =>
287- floatingIpDetach . mutateAsync ( {
288- path : { floatingIp : externalIp . name } ,
289- query : { project } ,
290- } ) ,
291- modalTitle : 'Detach Floating IP' ,
292- modalContent : (
293- < p >
294- Are you sure you want to detach floating IP < HL > { externalIp . name } </ HL > { ' ' }
295- from < HL > { instanceName } </ HL > ? The instance will no longer be reachable
296- at < HL > { externalIp . ip } </ HL > .
297- </ p >
298- ) ,
299- errorTitle : 'Error detaching floating IP' ,
300- } ) ,
301- } ,
302- ]
303- }
288+ const doAction =
289+ externalIp . kind === 'floating'
290+ ? ( ) =>
291+ floatingIpDetach . mutateAsync ( {
292+ path : { floatingIp : externalIp . name } ,
293+ query : { project } ,
294+ } )
295+ : ( ) =>
296+ ephemeralIpDetach . mutateAsync ( {
297+ path : { instance : instanceName } ,
298+ query : { project } ,
299+ } )
300+
301+ return [
302+ copyAction ,
303+ {
304+ label : 'Detach' ,
305+ onActivate : ( ) =>
306+ confirmAction ( {
307+ actionType : 'danger' ,
308+ doAction,
309+ modalTitle : `Confirm detach ${ externalIp . kind } IP` ,
310+ modalContent : (
311+ < p >
312+ Are you sure you want to detach{ ' ' }
313+ { externalIp . kind === 'ephemeral' ? (
314+ 'this ephemeral IP'
315+ ) : (
316+ < >
317+ floating IP < HL > { externalIp . name } </ HL >
318+ </ >
319+ ) } { ' ' }
320+ from < HL > { instanceName } </ HL > ? The instance will no longer be reachable at{ ' ' }
321+ < HL > { externalIp . ip } </ HL > .
322+ </ p >
323+ ) ,
324+ errorTitle : `Error detaching ${ externalIp . kind } IP` ,
325+ } ) ,
326+ } ,
327+ ]
328+
304329 return [ copyAction ]
305330 } ,
306- [ floatingIpDetach , instanceName , project ]
331+ [ ephemeralIpDetach , floatingIpDetach , instanceName , project ]
307332 )
308333
309334 const ipTableInstance = useReactTable ( {
@@ -338,7 +363,17 @@ export function NetworkingTab() {
338363 />
339364 ) }
340365 </ TableControls >
341- < Table aria-labelledby = "attached-ips-label" table = { ipTableInstance } />
366+ { eips . items . length > 0 ? (
367+ < Table aria-labelledby = "attached-ips-label" table = { ipTableInstance } />
368+ ) : (
369+ < TableEmptyBox >
370+ < EmptyMessage
371+ icon = { < IpGlobal24Icon /> }
372+ title = "No external IPs"
373+ body = "You need to attach an external IP to be able to see it here"
374+ />
375+ </ TableEmptyBox >
376+ ) }
342377
343378 < TableControls className = "mt-8" >
344379 < TableTitle id = "nics-label" > Network interfaces</ TableTitle >
0 commit comments