Skip to content

Commit

Permalink
Merge branch 'main' into silo-quotas
Browse files Browse the repository at this point in the history
  • Loading branch information
david-crespo committed Aug 15, 2024
2 parents 8ac826f + bb53f1b commit c3db0ca
Show file tree
Hide file tree
Showing 35 changed files with 1,321 additions and 45 deletions.
2 changes: 1 addition & 1 deletion OMICRON_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9b595e985721f8ab83d13c4dc4f257cbf8ac525c
eeb723c6a0d727f016ca947541ac85c029801c06
6 changes: 6 additions & 0 deletions app/api/__generated__/Api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/api/__generated__/OMICRON_VERSION

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion app/api/__generated__/validate.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion app/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,14 @@ export const getUsePrefetchedApiQuery =
})
invariant(
result.data,
`Expected query to be prefetched. Key: ${JSON.stringify(queryKey)}`
`Expected query to be prefetched.
Key: ${JSON.stringify(queryKey)}
Ensure the following:
• loader is running
• query matches in both the loader and the component
• request isn't erroring-out server-side (check the Networking tab)
• mock API endpoint is implemented in handlers.ts
`
)
// TS infers non-nullable on a freestanding variable, but doesn't like to do
// it on a property. So we give it a hint
Expand Down
2 changes: 2 additions & 0 deletions app/api/path-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export type SiloImage = { image?: string }
export type NetworkInterface = Merge<Instance, { interface?: string }>
export type Snapshot = Merge<Project, { snapshot?: string }>
export type Vpc = Merge<Project, { vpc?: string }>
export type VpcRouter = Merge<Vpc, { router?: string }>
export type VpcRouterRoute = Merge<VpcRouter, { route?: string }>
export type VpcSubnet = Merge<Vpc, { subnet?: string }>
export type FirewallRule = Merge<Vpc, { rule?: string }>
export type Silo = { silo?: string }
Expand Down
6 changes: 1 addition & 5 deletions app/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* Copyright Oxide Computer Company
*/
import React from 'react'
import { useNavigate } from 'react-router-dom'

import { navToLogin, useApiMutation } from '@oxide/api'
import { DirectionDownIcon, Profile16Icon } from '@oxide/design-system/icons/react'
Expand All @@ -17,7 +16,6 @@ import { DropdownMenu } from '~/ui/lib/DropdownMenu'
import { pb } from '~/util/path-builder'

