Skip to content

Commit c53101e

Browse files
authored
Subnet pool utilization (#3165)
oxidecomputer/omicron#10157 implements this on the API side. Because subnet pools don't have a type field like IP pools, I had to add last modified time to the properties table on the details page to get an even number of cells. <img width="1122" height="401" alt="image" src="https://github.com/user-attachments/assets/4d7b0f48-67d5-4b54-945f-a2343be471f5" /> <img width="1116" height="245" alt="image" src="https://github.com/user-attachments/assets/825e445d-46e7-4a7c-bccf-22ea2e24f0e1" />
1 parent a72a242 commit c53101e

File tree

12 files changed

+124
-42
lines changed

12 files changed

+124
-42
lines changed

OMICRON_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
d7c3b00d743bcc9212b222a74ae27cc970b1ee2c
1+
254a0c51bc0beecb79c8a9dfccce8e7bc35b5ca4

app/api/__generated__/Api.ts

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/api/__generated__/OMICRON_VERSION

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/api/__generated__/validate.ts

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/forms/subnet-pool-member-add.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export default function SubnetPoolMemberAdd() {
117117
const addMember = useApiMutation(api.systemSubnetPoolMemberAdd, {
118118
onSuccess() {
119119
queryClient.invalidateEndpoint('systemSubnetPoolMemberList')
120+
queryClient.invalidateEndpoint('systemSubnetPoolUtilizationView')
120121
addToast({ content: 'Member added' })
121122
onDismiss()
122123
},

app/pages/system/networking/IpPoolPage.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { LinkCell } from '~/table/cells/LinkCell'
4242
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
4343
import { Columns } from '~/table/columns/common'
4444
import { useQueryTable } from '~/table/QueryTable'
45-
import { BigNum } from '~/ui/lib/BigNum'
45+
import { UtilizationFraction } from '~/ui/lib/BigNum'
4646
import { toComboboxItems } from '~/ui/lib/Combobox'
4747
import { CreateButton, CreateLink } from '~/ui/lib/CreateButton'
4848
import * as Dropdown from '~/ui/lib/DropdownMenu'
@@ -209,9 +209,7 @@ function PoolProperties() {
209209
</PropertiesTable.Row>
210210
<PropertiesTable.Row label="IPs remaining">
211211
<span>
212-
<BigNum className="text-raise" num={utilization.remaining} />
213-
{' / '}
214-
<BigNum className="text-secondary" num={utilization.capacity} />
212+
<UtilizationFraction {...utilization} />
215213
</span>
216214
</PropertiesTable.Row>
217215
<PropertiesTable.DateRow date={pool.timeCreated} label="Created" />

app/pages/system/networking/IpPoolsPage.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { makeLinkCell } from '~/table/cells/LinkCell'
2626
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
2727
import { Columns } from '~/table/columns/common'
2828
import { useQueryTable } from '~/table/QueryTable'
29-
import { BigNum } from '~/ui/lib/BigNum'
29+
import { UtilizationFraction } from '~/ui/lib/BigNum'
3030
import { CreateLink } from '~/ui/lib/CreateButton'
3131
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3232
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -50,8 +50,7 @@ function UtilizationCell({ pool }: { pool: string }) {
5050
if (!data) return <SkeletonCell />
5151
return (
5252
<div>
53-
<BigNum className="text-raise" num={data.remaining} /> /{' '}
54-
<BigNum className="text-secondary" num={data.capacity} />
53+
<UtilizationFraction {...data} />
5554
</div>
5655
)
5756
}

app/pages/system/networking/SubnetPoolPage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { LinkCell } from '~/table/cells/LinkCell'
4242
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
4343
import { Columns } from '~/table/columns/common'
4444
import { useQueryTable } from '~/table/QueryTable'
45+
import { UtilizationFraction } from '~/ui/lib/BigNum'
4546
import { toComboboxItems } from '~/ui/lib/Combobox'
4647
import { CreateButton, CreateLink } from '~/ui/lib/CreateButton'
4748
import * as Dropdown from '~/ui/lib/DropdownMenu'
@@ -65,6 +66,8 @@ const subnetPoolMemberList = ({ subnetPool }: PP.SubnetPool) =>
6566
getListQFn(api.systemSubnetPoolMemberList, { path: { pool: subnetPool } })
6667
const siloList = q(api.siloList, { query: { limit: ALL_ISH } })
6768
const siloView = ({ silo }: PP.Silo) => q(api.siloView, { path: { silo } })
69+
const subnetPoolUtilizationView = ({ subnetPool }: PP.SubnetPool) =>
70+
q(api.systemSubnetPoolUtilizationView, { path: { pool: subnetPool } })
6871
const siloSubnetPoolList = (silo: string) =>
6972
q(api.siloSubnetPoolList, { path: { silo }, query: { limit: ALL_ISH } })
7073

@@ -78,6 +81,7 @@ export async function clientLoader({ params }: LoaderFunctionArgs) {
7881
}
7982
}),
8083
queryClient.prefetchQuery(subnetPoolMemberList(selector).optionsFn()),
84+
queryClient.prefetchQuery(subnetPoolUtilizationView(selector)),
8185
queryClient.fetchQuery(siloList).then((silos) => {
8286
for (const silo of silos.items) {
8387
queryClient.setQueryData(siloView({ silo: silo.id }).queryKey, silo)
@@ -154,6 +158,7 @@ export default function SubnetPoolPage() {
154158
function PoolProperties() {
155159
const poolSelector = useSubnetPoolSelector()
156160
const { data: pool } = usePrefetchedQuery(subnetPoolView(poolSelector))
161+
const { data: utilization } = usePrefetchedQuery(subnetPoolUtilizationView(poolSelector))
157162

158163
return (
159164
<PropertiesTable columns={2} className="-mt-8 mb-8">
@@ -162,9 +167,13 @@ function PoolProperties() {
162167
<PropertiesTable.Row label="IP version">
163168
<IpVersionBadge ipVersion={pool.ipVersion} />
164169
</PropertiesTable.Row>
165-
{/* TODO: add utilization row once Nexus endpoint is implemented
166-
https://github.com/oxidecomputer/omicron/issues/10109 */}
170+
<PropertiesTable.Row label="Addresses remaining">
171+
<span>
172+
<UtilizationFraction {...utilization} />
173+
</span>
174+
</PropertiesTable.Row>
167175
<PropertiesTable.DateRow date={pool.timeCreated} label="Created" />
176+
<PropertiesTable.DateRow date={pool.timeModified} label="Last Modified" />
168177
</PropertiesTable>
169178
)
170179
}
@@ -183,6 +192,7 @@ function MembersTable() {
183192
const { mutateAsync: removeMember } = useApiMutation(api.systemSubnetPoolMemberRemove, {
184193
onSuccess() {
185194
queryClient.invalidateEndpoint('systemSubnetPoolMemberList')
195+
queryClient.invalidateEndpoint('systemSubnetPoolUtilizationView')
186196
},
187197
})
188198
const emptyState = (

app/pages/system/networking/SubnetPoolsPage.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import { IpVersionBadge } from '~/components/IpVersionBadge'
2727
import { useQuickActions } from '~/hooks/use-quick-actions'
2828
import { confirmDelete } from '~/stores/confirm-delete'
2929
import { addToast } from '~/stores/toast'
30+
import { SkeletonCell } from '~/table/cells/EmptyCell'
3031
import { makeLinkCell } from '~/table/cells/LinkCell'
3132
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
3233
import { Columns } from '~/table/columns/common'
3334
import { useQueryTable } from '~/table/QueryTable'
35+
import { UtilizationFraction } from '~/ui/lib/BigNum'
3436
import { CreateLink } from '~/ui/lib/CreateButton'
3537
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3638
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -49,10 +51,18 @@ const EmptyState = () => (
4951
/>
5052
)
5153

54+
function UtilizationCell({ pool }: { pool: string }) {
55+
const { data } = useQuery(q(api.systemSubnetPoolUtilizationView, { path: { pool } }))
56+
if (!data) return <SkeletonCell />
57+
return (
58+
<div>
59+
<UtilizationFraction {...data} />
60+
</div>
61+
)
62+
}
63+
5264
const colHelper = createColumnHelper<SubnetPool>()
5365

54-
// TODO: add utilization column once Nexus endpoint is implemented
55-
// https://github.com/oxidecomputer/omicron/issues/10109
5666
const staticColumns = [
5767
colHelper.accessor('name', {
5868
cell: makeLinkCell((pool) => pb.subnetPool({ subnetPool: pool })),
@@ -62,6 +72,10 @@ const staticColumns = [
6272
header: 'Version',
6373
cell: (info) => <IpVersionBadge ipVersion={info.getValue()} />,
6474
}),
75+
colHelper.display({
76+
header: 'Addresses remaining',
77+
cell: (info) => <UtilizationCell pool={info.row.original.name} />,
78+
}),
6579
colHelper.accessor('timeCreated', Columns.timeCreated),
6680
]
6781

app/ui/lib/BigNum.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,20 @@ export function BigNum({ num, className }: { num: number | bigint; className?: s
2323

2424
return <Tooltip content={num.toLocaleString()}>{inner}</Tooltip>
2525
}
26+
27+
/** Display `remaining / capacity` with BigNum formatting. */
28+
export function UtilizationFraction({
29+
remaining,
30+
capacity,
31+
}: {
32+
remaining: number | bigint
33+
capacity: number | bigint
34+
}) {
35+
return (
36+
<>
37+
<BigNum className="text-raise" num={remaining} />
38+
{' / '}
39+
<BigNum className="text-secondary" num={capacity} />
40+
</>
41+
)
42+
}

0 commit comments

Comments
 (0)