88
99import { createColumnHelper } from '@tanstack/react-table'
1010import { useCallback , useMemo , useState } from 'react'
11- import { Outlet , type LoaderFunctionArgs } from 'react-router-dom'
11+ import { Outlet , useNavigate , type LoaderFunctionArgs } from 'react-router-dom'
1212
1313import {
1414 apiQueryClient ,
@@ -26,9 +26,11 @@ import { CapacityBar } from '~/components/CapacityBar'
2626import { DocsPopover } from '~/components/DocsPopover'
2727import { ComboboxField } from '~/components/form/fields/ComboboxField'
2828import { HL } from '~/components/HL'
29+ import { MoreActionsMenu } from '~/components/MoreActionsMenu'
2930import { QueryParamTabs } from '~/components/QueryParamTabs'
3031import { getIpPoolSelector , useForm , useIpPoolSelector } from '~/hooks'
3132import { confirmAction } from '~/stores/confirm-action'
33+ import { confirmDelete } from '~/stores/confirm-delete'
3234import { addToast } from '~/stores/toast'
3335import { DefaultPoolCell } from '~/table/cells/DefaultPoolCell'
3436import { SkeletonCell } from '~/table/cells/EmptyCell'
@@ -46,9 +48,10 @@ import { TipIcon } from '~/ui/lib/TipIcon'
4648import { docLinks } from '~/util/links'
4749import { pb } from '~/util/path-builder'
4850
51+ const query = { limit : PAGE_SIZE }
52+
4953IpPoolPage . loader = async function ( { params } : LoaderFunctionArgs ) {
5054 const { pool } = getIpPoolSelector ( params )
51- const query = { limit : PAGE_SIZE }
5255 await Promise . all ( [
5356 apiQueryClient . prefetchQuery ( 'ipPoolView' , { path : { pool } } ) ,
5457 apiQueryClient . prefetchQuery ( 'ipPoolSiloList' , { path : { pool } , query } ) ,
@@ -70,16 +73,54 @@ IpPoolPage.loader = async function ({ params }: LoaderFunctionArgs) {
7073export function IpPoolPage ( ) {
7174 const poolSelector = useIpPoolSelector ( )
7275 const { data : pool } = usePrefetchedApiQuery ( 'ipPoolView' , { path : poolSelector } )
76+ const { data : ranges } = usePrefetchedApiQuery ( 'ipPoolRangeList' , {
77+ path : poolSelector ,
78+ query,
79+ } )
80+ const navigate = useNavigate ( )
81+ const { mutateAsync : deletePool } = useApiMutation ( 'ipPoolDelete' , {
82+ onSuccess ( ) {
83+ apiQueryClient . invalidateQueries ( 'ipPoolList' )
84+ navigate ( pb . ipPools ( ) )
85+ addToast ( { content : 'IP pool deleted' } )
86+ } ,
87+ } )
88+
89+ const actions = useMemo (
90+ ( ) => [
91+ {
92+ label : 'Edit' ,
93+ onActivate ( ) {
94+ navigate ( pb . ipPoolEdit ( poolSelector ) )
95+ } ,
96+ } ,
97+ {
98+ label : 'Delete' ,
99+ onActivate : confirmDelete ( {
100+ doDelete : ( ) => deletePool ( { path : { pool : pool . name } } ) ,
101+ label : pool . name ,
102+ } ) ,
103+ disabled :
104+ ! ! ranges . items . length && 'IP pool cannot be deleted while it contains IP ranges' ,
105+ className : ranges . items . length ? '' : 'destructive' ,
106+ } ,
107+ ] ,
108+ [ deletePool , navigate , poolSelector , pool . name , ranges . items ]
109+ )
110+
73111 return (
74112 < >
75113 < PageHeader >
76114 < PageTitle icon = { < IpGlobal24Icon /> } > { pool . name } </ PageTitle >
77- < DocsPopover
78- heading = "IP pools"
79- icon = { < IpGlobal16Icon /> }
80- summary = "IP pools are collections of external IPs you can assign to silos. When a pool is linked to a silo, users in that silo can allocate IPs from the pool for their instances."
81- links = { [ docLinks . systemIpPools ] }
82- />
115+ < div className = "inline-flex gap-2" >
116+ < DocsPopover
117+ heading = "IP pools"
118+ icon = { < IpGlobal16Icon /> }
119+ summary = "IP pools are collections of external IPs you can assign to silos. When a pool is linked to a silo, users in that silo can allocate IPs from the pool for their instances."
120+ links = { [ docLinks . systemIpPools ] }
121+ />
122+ < MoreActionsMenu label = "IP pool actions" actions = { actions } />
123+ </ div >
83124 </ PageHeader >
84125 < UtilizationBars />
85126 < QueryParamTabs className = "full-width" defaultValue = "ranges" >
0 commit comments