Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choose custom router on subnet create/edit forms #2393

Merged
merged 15 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/components/form/fields/useItemsList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 { useMemo } from 'react'

import { useApiQuery } from '~/api'
import { useVpcSelector } from '~/hooks'

export const useCustomRouterItems = () => {
const vpcSelector = useVpcSelector()
const routers = useApiQuery('vpcRouterList', { query: { ...vpcSelector } })
const routerItems = useMemo(() => {
return (
routers?.data?.items
.filter((item) => item.kind === 'custom')
.map((router) => ({ value: router.id, label: router.name })) || []
)
}, [routers])

return { isLoading: routers.isLoading, items: routerItems }
}
13 changes: 13 additions & 0 deletions app/forms/subnet-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import { useNavigate } from 'react-router-dom'
import { useApiMutation, useApiQueryClient, type VpcSubnetCreate } from '@oxide/api'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { ListboxField } from '~/components/form/fields/ListboxField'
import { NameField } from '~/components/form/fields/NameField'
import { TextField } from '~/components/form/fields/TextField'
import { useCustomRouterItems } from '~/components/form/fields/useItemsList'
import { SideModalForm } from '~/components/form/SideModalForm'
import { useForm, useVpcSelector } from '~/hooks'
import { FormDivider } from '~/ui/lib/Divider'
import { pb } from '~/util/path-builder'

const defaultValues: VpcSubnetCreate = {
name: '',
customRouter: '',
description: '',
ipv4Block: '',
}
Expand All @@ -38,6 +41,7 @@ export function CreateSubnetForm() {
})

const form = useForm({ defaultValues })
const { isLoading, items } = useCustomRouterItems()

