diff --git a/app/forms/external-subnet-edit.tsx b/app/forms/external-subnet-edit.tsx index 0efa2622c..5e43b0556 100644 --- a/app/forms/external-subnet-edit.tsx +++ b/app/forms/external-subnet-edit.tsx @@ -101,9 +101,7 @@ export default function EditExternalSubnetSideModalForm() { submitError={editExternalSubnet.error} > - - - + {subnet.subnet} diff --git a/app/forms/firewall-rules-edit.tsx b/app/forms/firewall-rules-edit.tsx index 0e7c0c590..1998b279d 100644 --- a/app/forms/firewall-rules-edit.tsx +++ b/app/forms/firewall-rules-edit.tsx @@ -27,6 +27,8 @@ import { useVpcSelector, } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { ALL_ISH } from '~/util/consts' import { invariant } from '~/util/invariant' import { pb } from '~/util/path-builder' @@ -134,6 +136,10 @@ export default function EditFirewallRuleForm() { loading={updateRules.isPending} submitError={updateRules.error} > + + + + - - - + @@ -113,6 +112,7 @@ export default function EditFloatingIpSideModalForm() { + diff --git a/app/forms/idp/edit.tsx b/app/forms/idp/edit.tsx index 2f02e2723..ebec9d663 100644 --- a/app/forms/idp/edit.tsx +++ b/app/forms/idp/edit.tsx @@ -56,10 +56,9 @@ export default function EditIdpSideModalForm() { } > - - - + + diff --git a/app/forms/image-from-snapshot.tsx b/app/forms/image-from-snapshot.tsx index b6f7767e8..012548590 100644 --- a/app/forms/image-from-snapshot.tsx +++ b/app/forms/image-from-snapshot.tsx @@ -25,6 +25,7 @@ import { HL } from '~/components/HL' import { titleCrumb } from '~/hooks/use-crumbs' import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' @@ -97,6 +98,7 @@ export default function CreateImageFromSnapshotSideModalForm() { {formatBytes(data.size).label} + diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index 15c6b7505..8f1a43750 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -18,7 +18,9 @@ import { HL } from '~/components/HL' import { makeCrumb } from '~/hooks/use-crumbs' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -73,6 +75,10 @@ export default function EditIpPoolSideModalForm() { loading={editPool.isPending} submitError={editPool.error} > + + + + diff --git a/app/forms/network-interface-edit.tsx b/app/forms/network-interface-edit.tsx index cbc7ef6d0..f0caaa589 100644 --- a/app/forms/network-interface-edit.tsx +++ b/app/forms/network-interface-edit.tsx @@ -24,11 +24,14 @@ import { SideModalForm } from '~/components/form/SideModalForm' import { HL } from '~/components/HL' import { useInstanceSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { SubnetNameFromId } from '~/table/cells/SubnetNameCell' +import { CopyableIp } from '~/ui/lib/CopyableIp' import { FormDivider } from '~/ui/lib/Divider' import { FieldLabel } from '~/ui/lib/FieldLabel' import { Message } from '~/ui/lib/Message' import { ClearAndAddButtons, MiniTable } from '~/ui/lib/MiniTable' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { HintLink, TextInputHint } from '~/ui/lib/TextInput' import { KEYS } from '~/ui/util/keys' import { parseIpNet, validateIpNet } from '~/util/ip' @@ -36,6 +39,16 @@ import { docLinks, links } from '~/util/links' const transitIpTableColumns = [{ header: 'Transit IPs', cell: (ip: string) => ip }] +// IP addresses aren't editable — a new interface must be created to change them +// — so surface them as read-only metadata. A NIC has a v4 address, a v6 +// address, or both, depending on its stack type. +const privateIps = (ipStack: InstanceNetworkInterface['ipStack']) => + match(ipStack) + .with({ type: 'v4' }, ({ value }) => ({ v4: value.ip, v6: undefined })) + .with({ type: 'v6' }, ({ value }) => ({ v4: undefined, v6: value.ip })) + .with({ type: 'dual_stack' }, ({ value }) => ({ v4: value.v4.ip, v6: value.v6.ip })) + .exhaustive() + type EditNetworkInterfaceFormProps = { editing: InstanceNetworkInterface onDismiss: () => void @@ -46,6 +59,7 @@ export function EditNetworkInterfaceForm({ editing, }: EditNetworkInterfaceFormProps) { const instanceSelector = useInstanceSelector() + const ips = privateIps(editing.ipStack) const editNetworkInterface = useApiMutation(api.instanceNetworkInterfaceUpdate, { onSuccess(nic) { @@ -113,6 +127,24 @@ export function EditNetworkInterfaceForm({ loading={editNetworkInterface.isPending} submitError={editNetworkInterface.error} > + + + {ips.v4 && ( + + + + )} + {ips.v6 && ( + + + + )} + {editing.mac} + + + + + diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 43144736a..00dea1902 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -17,7 +17,9 @@ import { HL } from '~/components/HL' import { titleCrumb } from '~/hooks/use-crumbs' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -68,6 +70,10 @@ export default function EditProjectSideModalForm() { loading={editProject.isPending} submitError={editProject.error} > + + + + diff --git a/app/forms/ssh-key-edit.tsx b/app/forms/ssh-key-edit.tsx index 77408c1ee..210c986cb 100644 --- a/app/forms/ssh-key-edit.tsx +++ b/app/forms/ssh-key-edit.tsx @@ -56,9 +56,7 @@ export default function EditSSHKeySideModalForm() { } > - - - + diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 8b5bcb61d..8bc8415d7 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -33,6 +33,7 @@ import { getVpcSubnetSelector, useVpcSubnetSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -95,6 +96,12 @@ export default function EditSubnetForm() { loading={updateSubnet.isPending} submitError={updateSubnet.error} > + + + {subnet.ipv4Block} + {subnet.ipv6Block} + + diff --git a/app/forms/subnet-pool-edit.tsx b/app/forms/subnet-pool-edit.tsx index 21049c539..1d5c860ae 100644 --- a/app/forms/subnet-pool-edit.tsx +++ b/app/forms/subnet-pool-edit.tsx @@ -18,7 +18,9 @@ import { HL } from '~/components/HL' import { makeCrumb } from '~/hooks/use-crumbs' import { getSubnetPoolSelector, useSubnetPoolSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -72,6 +74,10 @@ export default function EditSubnetPoolSideModalForm() { loading={editPool.isPending} submitError={editPool.error} > + + + + diff --git a/app/forms/vpc-edit.tsx b/app/forms/vpc-edit.tsx index a2e5c60e1..a286c9b73 100644 --- a/app/forms/vpc-edit.tsx +++ b/app/forms/vpc-edit.tsx @@ -17,7 +17,9 @@ import { HL } from '~/components/HL' import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -75,6 +77,10 @@ export default function EditVpcSideModalForm() { loading={editVpc.isPending} submitError={editVpc.error} > + + + + diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 4feccb194..42720d8ad 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -16,6 +16,7 @@ import { usePrefetchedQuery, type VpcRouterUpdate, } from '@oxide/api' +import { Badge } from '@oxide/design-system/ui' import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' @@ -24,7 +25,9 @@ import { HL } from '~/components/HL' import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' import { SideModalFormDocs } from '~/ui/lib/ModalLinks' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' @@ -78,6 +81,13 @@ export default function EditRouterSideModalForm() { loading={editRouter.isPending} submitError={editRouter.error} > + + + + {routerData.kind} + + + diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index f0d2dbd24..546cddc65 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -10,6 +10,7 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router' import * as R from 'remeda' import { api, q, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/api' +import { Badge } from '@oxide/design-system/ui' import { SideModalForm } from '~/components/form/SideModalForm' import { HL } from '~/components/HL' @@ -22,6 +23,8 @@ import { import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcRouterRouteSelector, useVpcRouterRouteSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' +import { FormDivider } from '~/ui/lib/Divider' +import { PropertiesTable } from '~/ui/lib/PropertiesTable' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' @@ -99,6 +102,13 @@ export default function EditRouterRouteSideModalForm() { submitError={updateRouterRoute.error} submitDisabled={disabled ? routeFormMessage.vpcSubnetNotModifiable : undefined} > + + + + {route.kind.replace('_', ' ')} + + + diff --git a/app/pages/project/external-subnets/ExternalSubnetsPage.tsx b/app/pages/project/external-subnets/ExternalSubnetsPage.tsx index 8c3bec28f..c52b9b69d 100644 --- a/app/pages/project/external-subnets/ExternalSubnetsPage.tsx +++ b/app/pages/project/external-subnets/ExternalSubnetsPage.tsx @@ -7,7 +7,7 @@ */ import { useQuery } from '@tanstack/react-query' import { createColumnHelper } from '@tanstack/react-table' -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useForm } from 'react-hook-form' import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router' @@ -34,6 +34,7 @@ import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { InstanceLink } from '~/table/cells/InstanceLinkCell' +import { makeLinkCell } from '~/table/cells/LinkCell' import { SubnetPoolCell } from '~/table/cells/SubnetPoolCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' @@ -77,7 +78,6 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { const colHelper = createColumnHelper() const staticCols = [ - colHelper.accessor('name', {}), colHelper.accessor('description', Columns.description), colHelper.accessor('subnet', { header: 'Subnet', @@ -189,7 +189,20 @@ export default function ExternalSubnetsPage() { [deleteExternalSubnet, externalSubnetDetach, navigate, project, instances] ) - const columns = useColsWithActions(staticCols, makeActions) + // name column links to the edit side modal; defined here (not in staticCols) + // because the link needs the project from the route + const cols = useMemo( + () => [ + colHelper.accessor('name', { + cell: makeLinkCell((externalSubnet) => + pb.externalSubnetEdit({ project, externalSubnet }) + ), + }), + ...staticCols, + ], + [project] + ) + const columns = useColsWithActions(cols, makeActions) const { table } = useQueryTable({ query: subnetList(project), columns, diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 8339f6777..fa1ae8972 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -7,7 +7,7 @@ */ import { useQuery } from '@tanstack/react-query' import { createColumnHelper } from '@tanstack/react-table' -import { useCallback, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useForm } from 'react-hook-form' import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router' @@ -35,6 +35,7 @@ import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' import { InstanceLink } from '~/table/cells/InstanceLinkCell' import { IpPoolCell, ipPoolErrorsAllowedQuery } from '~/table/cells/IpPoolCell' +import { makeLinkCell } from '~/table/cells/LinkCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { useQueryTable } from '~/table/QueryTable' @@ -90,7 +91,6 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { const colHelper = createColumnHelper() const staticCols = [ - colHelper.accessor('name', {}), colHelper.accessor('description', Columns.description), colHelper.accessor('ip', { header: 'IP address', @@ -204,7 +204,18 @@ export default function FloatingIpsPage() { [deleteFloatingIp, floatingIpDetach, navigate, project, instances] ) - const columns = useColsWithActions(staticCols, makeActions) + // name column links to the edit side modal; defined here (not in staticCols) + // because the link needs the project from the route + const cols = useMemo( + () => [ + colHelper.accessor('name', { + cell: makeLinkCell((floatingIp) => pb.floatingIpEdit({ project, floatingIp })), + }), + ...staticCols, + ], + [project] + ) + const columns = useColsWithActions(cols, makeActions) const { table } = useQueryTable({ query: fipList(project), columns, diff --git a/app/pages/project/instances/NetworkingTab.tsx b/app/pages/project/instances/NetworkingTab.tsx index 9f651adc4..bc622861e 100644 --- a/app/pages/project/instances/NetworkingTab.tsx +++ b/app/pages/project/instances/NetworkingTab.tsx @@ -52,6 +52,7 @@ import { DescriptionCell } from '~/table/cells/DescriptionCell' import { EmptyCell, SkeletonCell } from '~/table/cells/EmptyCell' import { IpPoolCell } from '~/table/cells/IpPoolCell' import { LinkCell } from '~/table/cells/LinkCell' +import { SubnetNameFromId } from '~/table/cells/SubnetNameCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { Columns } from '~/table/columns/common' import { Table } from '~/table/Table' @@ -88,18 +89,6 @@ const VpcNameFromId = ({ value }: { value: string }) => { return {vpc.name} } -const SubnetNameFromId = ({ value }: { value: string }) => { - const { data: subnet, isError } = useQuery( - q(api.vpcSubnetView, { path: { subnet: value } }, { throwOnError: false }) - ) - - // same deal as VPC: probably not possible but let's be safe - if (isError) return Deleted - if (!subnet) return // loading - - return {subnet.name} -} - const NonFloatingEmptyCell = ({ kind }: { kind: 'snat' | 'ephemeral' }) => ( , + cell: (info) => , }), colHelper.display({ id: 'transitIps', diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index 5dff2e109..43210dbff 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -7,7 +7,7 @@ */ import { createColumnHelper } from '@tanstack/react-table' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router' import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/react' @@ -34,6 +34,7 @@ import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmAction } from '~/stores/confirm-action' import { addToast } from '~/stores/toast' +import { LinkCell } from '~/table/cells/LinkCell' import { TypeValueCell } from '~/table/cells/TypeValueCell' import { useColsWithActions, type MenuAction } from '~/table/columns/action-col' import { useQueryTable } from '~/table/QueryTable' @@ -73,6 +74,8 @@ const routeTypes = { vpc: 'VPC', } +const routerRoutesColHelper = createColumnHelper() + // All will have a type and a value except `Drop`, which only has a type const RouterRouteTypeValueBadge = ({ type, @@ -111,23 +114,33 @@ export default function RouterPage() { ) const navigate = useNavigate() - const routerRoutesColHelper = createColumnHelper() - - const routerRoutesStaticCols = [ - routerRoutesColHelper.accessor('name', { header: 'Name' }), - routerRoutesColHelper.accessor('kind', { - header: 'Kind', - cell: (info) => {info.getValue().replace('_', ' ')}, - }), - routerRoutesColHelper.accessor('destination', { - header: 'Destination', - cell: (info) => , - }), - routerRoutesColHelper.accessor('target', { - header: 'Target', - cell: (info) => , - }), - ] + const routerRoutesStaticCols = useMemo( + () => [ + routerRoutesColHelper.accessor('name', { + header: 'Name', + cell: (info) => ( + + {info.getValue()} + + ), + }), + routerRoutesColHelper.accessor('kind', { + header: 'Kind', + cell: (info) => {info.getValue().replace('_', ' ')}, + }), + routerRoutesColHelper.accessor('destination', { + header: 'Destination', + cell: (info) => , + }), + routerRoutesColHelper.accessor('target', { + header: 'Target', + cell: (info) => , + }), + ], + [project, vpc, router] + ) const makeRangeActions = useCallback( (routerRoute: RouterRoute): MenuAction[] => [ diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx index 5d5ac415d..4c54c8bb8 100644 --- a/app/pages/project/vpcs/internet-gateway-edit.tsx +++ b/app/pages/project/vpcs/internet-gateway-edit.tsx @@ -128,8 +128,8 @@ export default function EditInternetGatewayForm() { /> - +
Internet gateway IP address diff --git a/app/table/cells/SubnetNameCell.tsx b/app/table/cells/SubnetNameCell.tsx new file mode 100644 index 000000000..85710e328 --- /dev/null +++ b/app/table/cells/SubnetNameCell.tsx @@ -0,0 +1,25 @@ +/* + * 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 { useQuery } from '@tanstack/react-query' + +import { api, q } from '@oxide/api' +import { Badge } from '@oxide/design-system/ui' + +import { SkeletonCell } from './EmptyCell' + +/** Resolve a subnet ID to its name. Callers must ensure the query is enabled. */ +export const SubnetNameFromId = ({ subnetId }: { subnetId: string }) => { + const { data: subnet, isError } = useQuery( + q(api.vpcSubnetView, { path: { subnet: subnetId } }, { throwOnError: false }) + ) + // probably not possible but let's be safe + if (isError) return Deleted + if (!subnet) return // loading + return {subnet.name} +} diff --git a/app/ui/lib/PropertiesTable.tsx b/app/ui/lib/PropertiesTable.tsx index 9f7f96303..e7f7a5b69 100644 --- a/app/ui/lib/PropertiesTable.tsx +++ b/app/ui/lib/PropertiesTable.tsx @@ -37,6 +37,7 @@ export function PropertiesTable({ PropertiesTable.DateRow, PropertiesTable.SizeRow, PropertiesTable.CopyableRow, + PropertiesTable.ResourceRows, ]), 'PropertiesTable only accepts specific Row components as children' ) @@ -122,3 +123,20 @@ PropertiesTable.CopyableRow = ({ label, text }: { label: string; text: string }) ) + +/** + * The ID + created/updated timestamps every API resource carries. Renders the + * three rows nearly every edit side modal opens with; pass additional + * resource-specific `PropertiesTable.*` rows as siblings after it. + */ +PropertiesTable.ResourceRows = ({ + resource, +}: { + resource: { id: string; timeCreated: Date; timeModified: Date } +}) => ( + <> + + + + +) diff --git a/test/e2e/external-subnets.e2e.ts b/test/e2e/external-subnets.e2e.ts index 3aa1133d6..dc343c0ff 100644 --- a/test/e2e/external-subnets.e2e.ts +++ b/test/e2e/external-subnets.e2e.ts @@ -128,6 +128,13 @@ test('can create an external subnet with explicit CIDR', async ({ page }) => { }) }) +test('clicking the name opens the edit side modal', async ({ page }) => { + await page.goto(externalSubnetsPage) + await page.getByRole('link', { name: 'web-subnet' }).click() + await expect(page).toHaveURL(`${externalSubnetsPage}/web-subnet/edit`) + await expect(page.getByRole('heading', { name: /Edit external subnet/ })).toBeVisible() +}) + test('can update an external subnet', async ({ page }) => { await page.goto(externalSubnetsPage) await clickRowAction(page, 'web-subnet', 'Edit') diff --git a/test/e2e/floating-ip-update.e2e.ts b/test/e2e/floating-ip-update.e2e.ts index 7aeb8498d..d60eeb486 100644 --- a/test/e2e/floating-ip-update.e2e.ts +++ b/test/e2e/floating-ip-update.e2e.ts @@ -50,6 +50,13 @@ test('can update a floating IP', async ({ page }) => { await expectToast(page, `Floating IP ${updatedName} updated`) }) +test('clicking the name opens the edit side modal', async ({ page }) => { + await page.goto(floatingIpsPage) + await page.getByRole('link', { name: originalName }).click() + await expect(page).toHaveURL(`${floatingIpsPage}/${originalName}/edit`) + await expectVisible(page, expectedFormElements) +}) + // Make sure that it still works even if the name doesn't change test('can update *just* the floating IP description', async ({ page }) => { // Go to the edit page for the original floating IP diff --git a/test/e2e/vpcs.e2e.ts b/test/e2e/vpcs.e2e.ts index 6df00b31b..1694c6a67 100644 --- a/test/e2e/vpcs.e2e.ts +++ b/test/e2e/vpcs.e2e.ts @@ -280,6 +280,14 @@ test('create router route', async ({ page }) => { }) }) +test('clicking a route name opens the edit modal', async ({ page }) => { + const router = '/projects/mock-project/vpcs/mock-vpc/routers/mock-custom-router' + await page.goto(router) + await page.getByRole('link', { name: 'drop-local' }).click() + await expect(page).toHaveURL(`${router}/routes/drop-local/edit`) + await expect(page.getByRole('dialog', { name: 'Edit route' })).toBeVisible() +}) + test('edit and delete router route', async ({ page }) => { await page.goto('/projects/mock-project/vpcs/mock-vpc/routers/mock-custom-router')