Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 7 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@
{
// default exports are needed in the route modules and the config files,
// but we want to avoid them anywhere else
"files": ["app/pages/**/*", "app/layouts/**/*", "app/forms/**/*", "*.config.ts", "*.config.mjs"],
"files": [
"app/pages/**/*",
"app/layouts/**/*",
"app/forms/**/*",
"*.config.ts",
"*.config.mjs"
],
"rules": {
"import/no-default-export": "off"
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/form/fields/SshKeysField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { usePrefetchedApiQuery } from '@oxide/api'
import { Key16Icon } from '@oxide/design-system/icons/react'

import type { InstanceCreateInput } from '~/forms/instance-create'
import { Component as CreateSSHKeySideModalForm } from '~/forms/ssh-key-create'
import { SSHKeyCreate } from '~/forms/ssh-key-create'
import { Button } from '~/ui/lib/Button'
import { Checkbox } from '~/ui/lib/Checkbox'
import { Divider } from '~/ui/lib/Divider'
Expand Down Expand Up @@ -138,7 +138,7 @@ export function SshKeysField({
</div>
)}
{showAddSshKey && (
<CreateSSHKeySideModalForm
<SSHKeyCreate
onDismiss={() => setShowAddSshKey(false)}
message={
<Message
Expand Down
12 changes: 3 additions & 9 deletions app/forms/disk-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { filesize } from 'filesize'
import { useMemo } from 'react'
import { useController, useForm, type Control } from 'react-hook-form'
import { useNavigate, type NavigateFunction } from 'react-router'

import {
useApiMutation,
Expand Down Expand Up @@ -59,11 +58,7 @@ type CreateSideModalFormProps = {
* the RQ `onSuccess` defined for the mutation.
*/
onSubmit?: (diskCreate: DiskCreate) => void
/**
* Passing navigate is a bit of a hack to be able to do a nav from the routes
* file. The callers that don't need the arg can ignore it.
*/
onDismiss: (navigate: NavigateFunction) => void
onDismiss: () => void
onSuccess?: (disk: Disk) => void
unavailableDiskNames?: string[]
}
Expand All @@ -75,14 +70,13 @@ export function CreateDiskSideModalForm({
unavailableDiskNames = [],
}: CreateSideModalFormProps) {
const queryClient = useApiQueryClient()
const navigate = useNavigate()

const createDisk = useApiMutation('diskCreate', {
onSuccess(data) {
queryClient.invalidateQueries('diskList')
addToast(<>Disk <HL>{data.name}</HL> created</>) // prettier-ignore
onSuccess?.(data)
onDismiss(navigate)
onDismiss()
},
})

Expand Down Expand Up @@ -123,7 +117,7 @@ export function CreateDiskSideModalForm({
form={form}
formType="create"
resourceName="disk"
onDismiss={() => onDismiss(navigate)}
onDismiss={onDismiss}
onSubmit={({ size, ...rest }) => {
const body = { size: size * GiB, ...rest }
if (onSubmit) {
Expand Down
49 changes: 3 additions & 46 deletions app/forms/image-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,21 @@
* Copyright Oxide Computer Company
*/
import { useForm } from 'react-hook-form'
import { useNavigate, type LoaderFunctionArgs } from 'react-router'
import { useNavigate } from 'react-router'

import { apiQueryClient, usePrefetchedApiQuery, type Image } from '@oxide/api'
import { type Image } from '@oxide/api'
import { Images16Icon } from '@oxide/design-system/icons/react'

import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { TextField } from '~/components/form/fields/TextField'
import { SideModalForm } from '~/components/form/SideModalForm'
import {
getProjectImageSelector,
getSiloImageSelector,
useProjectImageSelector,
useSiloImageSelector,
} from '~/hooks/use-params'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
import { ResourceLabel } from '~/ui/lib/SideModal'
import { pb } from '~/util/path-builder'
import { capitalize } from '~/util/str'
import { bytesToGiB } from '~/util/units'

export const ProjectImageEdit = {
loader: async ({ params }: LoaderFunctionArgs) => {
const { project, image } = getProjectImageSelector(params)
await apiQueryClient.prefetchQuery('imageView', { path: { image }, query: { project } })
return null
},
Component: EditProjectImageSideModalForm,
}

export const SiloImageEdit = {
loader: async ({ params }: LoaderFunctionArgs) => {
const { image } = getSiloImageSelector(params)
await apiQueryClient.prefetchQuery('imageView', { path: { image } })
return null
},
Component: EditSiloImageSideModalForm,
}

function EditProjectImageSideModalForm() {
const { project, image } = useProjectImageSelector()
const { data } = usePrefetchedApiQuery('imageView', {
path: { image },
query: { project },
})

const dismissLink = pb.projectImages({ project })
return <EditImageSideModalForm image={data} dismissLink={dismissLink} type="Project" />
}

function EditSiloImageSideModalForm() {
const { image } = useSiloImageSelector()
const { data } = usePrefetchedApiQuery('imageView', { path: { image } })

return <EditImageSideModalForm image={data} dismissLink={pb.siloImages()} type="Silo" />
}

function EditImageSideModalForm({
export function EditImageSideModalForm({
image,
dismissLink,
type,
Expand Down
7 changes: 5 additions & 2 deletions app/forms/image-from-snapshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { NameField } from '~/components/form/fields/NameField'
import { TextField } from '~/components/form/fields/TextField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getProjectSnapshotSelector, useProjectSnapshotSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { PropertiesTable } from '~/ui/lib/PropertiesTable'
Expand All @@ -38,13 +39,15 @@ const defaultValues: Omit<ImageCreate, 'source'> = {
const snapshotView = ({ project, snapshot }: PP.Snapshot) =>
apiq('snapshotView', { path: { snapshot }, query: { project } })

CreateImageFromSnapshotSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => {
export async function clientLoader({ params }: LoaderFunctionArgs) {
const { project, snapshot } = getProjectSnapshotSelector(params)
await queryClient.prefetchQuery(snapshotView({ project, snapshot }))
return null
}

export function CreateImageFromSnapshotSideModalForm() {
export const handle = titleCrumb('Create image from snapshot')

export default function CreateImageFromSnapshotSideModalForm() {
const { snapshot, project } = useProjectSnapshotSelector()
const { data } = usePrefetchedQuery(snapshotView({ project, snapshot }))
const navigate = useNavigate()
Expand Down
6 changes: 4 additions & 2 deletions app/forms/image-upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { NameField } from '~/components/form/fields/NameField'
import { RadioField } from '~/components/form/fields/RadioField'
import { TextField } from '~/components/form/fields/TextField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { titleCrumb } from '~/hooks/use-crumbs'
import { useProjectSelector } from '~/hooks/use-params'
import { Message } from '~/ui/lib/Message'
import { Modal } from '~/ui/lib/Modal'
Expand Down Expand Up @@ -175,11 +176,12 @@ const CHUNK_SIZE_BYTES = 512 * KiB
// TODO: make sure cleanup, cancelEverything, and resetMainFlow are called in
// the right places

Component.displayName = 'ImageCreate'
export const handle = titleCrumb('Upload image')

/**
* Upload an image. Opens a second modal to show upload progress.
*/
export function Component() {
export default function ImageCreate() {
const navigate = useNavigate()
const queryClient = useApiQueryClient()
const { project } = useProjectSelector()
Expand Down
5 changes: 4 additions & 1 deletion app/forms/ip-pool-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { addToast } from '~/stores/toast'
import { Message } from '~/ui/lib/Message'
import { pb } from '~/util/path-builder'
Expand All @@ -23,7 +24,9 @@ const defaultValues: IpPoolCreate = {
description: '',
}

export function CreateIpPoolSideModalForm() {
export const handle = titleCrumb('New IP pool')

export default function CreateIpPoolSideModalForm() {
const navigate = useNavigate()
const queryClient = useApiQueryClient()

Expand Down
8 changes: 5 additions & 3 deletions app/forms/ip-pool-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { makeCrumb } from '~/hooks/use-crumbs'
import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { pb } from '~/util/path-builder'

import { IpPoolVisibilityMessage } from './ip-pool-create'

export async function loader({ params }: LoaderFunctionArgs) {
export async function clientLoader({ params }: LoaderFunctionArgs) {
const { pool } = getIpPoolSelector(params)
await apiQueryClient.prefetchQuery('ipPoolView', { path: { pool } })
return null
}

Component.displayName = 'EditIpPoolSideModalForm'
export function Component() {
export const handle = makeCrumb('Edit IP pool')

export default function EditIpPoolSideModalForm() {
const queryClient = useApiQueryClient()
const navigate = useNavigate()
const poolSelector = useIpPoolSelector()
Expand Down
6 changes: 4 additions & 2 deletions app/forms/ip-pool-range-add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useApiMutation, useApiQueryClient, type IpRange } from '@oxide/api'

import { TextField } from '~/components/form/fields/TextField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { titleCrumb } from '~/hooks/use-crumbs'
import { useIpPoolSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { Message } from '~/ui/lib/Message'
Expand Down Expand Up @@ -59,8 +60,9 @@ function resolver(values: IpRange) {
return Object.keys(errors).length > 0 ? { values: {}, errors } : { values, errors: {} }
}

Component.displayName = 'IpPoolAddRange'
export function Component() {
export const handle = titleCrumb('Add Range')

export default function IpPoolAddRange() {
const { pool } = useIpPoolSelector()
const navigate = useNavigate()
const queryClient = useApiQueryClient()
Expand Down
6 changes: 4 additions & 2 deletions app/forms/project-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { addToast } from '~/stores/toast'
import { pb } from '~/util/path-builder'

Expand All @@ -22,8 +23,9 @@ const defaultValues: ProjectCreate = {
description: '',
}

Component.displayName = 'ProjectCreateSideModalForm'
export function Component() {
export const handle = titleCrumb('New project')

export default function ProjectCreateSideModalForm() {
const navigate = useNavigate()
const queryClient = useApiQueryClient()

Expand Down
7 changes: 5 additions & 2 deletions app/forms/project-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getProjectSelector, useProjectSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { pb } from '~/util/path-builder'
import type * as PP from '~/util/path-params'

const projectView = ({ project }: PP.Project) => apiq('projectView', { path: { project } })

EditProjectSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => {
export const handle = titleCrumb('Edit project')

export async function clientLoader({ params }: LoaderFunctionArgs) {
const { project } = getProjectSelector(params)
await queryClient.prefetchQuery(projectView({ project }))
return null
}

export function EditProjectSideModalForm() {
export default function EditProjectSideModalForm() {
const navigate = useNavigate()

const projectSelector = useProjectSelector()
Expand Down
6 changes: 4 additions & 2 deletions app/forms/snapshot-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DescriptionField } from '~/components/form/fields/DescriptionField'
import { NameField } from '~/components/form/fields/NameField'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { useProjectSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { toComboboxItems } from '~/ui/lib/Combobox'
Expand All @@ -42,8 +43,9 @@ const defaultValues: SnapshotCreate = {
name: '',
}

Component.displayName = 'SnapshotCreate'
export function Component() {
export const handle = titleCrumb('New snapshot')

export default function SnapshotCreate() {
const queryClient = useApiQueryClient()
const projectSelector = useProjectSelector()
const navigate = useNavigate()
Expand Down
3 changes: 1 addition & 2 deletions app/forms/ssh-key-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ type Props = {
message?: React.ReactNode
}

Component.displayName = 'SSHKeyCreate'
export function Component({ onDismiss, message }: Props) {
export function SSHKeyCreate({ onDismiss, message }: Props) {
const queryClient = useApiQueryClient()
const navigate = useNavigate()

Expand Down
5 changes: 4 additions & 1 deletion app/forms/subnet-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '~/components/form/fields/useItemsList'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { useVpcSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
Expand All @@ -36,7 +37,9 @@ const defaultValues: Required<VpcSubnetCreate> = {
customRouter: customRouterDataToForm(undefined),
}

export function CreateSubnetForm() {
export const handle = titleCrumb('New Subnet')

export default function CreateSubnetForm() {
const vpcSelector = useVpcSelector()
const queryClient = useApiQueryClient()

Expand Down
7 changes: 5 additions & 2 deletions app/forms/subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '~/components/form/fields/useItemsList'
import { SideModalForm } from '~/components/form/SideModalForm'
import { HL } from '~/components/HL'
import { titleCrumb } from '~/hooks/use-crumbs'
import { getVpcSubnetSelector, useVpcSubnetSelector } from '~/hooks/use-params'
import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
Expand All @@ -35,13 +36,15 @@ import type * as PP from '~/util/path-params'
const subnetView = ({ project, vpc, subnet }: PP.VpcSubnet) =>
apiq('vpcSubnetView', { query: { project, vpc }, path: { subnet } })

EditSubnetForm.loader = async ({ params }: LoaderFunctionArgs) => {
export const handle = titleCrumb('Edit Subnet')

export async function clientLoader({ params }: LoaderFunctionArgs) {
const selector = getVpcSubnetSelector(params)
await queryClient.prefetchQuery(subnetView(selector))
return null
}

export function EditSubnetForm() {
export default function EditSubnetForm() {
const subnetSelector = useVpcSubnetSelector()
const { project, vpc } = subnetSelector

Expand Down
Loading
Loading