export function TopBar({ children }: { children: React.ReactNode }) {
const navigate = useNavigate()
const logout = useApiMutation('logout', {
onSuccess: () => navToLogin({ includeCurrent: false }),
})
Expand Down Expand Up @@ -63,9 +61,7 @@ export function TopBar({ children }: { children: React.ReactNode }) {
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end" sideOffset={8}>
<DropdownMenu.Item onSelect={() => navigate(pb.profile())}>
Settings
</DropdownMenu.Item>
<DropdownMenu.LinkItem to={pb.profile()}>Settings</DropdownMenu.LinkItem>
{loggedIn ? (
<DropdownMenu.Item onSelect={() => logout.mutate({})}>
Sign out
Expand Down
55 changes: 53 additions & 2 deletions app/components/TopBarPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import {
Success12Icon,
} from '@oxide/design-system/icons/react'

import { useInstanceSelector, useIpPoolSelector, useSiloSelector } from '~/hooks'
import {
useInstanceSelector,
useIpPoolSelector,
useSiloSelector,
useVpcRouterSelector,
useVpcSelector,
} from '~/hooks'
import { useCurrentUser } from '~/layouts/AuthenticatedLayout'
import { PAGE_SIZE } from '~/table/QueryTable'
import { Button } from '~/ui/lib/Button'
Expand Down Expand Up @@ -229,7 +235,7 @@ export function SiloPicker() {
export function IpPoolPicker() {
// picker only shows up when a pool is in scope
const { pool: poolName } = useIpPoolSelector()
const { data } = useApiQuery('ipPoolList', { query: { limit: 10 } })
const { data } = useApiQuery('ipPoolList', { query: { limit: PAGE_SIZE } })
const items = (data?.items || []).map((pool) => ({
label: pool.name,
to: pb.ipPool({ pool: pool.name }),
Expand All @@ -246,6 +252,51 @@ export function IpPoolPicker() {
)
}

/** Used when drilling down into a VPC from the Silo view. */
export function VpcPicker() {
// picker only shows up when a VPC is in scope
const { project, vpc } = useVpcSelector()
const { data } = useApiQuery('vpcList', { query: { project, limit: PAGE_SIZE } })
const items = (data?.items || []).map((v) => ({
label: v.name,
to: pb.vpc({ project, vpc: v.name }),
}))

return (
<TopBarPicker
aria-label="Switch VPC"
category="VPC"
current={vpc}
items={items}
noItemsText="No VPCs found"
to={pb.vpc({ project, vpc })}
/>
)
}

/** Used when drilling down into a VPC Router from the Silo view. */
export function VpcRouterPicker() {
// picker only shows up when a router is in scope
const { project, vpc, router } = useVpcRouterSelector()
const { data } = useApiQuery('vpcRouterList', {
query: { project, vpc, limit: PAGE_SIZE },
})
const items = (data?.items || []).map((r) => ({
label: r.name,
to: pb.vpcRouter({ vpc, project, router: r.name }),
}))

return (
<TopBarPicker
aria-label="Switch router"
category="router"
current={router}
items={items}
noItemsText="No routers found"
/>
)
}

const NoProjectLogo = () => (
<div className="flex h-[34px] w-[34px] items-center justify-center rounded text-secondary bg-secondary">
<Folder16Icon />
Expand Down
2 changes: 1 addition & 1 deletion app/forms/subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function EditSubnetForm() {
},
})

const defaultValues = R.pick(subnet, ['name', 'description']) satisfies VpcSubnetUpdate
const defaultValues: VpcSubnetUpdate = R.pick(subnet, ['name', 'description'])

const form = useForm({ defaultValues })

Expand Down
55 changes: 55 additions & 0 deletions app/forms/vpc-router-create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { useNavigate } from 'react-router-dom'

import { useApiMutation, useApiQueryClient, type VpcRouterCreate } from '@oxide/api'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { useForm, useVpcSelector } from '~/hooks'
import { addToast } from '~/stores/toast'
import { pb } from '~/util/path-builder'

const defaultValues: VpcRouterCreate = {
name: '',
description: '',
}

export function CreateRouterSideModalForm() {
const queryClient = useApiQueryClient()
const vpcSelector = useVpcSelector()
const navigate = useNavigate()

const onDismiss = () => navigate(pb.vpcRouters(vpcSelector))

const createRouter = useApiMutation('vpcRouterCreate', {
onSuccess() {
queryClient.invalidateQueries('vpcRouterList')
addToast({ content: 'Your router has been created' })
onDismiss()
},
})

const form = useForm({ defaultValues })

return (
<SideModalForm
form={form}
formType="create"
resourceName="router"
onDismiss={onDismiss}
onSubmit={(body) => createRouter.mutate({ query: vpcSelector, body })}
loading={createRouter.isPending}
submitError={createRouter.error}
>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
</SideModalForm>
)
}
87 changes: 87 additions & 0 deletions app/forms/vpc-router-edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 {
useNavigate,
type LoaderFunctionArgs,
type NavigateFunction,
} from 'react-router-dom'

import {
apiQueryClient,
useApiMutation,
useApiQueryClient,
usePrefetchedApiQuery,
type VpcRouterUpdate,
} from '@oxide/api'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { getVpcRouterSelector, useForm, useVpcRouterSelector } from '~/hooks'
import { addToast } from '~/stores/toast'
import { pb } from '~/util/path-builder'

EditRouterSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => {
const { router, project, vpc } = getVpcRouterSelector(params)
await apiQueryClient.prefetchQuery('vpcRouterView', {
path: { router },
query: { project, vpc },
})
return null
}

export function EditRouterSideModalForm() {
const queryClient = useApiQueryClient()
const routerSelector = useVpcRouterSelector()
const { project, vpc, router } = routerSelector
const { data: routerData } = usePrefetchedApiQuery('vpcRouterView', {
path: { router },
query: { project, vpc },
})
const navigate = useNavigate()

const onDismiss = (navigate: NavigateFunction) => {
navigate(pb.vpcRouters({ project, vpc }))
}

const editRouter = useApiMutation('vpcRouterUpdate', {
onSuccess() {
queryClient.invalidateQueries('vpcRouterList')
addToast({ content: 'Your router has been updated' })
navigate(pb.vpcRouters({ project, vpc }))
},
})

const defaultValues: VpcRouterUpdate = {
name: router,
description: routerData.description,
}

const form = useForm({ defaultValues })

return (
<SideModalForm
form={form}
formType="edit"
resourceName="router"
onDismiss={() => onDismiss(navigate)}
onSubmit={(body) =>
editRouter.mutate({
path: { router },
query: { project, vpc },
body,
})
}
loading={editRouter.isPending}
submitError={editRouter.error}
>
<NameField name="name" control={form.control} />
<DescriptionField name="description" control={form.control} />
</SideModalForm>
)
}
Loading

0 comments on commit c3db0ca

Please sign in to comment.