diff --git a/app/components/IpPoolDetailSideModal.tsx b/app/components/IpPoolDetailSideModal.tsx
new file mode 100644
index 000000000..90f044b94
--- /dev/null
+++ b/app/components/IpPoolDetailSideModal.tsx
@@ -0,0 +1,51 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * Copyright Oxide Computer Company
+ */
+import { type IpPool } from '@oxide/api'
+import { IpGlobal16Icon } from '@oxide/design-system/icons/react'
+import { Badge } from '@oxide/design-system/ui'
+
+import { ReadOnlySideModalForm } from '~/components/form/ReadOnlySideModalForm'
+import { IpVersionBadge } from '~/components/IpVersionBadge'
+import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
+import { PropertiesTable } from '~/ui/lib/PropertiesTable'
+import { ResourceLabel } from '~/ui/lib/SideModal'
+import { docLinks } from '~/util/links'
+
+type IpPoolDetailSideModalProps = {
+ pool: IpPool
+ onDismiss: () => void
+}
+
+export function IpPoolDetailSideModal({ pool, onDismiss }: IpPoolDetailSideModalProps) {
+ return (
+
+ {pool.name}
+
+ }
+ >
+
+
+
+
+
+
+
+ {pool.poolType}
+
+
+
+
+
+
+ )
+}
diff --git a/app/forms/floating-ip-edit.tsx b/app/forms/floating-ip-edit.tsx
index 960f2e71e..02b522ed7 100644
--- a/app/forms/floating-ip-edit.tsx
+++ b/app/forms/floating-ip-edit.tsx
@@ -107,7 +107,7 @@ export default function EditFloatingIpSideModalForm() {
-
+
diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx
index 5d5ac415d..19e382107 100644
--- a/app/pages/project/vpcs/internet-gateway-edit.tsx
+++ b/app/pages/project/vpcs/internet-gateway-edit.tsx
@@ -173,7 +173,7 @@ export default function EditInternetGatewayForm() {
{gatewayIpPool.name}
-
+
))
diff --git a/app/table/cells/IpPoolCell.tsx b/app/table/cells/IpPoolCell.tsx
index fc234191b..7a383e178 100644
--- a/app/table/cells/IpPoolCell.tsx
+++ b/app/table/cells/IpPoolCell.tsx
@@ -6,33 +6,47 @@
* Copyright Oxide Computer Company
*/
import { useQuery } from '@tanstack/react-query'
+import { useState } from 'react'
import { api, qErrorsAllowed } from '~/api'
-import { Tooltip } from '~/ui/lib/Tooltip'
+import { IpPoolDetailSideModal } from '~/components/IpPoolDetailSideModal'
import { EmptyCell, SkeletonCell } from './EmptyCell'
+import { ButtonCell } from './LinkCell'
-export const IpPoolCell = ({ ipPoolId }: { ipPoolId: string }) => {
- const { data: result } = useQuery(
- qErrorsAllowed(
- api.ipPoolView,
- { path: { pool: ipPoolId } },
- {
- errorsExpected: {
- explanation: 'the referenced IP pool may have been deleted.',
- statusCode: 404,
- },
- }
- )
+const ipPoolQuery = (ipPoolId: string) =>
+ qErrorsAllowed(
+ api.ipPoolView,
+ { path: { pool: ipPoolId } },
+ {
+ errorsExpected: {
+ explanation: 'the referenced IP pool may have been deleted.',
+ statusCode: 404,
+ },
+ }
)
+
+type IpPoolCellProps = {
+ ipPoolId: string
+ /** Show the IP pool detail sidebar on click. Defaults to true. Pass false to render as plain text. */
+ showPoolInfo?: boolean
+}
+
+export const IpPoolCell = ({ ipPoolId, showPoolInfo = true }: IpPoolCellProps) => {
+ const [showDetail, setShowDetail] = useState(false)
+ const { data: result } = useQuery(ipPoolQuery(ipPoolId))
if (!result) return
// Defensive: the error case should never happen in practice. It should not be
// possible for a resource to reference a pool without that pool existing.
if (result.type === 'error') return
const pool = result.data
+ if (!showPoolInfo) return <>{pool.name}>
return (
-
- {pool.name}
-
+ <>
+ setShowDetail(true)}>{pool.name}
+ {showDetail && (
+ setShowDetail(false)} />
+ )}
+ >
)
}
diff --git a/test/e2e/floating-ip-create.e2e.ts b/test/e2e/floating-ip-create.e2e.ts
index c33bfeef4..44eae2767 100644
--- a/test/e2e/floating-ip-create.e2e.ts
+++ b/test/e2e/floating-ip-create.e2e.ts
@@ -50,6 +50,23 @@ test('can create a floating IP', async ({ page }) => {
})
})
+test('can view IP pool details from floating IP table', async ({ page }) => {
+ await page.goto(floatingIpsPage)
+
+ // cola-float is in ip-pool-1; click the pool cell to open the detail modal
+ const row = page.getByRole('row', { name: /cola-float/ })
+ await row.getByRole('button', { name: 'ip-pool-1' }).click()
+
+ const dialog = page.getByRole('dialog', { name: 'IP pool details' })
+ await expect(dialog).toBeVisible()
+ await expect(dialog.getByText('public IPs')).toBeVisible()
+ await expect(dialog.getByText('v4')).toBeVisible()
+ await expect(dialog.getByText('unicast')).toBeVisible()
+
+ await page.getByRole('contentinfo').getByRole('button', { name: 'Close' }).click()
+ await expect(dialog).toBeHidden()
+})
+
test('can detach and attach a floating IP', async ({ page }) => {
// check floating IP is visible on instance detail
await page.goto('/projects/mock-project/instances/db1')