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')