return (
<SideModalForm
Expand All @@ -52,6 +56,15 @@ export function CreateSubnetForm() {
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<FormDivider />
<ListboxField
label="Custom router"
name="customRouter"
placeholder="Select a custom router"
isLoading={isLoading}
items={items}
control={form.control}
/>
<FormDivider />
<TextField name="ipv4Block" label="IPv4 block" required control={form.control} />
<TextField name="ipv6Block" label="IPv6 block" control={form.control} />
</SideModalForm>
Expand Down
20 changes: 18 additions & 2 deletions app/forms/subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* Copyright Oxide Computer Company
*/
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
import * as R from 'remeda'

import {
apiQueryClient,
Expand All @@ -17,9 +16,12 @@ import {
} from '@oxide/api'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { ListboxField } from '~/components/form/fields/ListboxField'
import { NameField } from '~/components/form/fields/NameField'
import { useCustomRouterItems } from '~/components/form/fields/useItemsList'
import { SideModalForm } from '~/components/form/SideModalForm'
import { getVpcSubnetSelector, useForm, useVpcSubnetSelector } from '~/hooks'
import { FormDivider } from '~/ui/lib/Divider'
import { pb } from '~/util/path-builder'

EditSubnetForm.loader = async ({ params }: LoaderFunctionArgs) => {
Expand Down Expand Up @@ -50,9 +52,14 @@ export function EditSubnetForm() {
},
})

const defaultValues: VpcSubnetUpdate = R.pick(subnet, ['name', 'description'])
const defaultValues: VpcSubnetUpdate = {
name: subnet.name,
description: subnet.description,
customRouter: subnet.customRouterId,
}

const form = useForm({ defaultValues })
const { isLoading, items } = useCustomRouterItems()

return (
<SideModalForm
Expand All @@ -72,6 +79,15 @@ export function EditSubnetForm() {
>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
<FormDivider />
<ListboxField
label="Custom router"
name="customRouter"
placeholder="Select a custom router"
isLoading={isLoading}
items={items}
control={form.control}
/>
</SideModalForm>
)
}
7 changes: 7 additions & 0 deletions app/pages/project/vpcs/VpcPage/tabs/VpcSubnetsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { getVpcSelector, useVpcSelector } from '~/hooks'
import { confirmDelete } from '~/stores/confirm-delete'
import { makeLinkCell } from '~/table/cells/LinkCell'
import { RouterLinkCell } from '~/table/cells/RouterLinkCell'
import { TwoLineCell } from '~/table/cells/TwoLineCell'
import { getActionsCol, type MenuAction } from '~/table/columns/action-col'
import { Columns } from '~/table/columns/common'
Expand Down Expand Up @@ -75,10 +76,16 @@ export function VpcSubnetsTab() {
colHelper.accessor('name', {
cell: makeLinkCell((subnet) => pb.vpcSubnetsEdit({ ...vpcSelector, subnet })),
}),
colHelper.accessor('description', Columns.description),
colHelper.accessor((vpc) => [vpc.ipv4Block, vpc.ipv6Block] as const, {
header: 'IP Block',
cell: (info) => <TwoLineCell value={[...info.getValue()]} />,
}),
colHelper.accessor('customRouterId', {
header: 'Custom Router',
// RouterLinkCell needed, as we need to convert the customRouterId to the custom router's name
cell: (info) => <RouterLinkCell value={info.getValue()} />,
}),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol(makeActions),
],
Expand Down
36 changes: 36 additions & 0 deletions app/table/cells/RouterLinkCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { useMemo } from 'react'

import { useApiQuery } from '~/api'
import { useVpcSelector } from '~/hooks'
import { Badge } from '~/ui/lib/Badge'
import { pb } from '~/util/path-builder'

import { EmptyCell, SkeletonCell } from './EmptyCell'
import { LinkCell } from './LinkCell'

export const RouterLinkCell = ({ value }: { value?: string }) => {
const { project, vpc } = useVpcSelector()
const { data: subnet, isError } = useApiQuery('vpcRouterView', {
path: { router: value || '' },
query: { project, vpc },
})
return useMemo(() => {
if (!value) return <EmptyCell />
// probably not possible but let’s be safe
if (isError) return <Badge color="neutral">Deleted</Badge>
if (!subnet) return <SkeletonCell /> // loading
return (
<LinkCell to={pb.vpcRouter({ project, vpc, router: subnet.name })}>
{subnet.name}
</LinkCell>
)
}, [value, project, vpc, isError, subnet])
}
9 changes: 8 additions & 1 deletion mock-api/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,7 @@ export const handlers = makeHandlers({
const subnets = db.vpcSubnets.filter((s) => s.vpc_id === vpc.id)
return paginated(query, subnets)
},

vpcSubnetCreate({ body, query }) {
const vpc = lookup.vpc(query)
errIfExists(db.vpcSubnets, { vpc_id: vpc.id, name: body.name })
Expand All @@ -1159,7 +1160,10 @@ export const handlers = makeHandlers({
const newSubnet: Json<Api.VpcSubnet> = {
id: uuid(),
vpc_id: vpc.id,
...body,
name: body.name,
description: body.description || '',
ipv4_block: body.ipv4_block || '',
custom_router_id: body.custom_router || '',
// required in subnet create but not in update, so we need a fallback.
// API says "A random `/64` block will be assigned if one is not
// provided." Our fallback is not random, but it should be good enough.
Expand All @@ -1176,6 +1180,9 @@ export const handlers = makeHandlers({
if (body.name) {
subnet.name = body.name
}
if (body.custom_router) {
subnet.custom_router_id = body.custom_router
}
updateDesc(subnet, body)

return subnet
Expand Down
13 changes: 12 additions & 1 deletion mock-api/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,17 @@ export const customRouter: Json<VpcRouter> = {
kind: 'custom',
}

export const vpcRouters: Json<VpcRouter[]> = [defaultRouter, customRouter]
export const customRouter2: Json<VpcRouter> = {
id: '1ce32917-c940-4d3f-ad97-c62e11901f39',
name: 'mock-custom-router-2',
description: 'another fake custom router',
time_created: new Date(2024, 2, 1).toISOString(),
time_modified: new Date(2024, 2, 2).toISOString(),
vpc_id: vpc.id,
kind: 'custom',
}

export const vpcRouters: Json<VpcRouter[]> = [defaultRouter, customRouter, customRouter2]

const routeBase = {
time_created: '2024-07-11T17:46:21.161086Z',
Expand Down Expand Up @@ -147,6 +157,7 @@ export const vpcSubnet: Json<VpcSubnet> = {
vpc_id: vpc.id,
ipv4_block: '10.1.1.1/24',
ipv6_block: 'fd9b:870a:4245::/64',
custom_router_id: customRouter.id,
}

export const vpcSubnet2: Json<VpcSubnet> = {
Expand Down
Loading