From 1039aa33bef751c0368583a3fdcb968029d24f0f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 27 Feb 2025 19:09:20 -0600 Subject: [PATCH 1/8] convert image routes --- app/forms/image-upload.tsx | 6 ++++-- app/pages/project/images/ImagesPage.tsx | 7 +++++-- app/routes.tsx | 13 +++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/forms/image-upload.tsx b/app/forms/image-upload.tsx index cec930ca37..6ad5a37e97 100644 --- a/app/forms/image-upload.tsx +++ b/app/forms/image-upload.tsx @@ -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' @@ -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() diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index fb450c96e9..e58c6bf97c 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -14,6 +14,7 @@ import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' import { HL } from '~/components/HL' +import { makeCrumb } from '~/hooks/use-crumbs' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -45,13 +46,15 @@ const colHelper = createColumnHelper() const imageList = (query: PP.Project) => getListQFn('imageList', { query }) -ImagesPage.loader = async ({ params }: LoaderFunctionArgs) => { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project } = getProjectSelector(params) await queryClient.prefetchQuery(imageList({ project }).optionsFn()) return null } -export function ImagesPage() { +export const handle = makeCrumb('Images', (p) => pb.projectImages(getProjectSelector(p))) + +export default function ImagesPage() { const { project } = useProjectSelector() const [promoteImageName, setPromoteImageName] = useState(null) diff --git a/app/routes.tsx b/app/routes.tsx index 5995702267..cd5b28b5ed 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -18,7 +18,6 @@ import { NotFound } from './components/ErrorPage' import { CreateDiskSideModalForm } from './forms/disk-create' import { ProjectImageEdit, SiloImageEdit } from './forms/image-edit' import { CreateImageFromSnapshotSideModalForm } from './forms/image-from-snapshot' -import * as ImageCreate from './forms/image-upload' import { CreateIpPoolSideModalForm } from './forms/ip-pool-create' import * as IpPoolEdit from './forms/ip-pool-edit' import * as IpPoolAddRange from './forms/ip-pool-range-add' @@ -36,7 +35,6 @@ import { EditRouterRouteSideModalForm } from './forms/vpc-router-route-edit' import { makeCrumb, titleCrumb, type Crumb } from './hooks/use-crumbs' import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' import * as ProjectAccess from './pages/project/access/ProjectAccessPage' -import { ImagesPage } from './pages/project/images/ImagesPage' import * as ConnectTab from './pages/project/instances/ConnectTab' import { InstancePage } from './pages/project/instances/InstancePage' import * as NetworkingTab from './pages/project/instances/NetworkingTab' @@ -481,13 +479,12 @@ export const routes = createRoutesFromElements( handle={titleCrumb('Create image from snapshot')} /> - } - handle={makeCrumb('Images', (p) => pb.projectImages(getProjectSelector(p)))} - loader={ImagesPage.loader} - > + import('./pages/project/images/ImagesPage').then(convert)}> - + import('./forms/image-upload').then(convert)} + /> Date: Thu, 27 Feb 2025 23:56:29 -0600 Subject: [PATCH 2/8] convert more routes --- app/components/form/fields/SshKeysField.tsx | 4 +- app/forms/image-from-snapshot.tsx | 7 +- app/forms/project-create.tsx | 6 +- app/forms/project-edit.tsx | 8 +- app/forms/snapshot-create.tsx | 7 +- app/forms/ssh-key-create.tsx | 3 +- app/forms/subnet-create.tsx | 5 +- app/forms/subnet-edit.tsx | 8 +- app/forms/vpc-router-create.tsx | 6 +- app/forms/vpc-router-route-create.tsx | 7 +- app/forms/vpc-router-route-edit.tsx | 7 +- app/pages/ProjectsPage.tsx | 8 +- app/pages/SiloAccessPage.tsx | 7 +- .../project/access/ProjectAccessPage.tsx | 7 +- app/pages/project/snapshots/SnapshotsPage.tsx | 7 +- app/pages/project/vpcs/RouterPage.tsx | 8 +- app/pages/project/vpcs/VpcGatewaysTab.tsx | 6 +- app/pages/project/vpcs/VpcSubnetsTab.tsx | 7 +- .../project/vpcs/internet-gateway-edit.tsx | 7 +- app/pages/settings/ssh-key-create.tsx | 15 +++ app/pages/system/silos/SiloPage.tsx | 7 +- app/pages/system/silos/SilosPage.tsx | 7 +- app/routes.tsx | 95 +++++++------------ 23 files changed, 142 insertions(+), 107 deletions(-) create mode 100644 app/pages/settings/ssh-key-create.tsx diff --git a/app/components/form/fields/SshKeysField.tsx b/app/components/form/fields/SshKeysField.tsx index f177b9a3ac..fab12aea91 100644 --- a/app/components/form/fields/SshKeysField.tsx +++ b/app/components/form/fields/SshKeysField.tsx @@ -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' @@ -138,7 +138,7 @@ export function SshKeysField({ )} {showAddSshKey && ( - setShowAddSshKey(false)} message={ = { 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() diff --git a/app/forms/project-create.tsx b/app/forms/project-create.tsx index f7688b3f7d..d43fb08771 100644 --- a/app/forms/project-create.tsx +++ b/app/forms/project-create.tsx @@ -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' @@ -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() diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index a6ffd87b09..86edf14e53 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -10,6 +10,8 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router' import { apiq, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/api' +import { titleCrumb } from '~/hooks/use-crumbs' + import { DescriptionField } from '~/components/form/fields/DescriptionField' import { NameField } from '~/components/form/fields/NameField' import { SideModalForm } from '~/components/form/SideModalForm' @@ -21,13 +23,15 @@ 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() diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 7b15808547..790748f2ec 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -9,6 +9,8 @@ import { useMemo } from 'react' import { useForm } from 'react-hook-form' import { useNavigate } from 'react-router' +import { titleCrumb } from '~/hooks/use-crumbs' + import { diskCan, useApiMutation, @@ -42,8 +44,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() diff --git a/app/forms/ssh-key-create.tsx b/app/forms/ssh-key-create.tsx index 0d328b681f..ac4867006e 100644 --- a/app/forms/ssh-key-create.tsx +++ b/app/forms/ssh-key-create.tsx @@ -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() diff --git a/app/forms/subnet-create.tsx b/app/forms/subnet-create.tsx index 8728a2572c..62f6731980 100644 --- a/app/forms/subnet-create.tsx +++ b/app/forms/subnet-create.tsx @@ -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' @@ -36,7 +37,9 @@ const defaultValues: Required = { customRouter: customRouterDataToForm(undefined), } -export function CreateSubnetForm() { +export const handle = titleCrumb('New Subnet') + +export default function CreateSubnetForm() { const vpcSelector = useVpcSelector() const queryClient = useApiQueryClient() diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 897c4c2302..9d5b59cb1b 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -16,6 +16,8 @@ import { type VpcSubnetUpdate, } from '@oxide/api' +import { titleCrumb } from '~/hooks/use-crumbs' + import { DescriptionField } from '~/components/form/fields/DescriptionField' import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' @@ -35,13 +37,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 diff --git a/app/forms/vpc-router-create.tsx b/app/forms/vpc-router-create.tsx index ca14deb8ff..1132e3051d 100644 --- a/app/forms/vpc-router-create.tsx +++ b/app/forms/vpc-router-create.tsx @@ -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 { useVpcSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -23,8 +24,9 @@ const defaultValues: VpcRouterCreate = { description: '', } -Component.displayName = 'RouterCreate' -export function Component() { +export const handle = titleCrumb('New Router') + +export default function RouterCreate() { const queryClient = useApiQueryClient() const vpcSelector = useVpcSelector() const navigate = useNavigate() diff --git a/app/forms/vpc-router-route-create.tsx b/app/forms/vpc-router-route-create.tsx index 99c08bb75d..4ea1ecdf80 100644 --- a/app/forms/vpc-router-route-create.tsx +++ b/app/forms/vpc-router-route-create.tsx @@ -13,6 +13,7 @@ import { apiQueryClient, useApiMutation, useApiQueryClient } from '@oxide/api' import { SideModalForm } from '~/components/form/SideModalForm' import { HL } from '~/components/HL' import { RouteFormFields, type RouteFormValues } from '~/forms/vpc-router-route-common' +import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { ALL_ISH } from '~/util/consts' @@ -25,7 +26,9 @@ const defaultValues: RouteFormValues = { target: { type: 'ip', value: '' }, } -CreateRouterRouteSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => { +export const handle = titleCrumb('New Route') + +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcRouterSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('vpcSubnetList', { @@ -41,7 +44,7 @@ CreateRouterRouteSideModalForm.loader = async ({ params }: LoaderFunctionArgs) = return null } -export function CreateRouterRouteSideModalForm() { +export default function CreateRouterRouteSideModalForm() { const queryClient = useApiQueryClient() const routerSelector = useVpcRouterSelector() const navigate = useNavigate() diff --git a/app/forms/vpc-router-route-edit.tsx b/app/forms/vpc-router-route-edit.tsx index e28211d3f8..742408dcce 100644 --- a/app/forms/vpc-router-route-edit.tsx +++ b/app/forms/vpc-router-route-edit.tsx @@ -23,12 +23,15 @@ import { routeFormMessage, type RouteFormValues, } from '~/forms/vpc-router-route-common' +import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcRouterRouteSelector, useVpcRouterRouteSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' -EditRouterRouteSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => { +export const handle = titleCrumb('Edit Route') + +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc, router, route } = getVpcRouterRouteSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('vpcRouterRouteView', { @@ -48,7 +51,7 @@ EditRouterRouteSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => return null } -export function EditRouterRouteSideModalForm() { +export default function EditRouterRouteSideModalForm() { const queryClient = useApiQueryClient() const { route: routeName, ...routerSelector } = useVpcRouterRouteSelector() const navigate = useNavigate() diff --git a/app/pages/ProjectsPage.tsx b/app/pages/ProjectsPage.tsx index 5e059cf1af..539cf4a0d4 100644 --- a/app/pages/ProjectsPage.tsx +++ b/app/pages/ProjectsPage.tsx @@ -13,6 +13,7 @@ import { apiq, getListQFn, queryClient, useApiMutation, type Project } from '@ox import { Folder16Icon, Folder24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' +import { makeCrumb } from '~/hooks/use-crumbs' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' import { makeLinkCell } from '~/table/cells/LinkCell' @@ -38,7 +39,7 @@ const EmptyState = () => ( const projectList = getListQFn('projectList', {}) -export async function loader() { +export async function clientLoader() { // fetchQuery instead of prefetchQuery means errors blow up here instead of // waiting to hit the invariant in the useQuery call. We may end up doing this // everywhere, but here in particular it is needed to trigger the silo group @@ -47,6 +48,8 @@ export async function loader() { return null } +export const handle = makeCrumb('Projects', pb.projects()) + const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('name', { @@ -56,8 +59,7 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -Component.displayName = 'ProjectsPage' -export function Component() { +export default function ProjectsPage() { const navigate = useNavigate() const { mutateAsync: deleteProject } = useApiMutation('projectDelete', { diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index 3eabc68f75..1ce31ff2e1 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -52,7 +52,7 @@ const EmptyState = ({ onClick }: { onClick: () => void }) => ( ) -export async function loader() { +export async function clientLoader() { await Promise.all([ apiQueryClient.prefetchQuery('policyView', {}), // used to resolve user names @@ -62,6 +62,8 @@ export async function loader() { return null } +export const handle = { crumb: 'Access' } + type UserRow = { id: string identityType: IdentityType @@ -72,8 +74,7 @@ type UserRow = { const colHelper = createColumnHelper() -Component.displayName = 'SiloAccessPage' -export function Component() { +export default function SiloAccessPage() { const [addModalOpen, setAddModalOpen] = useState(false) const [editingUserRow, setEditingUserRow] = useState(null) diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 0468d0ebfd..b5fbb3e3c5 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -59,7 +59,7 @@ const EmptyState = ({ onClick }: { onClick: () => void }) => ( ) -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project } = getProjectSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('policyView', {}), @@ -71,6 +71,8 @@ export async function loader({ params }: LoaderFunctionArgs) { return null } +export const handle = { crumb: 'Access' } + type UserRow = { id: string identityType: IdentityType @@ -81,8 +83,7 @@ type UserRow = { const colHelper = createColumnHelper() -Component.displayName = 'ProjectAccessPage' -export function Component() { +export default function ProjectAccessPage() { const [addModalOpen, setAddModalOpen] = useState(false) const [editingUserRow, setEditingUserRow] = useState(null) const { project } = useProjectSelector() diff --git a/app/pages/project/snapshots/SnapshotsPage.tsx b/app/pages/project/snapshots/SnapshotsPage.tsx index f72f21ff33..416152ecc0 100644 --- a/app/pages/project/snapshots/SnapshotsPage.tsx +++ b/app/pages/project/snapshots/SnapshotsPage.tsx @@ -22,6 +22,7 @@ import { Snapshots16Icon, Snapshots24Icon } from '@oxide/design-system/icons/rea import { DocsPopover } from '~/components/DocsPopover' import { SnapshotStateBadge } from '~/components/StateBadge' +import { makeCrumb } from '~/hooks/use-crumbs' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { SkeletonCell } from '~/table/cells/EmptyCell' @@ -56,7 +57,7 @@ const EmptyState = () => ( const snapshotList = (project: string) => getListQFn('snapshotList', { query: { project } }) -SnapshotsPage.loader = async ({ params }: LoaderFunctionArgs) => { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project } = getProjectSelector(params) await Promise.all([ queryClient.prefetchQuery(snapshotList(project).optionsFn()), @@ -84,6 +85,8 @@ SnapshotsPage.loader = async ({ params }: LoaderFunctionArgs) => { return null } +export const handle = makeCrumb('Snapshots', (p) => pb.snapshots(getProjectSelector(p))) + const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('name', {}), @@ -99,7 +102,7 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -export function SnapshotsPage() { +export default function SnapshotsPage() { const queryClient = useApiQueryClient() const { project } = useProjectSelector() const navigate = useNavigate() diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index e07708f34f..baa40c60d9 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -27,6 +27,7 @@ import { DocsPopover } from '~/components/DocsPopover' import { HL } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { routeFormMessage } from '~/forms/vpc-router-route-common' +import { makeCrumb } from '~/hooks/use-crumbs' import { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { confirmAction } from '~/stores/confirm-action' import { addToast } from '~/stores/toast' @@ -43,12 +44,14 @@ import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' +export const handle = makeCrumb((p) => p.router!) + const routerView = ({ project, vpc, router }: PP.VpcRouter) => apiq('vpcRouterView', { path: { router }, query: { vpc, project } }) const routeList = (query: PP.VpcRouter) => getListQFn('vpcRouterRouteList', { query }) -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const routerSelector = getVpcRouterSelector(params) await Promise.all([ queryClient.prefetchQuery(routerView(routerSelector)), @@ -82,8 +85,7 @@ const RouterRouteTypeValueBadge = ({ ) } -Component.displayName = 'RouterPage' -export function Component() { +export default function RouterPage() { const { project, vpc, router } = useVpcRouterSelector() const { data: routerData } = usePrefetchedQuery(routerView({ project, vpc, router })) diff --git a/app/pages/project/vpcs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcGatewaysTab.tsx index 53c9c16e16..e1baf74d74 100644 --- a/app/pages/project/vpcs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcGatewaysTab.tsx @@ -32,6 +32,8 @@ import { useGatewayRoutes, } from './gateway-data' +export const handle = { crumb: 'Internet Gateways' } + const gatewayList = ({ project, vpc }: PP.Vpc) => getListQFn('internetGatewayList', { query: { project, vpc, limit: ALL_ISH } }) const projectIpPoolList = getListQFn('projectIpPoolList', { query: { limit: ALL_ISH } }) @@ -57,7 +59,7 @@ const GatewayRoutes = ({ project, vpc, gateway }: PP.VpcInternetGateway) => { const colHelper = createColumnHelper() -VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcSelector(params) const [gateways, routers] = await Promise.all([ queryClient.fetchQuery(gatewayList({ project, vpc }).optionsFn()), @@ -89,7 +91,7 @@ VpcInternetGatewaysTab.loader = async ({ params }: LoaderFunctionArgs) => { return null } -export function VpcInternetGatewaysTab() { +export default function VpcInternetGatewaysTab() { const { project, vpc } = useVpcSelector() const emptyState = ( diff --git a/app/pages/project/vpcs/VpcSubnetsTab.tsx b/app/pages/project/vpcs/VpcSubnetsTab.tsx index b5ea965998..354c198648 100644 --- a/app/pages/project/vpcs/VpcSubnetsTab.tsx +++ b/app/pages/project/vpcs/VpcSubnetsTab.tsx @@ -35,14 +35,15 @@ const colHelper = createColumnHelper() const subnetList = (params: PP.Vpc) => getListQFn('vpcSubnetList', { query: params }) -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcSelector(params) await queryClient.prefetchQuery(subnetList({ project, vpc }).optionsFn()) return null } -Component.displayName = 'VpcSubnetsTab' -export function Component() { +export const handle = { crumb: 'Subnets' } + +export default function VpcSubnetsTab() { const vpcSelector = useVpcSelector() const queryClient = useApiQueryClient() diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx index b62448726d..34720dd68f 100644 --- a/app/pages/project/vpcs/internet-gateway-edit.tsx +++ b/app/pages/project/vpcs/internet-gateway-edit.tsx @@ -10,11 +10,14 @@ import { useQuery } from '@tanstack/react-query' import { useForm } from 'react-hook-form' import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router' +export const handle = titleCrumb('Edit Internet Gateway') + import { Gateway16Icon } from '@oxide/design-system/icons/react' import { apiQueryClient, queryClient, usePrefetchedApiQuery } from '~/api' import { SideModalForm } from '~/components/form/SideModalForm' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' +import { titleCrumb } from '~/hooks/use-crumbs' import { IpPoolCell } from '~/table/cells/IpPoolCell' import { CopyableIp } from '~/ui/lib/CopyableIp' import { FormDivider } from '~/ui/lib/Divider' @@ -63,7 +66,7 @@ function RouteRows({ project, vpc, gateway }: PP.VpcInternetGateway) { )) } -EditInternetGatewayForm.loader = async function ({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc, gateway } = getInternetGatewaySelector(params) await Promise.all([ apiQueryClient.prefetchQuery('internetGatewayView', { @@ -82,7 +85,7 @@ EditInternetGatewayForm.loader = async function ({ params }: LoaderFunctionArgs) return null } -export function EditInternetGatewayForm() { +export default function EditInternetGatewayForm() { const navigate = useNavigate() const { project, vpc, gateway } = useInternetGatewaySelector() const onDismiss = () => navigate(pb.vpcInternetGateways({ project, vpc })) diff --git a/app/pages/settings/ssh-key-create.tsx b/app/pages/settings/ssh-key-create.tsx new file mode 100644 index 0000000000..3b934c44c1 --- /dev/null +++ b/app/pages/settings/ssh-key-create.tsx @@ -0,0 +1,15 @@ +/* + * 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 { SSHKeyCreate } from '~/forms/ssh-key-create' +import { titleCrumb } from '~/hooks/use-crumbs' + +export const handle = titleCrumb('New SSH key') + +export default function SSHKeyCreatePage() { + return +} diff --git a/app/pages/system/silos/SiloPage.tsx b/app/pages/system/silos/SiloPage.tsx index 2010a068e9..d335404c45 100644 --- a/app/pages/system/silos/SiloPage.tsx +++ b/app/pages/system/silos/SiloPage.tsx @@ -25,7 +25,7 @@ import { siloIdpList, SiloIdpsTab } from './SiloIdpsTab' import { siloIpPoolsQuery, SiloIpPoolsTab } from './SiloIpPoolsTab' import { SiloQuotasTab } from './SiloQuotasTab' -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { silo } = getSiloSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('siloView', { path: { silo } }), @@ -36,8 +36,9 @@ export async function loader({ params }: LoaderFunctionArgs) { return null } -Component.displayName = 'SiloPage' -export function Component() { +export const handle = { crumb: (p: any) => p.silo! } + +export default function SiloPage() { const siloSelector = useSiloSelector() const { data: silo } = usePrefetchedApiQuery('siloView', { path: siloSelector }) diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index 62f2dbaa0b..634d006570 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -64,13 +64,14 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -export async function loader() { +export async function clientLoader() { await queryClient.prefetchQuery(siloList().optionsFn()) return null } -Component.displayName = 'SilosPage' -export function Component() { +export const handle = { crumb: 'Silos' } + +export default function SilosPage() { const navigate = useNavigate() const queryClient = useApiQueryClient() diff --git a/app/routes.tsx b/app/routes.tsx index cd5b28b5ed..73cfd46ffc 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -17,40 +17,22 @@ import { import { NotFound } from './components/ErrorPage' import { CreateDiskSideModalForm } from './forms/disk-create' import { ProjectImageEdit, SiloImageEdit } from './forms/image-edit' -import { CreateImageFromSnapshotSideModalForm } from './forms/image-from-snapshot' import { CreateIpPoolSideModalForm } from './forms/ip-pool-create' import * as IpPoolEdit from './forms/ip-pool-edit' import * as IpPoolAddRange from './forms/ip-pool-range-add' -import * as ProjectCreate from './forms/project-create' -import { EditProjectSideModalForm } from './forms/project-edit' -import * as SnapshotCreate from './forms/snapshot-create' -import * as SSHKeyCreate from './forms/ssh-key-create' -import { CreateSubnetForm } from './forms/subnet-create' -import { EditSubnetForm } from './forms/subnet-edit' import { CreateVpcSideModalForm } from './forms/vpc-create' -import * as RouterCreate from './forms/vpc-router-create' import { EditRouterSideModalForm } from './forms/vpc-router-edit' -import { CreateRouterRouteSideModalForm } from './forms/vpc-router-route-create' -import { EditRouterRouteSideModalForm } from './forms/vpc-router-route-edit' import { makeCrumb, titleCrumb, type Crumb } from './hooks/use-crumbs' import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' -import * as ProjectAccess from './pages/project/access/ProjectAccessPage' import * as ConnectTab from './pages/project/instances/ConnectTab' import { InstancePage } from './pages/project/instances/InstancePage' import * as NetworkingTab from './pages/project/instances/NetworkingTab' import * as SettingsTab from './pages/project/instances/SettingsTab' import * as StorageTab from './pages/project/instances/StorageTab' -import { SnapshotsPage } from './pages/project/snapshots/SnapshotsPage' import * as VpcRoutersTab from './pages/project/vpcs//VpcRoutersTab' -import { EditInternetGatewayForm } from './pages/project/vpcs/internet-gateway-edit' -import * as RouterPage from './pages/project/vpcs/RouterPage' import { VpcFirewallRulesTab } from './pages/project/vpcs/VpcFirewallRulesTab' -import { VpcInternetGatewaysTab } from './pages/project/vpcs/VpcGatewaysTab' import { VpcPage } from './pages/project/vpcs/VpcPage' import { VpcsPage } from './pages/project/vpcs/VpcsPage' -import * as VpcSubnetsTab from './pages/project/vpcs/VpcSubnetsTab' -import * as Projects from './pages/ProjectsPage' -import * as SiloAccess from './pages/SiloAccessPage' import * as DisksTab from './pages/system/inventory/DisksTab' import { InventoryPage } from './pages/system/inventory/InventoryPage' import * as SledInstances from './pages/system/inventory/sled/SledInstancesTab' @@ -59,8 +41,6 @@ import * as SledsTab from './pages/system/inventory/SledsTab' import * as IpPool from './pages/system/networking/IpPoolPage' import * as IpPools from './pages/system/networking/IpPoolsPage' import * as SiloImages from './pages/system/SiloImagesPage' -import * as SiloPage from './pages/system/silos/SiloPage' -import * as SilosPage from './pages/system/silos/SilosPage' import { truncate } from './ui/lib/Truncate' import { pb } from './util/path-builder' @@ -125,12 +105,15 @@ export const routes = createRoutesFromElements( lazy={() => import('./forms/ssh-key-edit').then(convert)} /> - + import('./pages/settings/ssh-key-create').then(convert)} + /> import('./layouts/SystemLayout').then(convert)}> - + import('./pages/system/silos/SilosPage').then(convert)}> - p.silo!)}> + import('./pages/system/silos/SiloPage').then(convert)} + > import('./forms/idp/create').then(convert)} @@ -221,22 +207,19 @@ export const routes = createRoutesFromElements( /> {/* these are here instead of under projects because they need to use SiloLayout */} - + import('./pages/ProjectsPage').then(convert)}> import('./forms/project-create').then(convert)} /> } - loader={EditProjectSideModalForm.loader} - handle={titleCrumb('Edit project')} + lazy={() => import('./forms/project-edit').then(convert)} /> - + import('./pages/SiloAccessPage').then(convert)} /> {/* PROJECT */} @@ -366,18 +349,17 @@ export const routes = createRoutesFromElements( /> - + import('./pages/project/vpcs/VpcSubnetsTab').then(convert)} + > } - handle={titleCrumb('New Subnet')} + lazy={() => import('./forms/subnet-create').then(convert)} /> } - loader={EditSubnetForm.loader} - handle={titleCrumb('Edit Subnet')} + lazy={() => import('./forms/subnet-edit').then(convert)} /> @@ -391,21 +373,18 @@ export const routes = createRoutesFromElements( import('./forms/vpc-router-create').then(convert)} /> } + lazy={() => import('./pages/project/vpcs/VpcGatewaysTab').then(convert)} > } - loader={EditInternetGatewayForm.loader} - handle={titleCrumb('Edit Internet Gateway')} + lazy={() => + import('./pages/project/vpcs/internet-gateway-edit').then(convert) + } /> @@ -414,20 +393,19 @@ export const routes = createRoutesFromElements( p.vpc!)}> - p.router!)}> + import('./pages/project/vpcs/RouterPage').then(convert)} + > } - loader={CreateRouterRouteSideModalForm.loader} - handle={titleCrumb('New Route')} + lazy={() => import('./forms/vpc-router-route-create').then(convert)} /> } - loader={EditRouterRouteSideModalForm.loader} - handle={titleCrumb('Edit Route')} + lazy={() => import('./forms/vpc-router-route-edit').then(convert)} /> @@ -462,21 +440,17 @@ export const routes = createRoutesFromElements( /> } - handle={makeCrumb('Snapshots', (p) => pb.snapshots(getProjectSelector(p)))} - loader={SnapshotsPage.loader} + lazy={() => import('./pages/project/snapshots/SnapshotsPage').then(convert)} > import('./forms/snapshot-create').then(convert)} handle={titleCrumb('New snapshot')} /> } - loader={CreateImageFromSnapshotSideModalForm.loader} - handle={titleCrumb('Create image from snapshot')} + lazy={() => import('./forms/image-from-snapshot').then(convert)} /> import('./pages/project/images/ImagesPage').then(convert)}> @@ -491,7 +465,10 @@ export const routes = createRoutesFromElements( handle={titleCrumb('Edit Image')} /> - + import('./pages/project/access/ProjectAccessPage').then(convert)} + /> From 127eaa1dd2b33722400b3032f2952bc4d1d4f447 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Feb 2025 10:23:22 -0600 Subject: [PATCH 3/8] convert more --- .oxlintrc.json | 8 ++++++- app/forms/project-edit.tsx | 3 +-- app/forms/snapshot-create.tsx | 3 +-- app/forms/subnet-edit.tsx | 3 +-- .../project/vpcs/VpcFirewallRulesTab.tsx | 7 ++++-- .../project/vpcs/internet-gateway-edit.tsx | 6 ++--- app/pages/system/silos/SiloPage.tsx | 3 ++- app/pages/system/silos/SilosPage.tsx | 5 ++-- app/routes.tsx | 24 ++++++++++++------- 9 files changed, 39 insertions(+), 23 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index fae39f64de..17ac403c4a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -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" } diff --git a/app/forms/project-edit.tsx b/app/forms/project-edit.tsx index 86edf14e53..11db6968de 100644 --- a/app/forms/project-edit.tsx +++ b/app/forms/project-edit.tsx @@ -10,12 +10,11 @@ import { useNavigate, type LoaderFunctionArgs } from 'react-router' import { apiq, queryClient, useApiMutation, usePrefetchedQuery } from '@oxide/api' -import { titleCrumb } from '~/hooks/use-crumbs' - 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' diff --git a/app/forms/snapshot-create.tsx b/app/forms/snapshot-create.tsx index 790748f2ec..4f98b28fdf 100644 --- a/app/forms/snapshot-create.tsx +++ b/app/forms/snapshot-create.tsx @@ -9,8 +9,6 @@ import { useMemo } from 'react' import { useForm } from 'react-hook-form' import { useNavigate } from 'react-router' -import { titleCrumb } from '~/hooks/use-crumbs' - import { diskCan, useApiMutation, @@ -24,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' diff --git a/app/forms/subnet-edit.tsx b/app/forms/subnet-edit.tsx index 9d5b59cb1b..661755aa07 100644 --- a/app/forms/subnet-edit.tsx +++ b/app/forms/subnet-edit.tsx @@ -16,8 +16,6 @@ import { type VpcSubnetUpdate, } from '@oxide/api' -import { titleCrumb } from '~/hooks/use-crumbs' - import { DescriptionField } from '~/components/form/fields/DescriptionField' import { ListboxField } from '~/components/form/fields/ListboxField' import { NameField } from '~/components/form/fields/NameField' @@ -28,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' diff --git a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx index 7528344d44..498b04e9b2 100644 --- a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx +++ b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx @@ -19,6 +19,7 @@ import { } from '@oxide/api' import { ListPlusCell } from '~/components/ListPlusCell' +import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { EnabledCell } from '~/table/cells/EnabledCell' @@ -100,13 +101,15 @@ const staticColumns = [ const rulesView = (query: PP.Vpc) => apiq('vpcFirewallRulesView', { query }) -VpcFirewallRulesTab.loader = async ({ params }: LoaderFunctionArgs) => { +export const handle = titleCrumb('Firewall Rules') + +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcSelector(params) await queryClient.prefetchQuery(rulesView({ project, vpc })) return null } -export function VpcFirewallRulesTab() { +export default function VpcFirewallRulesTab() { const vpcSelector = useVpcSelector() const { data } = usePrefetchedQuery(rulesView(vpcSelector)) diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx index 34720dd68f..59af8c3e0a 100644 --- a/app/pages/project/vpcs/internet-gateway-edit.tsx +++ b/app/pages/project/vpcs/internet-gateway-edit.tsx @@ -10,14 +10,12 @@ import { useQuery } from '@tanstack/react-query' import { useForm } from 'react-hook-form' import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router' -export const handle = titleCrumb('Edit Internet Gateway') - import { Gateway16Icon } from '@oxide/design-system/icons/react' import { apiQueryClient, queryClient, usePrefetchedApiQuery } from '~/api' import { SideModalForm } from '~/components/form/SideModalForm' -import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' import { titleCrumb } from '~/hooks/use-crumbs' +import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' import { IpPoolCell } from '~/table/cells/IpPoolCell' import { CopyableIp } from '~/ui/lib/CopyableIp' import { FormDivider } from '~/ui/lib/Divider' @@ -37,6 +35,8 @@ import { useGatewayRoutes, } from './gateway-data' +export const handle = titleCrumb('Edit Internet Gateway') + const RoutesEmpty = () => ( diff --git a/app/pages/system/silos/SiloPage.tsx b/app/pages/system/silos/SiloPage.tsx index d335404c45..609842d794 100644 --- a/app/pages/system/silos/SiloPage.tsx +++ b/app/pages/system/silos/SiloPage.tsx @@ -12,6 +12,7 @@ import { Cloud16Icon, Cloud24Icon, NextArrow12Icon } from '@oxide/design-system/ import { DocsPopover } from '~/components/DocsPopover' import { QueryParamTabs } from '~/components/QueryParamTabs' +import { makeCrumb } from '~/hooks/use-crumbs' import { getSiloSelector, useSiloSelector } from '~/hooks/use-params' import { Badge } from '~/ui/lib/Badge' import { EmptyMessage } from '~/ui/lib/EmptyMessage' @@ -36,7 +37,7 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { return null } -export const handle = { crumb: (p: any) => p.silo! } +export const handle = makeCrumb((p) => p.silo!) export default function SiloPage() { const siloSelector = useSiloSelector() diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index 634d006570..7c15d5763c 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -20,6 +20,7 @@ import { Cloud16Icon, Cloud24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' import { HL } from '~/components/HL' +import { makeCrumb } from '~/hooks/use-crumbs' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' import { addToast } from '~/stores/toast' @@ -69,14 +70,14 @@ export async function clientLoader() { return null } -export const handle = { crumb: 'Silos' } +export const handle = makeCrumb('Silos', pb.silos()) export default function SilosPage() { const navigate = useNavigate() const queryClient = useApiQueryClient() const { mutateAsync: deleteSilo } = useApiMutation('siloDelete', { - onSuccess(silo, { path }) { + onSuccess(_silo, { path }) { queryClient.invalidateQueries('siloList') addToast(<>Silo {path.silo} deleted) // prettier-ignore }, diff --git a/app/routes.tsx b/app/routes.tsx index 73cfd46ffc..b2141e2c36 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -30,7 +30,6 @@ import * as NetworkingTab from './pages/project/instances/NetworkingTab' import * as SettingsTab from './pages/project/instances/SettingsTab' import * as StorageTab from './pages/project/instances/StorageTab' import * as VpcRoutersTab from './pages/project/vpcs//VpcRoutersTab' -import { VpcFirewallRulesTab } from './pages/project/vpcs/VpcFirewallRulesTab' import { VpcPage } from './pages/project/vpcs/VpcPage' import { VpcsPage } from './pages/project/vpcs/VpcsPage' import * as DisksTab from './pages/system/inventory/DisksTab' @@ -322,12 +321,22 @@ export const routes = createRoutesFromElements( } loader={VpcPage.loader}> } - loader={VpcFirewallRulesTab.loader} + // janky one. we only want the loader. we'll have to make this + // its own file eventually. unfortunately the loader can't + // do redirect() with a replace + lazy={() => + import('./pages/project/vpcs/VpcFirewallRulesTab') + .then(convert) + .then(({ loader }) => ({ + loader, + Component: () => , + })) + } /> } - loader={VpcFirewallRulesTab.loader} + lazy={() => + import('./pages/project/vpcs/VpcFirewallRulesTab').then(convert) + } > - + import('./forms/firewall-rules-create').then(convert)} @@ -446,7 +455,6 @@ export const routes = createRoutesFromElements( import('./forms/snapshot-create').then(convert)} - handle={titleCrumb('New snapshot')} /> Date: Fri, 28 Feb 2025 10:55:22 -0600 Subject: [PATCH 4/8] convert ip pool forms, vpc, and disk create --- app/forms/disk-create.tsx | 12 +++------- app/forms/ip-pool-create.tsx | 5 +++- app/forms/ip-pool-edit.tsx | 8 ++++--- app/forms/ip-pool-range-add.tsx | 6 +++-- app/forms/vpc-create.tsx | 5 +++- app/forms/vpc-router-edit.tsx | 7 ++++-- app/pages/project/disks/DiskCreate.tsx | 22 ++++++++++++++++++ app/routes.tsx | 32 ++++++++++---------------- 8 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 app/pages/project/disks/DiskCreate.tsx diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index 4e5db15fa6..ded6cec33a 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -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, @@ -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[] } @@ -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 {data.name} created) // prettier-ignore onSuccess?.(data) - onDismiss(navigate) + onDismiss() }, }) @@ -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) { diff --git a/app/forms/ip-pool-create.tsx b/app/forms/ip-pool-create.tsx index 65fafaeca3..5190d881ce 100644 --- a/app/forms/ip-pool-create.tsx +++ b/app/forms/ip-pool-create.tsx @@ -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' @@ -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() diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index 3c92cd8c00..e65ed367ff 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -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 { titleCrumb } 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 = titleCrumb('Edit IP pool') + +export default function EditIpPoolSideModalForm() { const queryClient = useApiQueryClient() const navigate = useNavigate() const poolSelector = useIpPoolSelector() diff --git a/app/forms/ip-pool-range-add.tsx b/app/forms/ip-pool-range-add.tsx index 2039cfaf2d..5697b2e0ea 100644 --- a/app/forms/ip-pool-range-add.tsx +++ b/app/forms/ip-pool-range-add.tsx @@ -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' @@ -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() diff --git a/app/forms/vpc-create.tsx b/app/forms/vpc-create.tsx index eda29adf6d..c3d0f23ae0 100644 --- a/app/forms/vpc-create.tsx +++ b/app/forms/vpc-create.tsx @@ -15,6 +15,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 { useProjectSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -25,7 +26,9 @@ const defaultValues: VpcCreate = { dnsName: '', } -export function CreateVpcSideModalForm() { +export const handle = titleCrumb('New VPC') + +export default function CreateVpcSideModalForm() { const projectSelector = useProjectSelector() const queryClient = useApiQueryClient() const navigate = useNavigate() diff --git a/app/forms/vpc-router-edit.tsx b/app/forms/vpc-router-edit.tsx index 3d96605e09..64eb97dc09 100644 --- a/app/forms/vpc-router-edit.tsx +++ b/app/forms/vpc-router-edit.tsx @@ -20,6 +20,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 { getVpcRouterSelector, useVpcRouterSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -28,13 +29,15 @@ import type * as PP from '~/util/path-params' const routerView = ({ project, vpc, router }: PP.VpcRouter) => apiq('vpcRouterView', { path: { router }, query: { project, vpc } }) -EditRouterSideModalForm.loader = async ({ params }: LoaderFunctionArgs) => { +export async function clientLoader({ params }: LoaderFunctionArgs) { const selector = getVpcRouterSelector(params) await queryClient.prefetchQuery(routerView(selector)) return null } -export function EditRouterSideModalForm() { +export const handle = titleCrumb('Edit Router') + +export default function EditRouterSideModalForm() { const routerSelector = useVpcRouterSelector() const { project, vpc, router } = routerSelector const { data: routerData } = usePrefetchedQuery(routerView(routerSelector)) diff --git a/app/pages/project/disks/DiskCreate.tsx b/app/pages/project/disks/DiskCreate.tsx new file mode 100644 index 0000000000..b3baeb5785 --- /dev/null +++ b/app/pages/project/disks/DiskCreate.tsx @@ -0,0 +1,22 @@ +/* + * 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' + +import { CreateDiskSideModalForm } from '~/forms/disk-create' +import { titleCrumb } from '~/hooks/use-crumbs' +import { useProjectSelector } from '~/hooks/use-params' +import { pb } from '~/util/path-builder' + +export const handle = titleCrumb('New disk') + +export default function DiskCreate() { + const navigate = useNavigate() + const { project } = useProjectSelector() + const onDismiss = () => navigate(pb.disks({ project })) + return +} diff --git a/app/routes.tsx b/app/routes.tsx index b2141e2c36..852537cf8e 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -15,13 +15,7 @@ import { } from 'react-router' import { NotFound } from './components/ErrorPage' -import { CreateDiskSideModalForm } from './forms/disk-create' import { ProjectImageEdit, SiloImageEdit } from './forms/image-edit' -import { CreateIpPoolSideModalForm } from './forms/ip-pool-create' -import * as IpPoolEdit from './forms/ip-pool-edit' -import * as IpPoolAddRange from './forms/ip-pool-range-add' -import { CreateVpcSideModalForm } from './forms/vpc-create' -import { EditRouterSideModalForm } from './forms/vpc-router-edit' import { makeCrumb, titleCrumb, type Crumb } from './hooks/use-crumbs' import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' import * as ConnectTab from './pages/project/instances/ConnectTab' @@ -173,13 +167,19 @@ export const routes = createRoutesFromElements( } /> - } /> + import('./forms/ip-pool-create').then(convert)} + /> p.pool!)}> - - + import('./forms/ip-pool-edit').then(convert)} /> + import('./forms/ip-pool-range-add').then(convert)} + /> @@ -306,8 +306,7 @@ export const routes = createRoutesFromElements( } - handle={titleCrumb('New VPC')} + lazy={() => import('./forms/vpc-create').then(convert)} /> @@ -375,9 +374,7 @@ export const routes = createRoutesFromElements( } - loader={EditRouterSideModalForm.loader} - handle={titleCrumb('Edit Router')} + lazy={() => import('./forms/vpc-router-edit').then(convert)} /> navigate('../disks')} /> - } - handle={titleCrumb('New disk')} + lazy={() => import('./pages/project/disks/DiskCreate').then(convert)} /> Date: Fri, 28 Feb 2025 11:19:54 -0600 Subject: [PATCH 5/8] convert image edit things (all claude code) --- app/forms/image-edit.tsx | 49 ++----------------- app/pages/project/images/ProjectImageEdit.tsx | 34 +++++++++++++ app/pages/system/SiloImageEdit.tsx | 30 ++++++++++++ app/routes.tsx | 11 +++-- 4 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 app/pages/project/images/ProjectImageEdit.tsx create mode 100644 app/pages/system/SiloImageEdit.tsx diff --git a/app/forms/image-edit.tsx b/app/forms/image-edit.tsx index 721460246c..b0d348aee8 100644 --- a/app/forms/image-edit.tsx +++ b/app/forms/image-edit.tsx @@ -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 -} - -function EditSiloImageSideModalForm() { - const { image } = useSiloImageSelector() - const { data } = usePrefetchedApiQuery('imageView', { path: { image } }) - - return -} - -function EditImageSideModalForm({ +export function EditImageSideModalForm({ image, dismissLink, type, diff --git a/app/pages/project/images/ProjectImageEdit.tsx b/app/pages/project/images/ProjectImageEdit.tsx new file mode 100644 index 0000000000..5c92a1ae3d --- /dev/null +++ b/app/pages/project/images/ProjectImageEdit.tsx @@ -0,0 +1,34 @@ +/* + * 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 LoaderFunctionArgs } from 'react-router' + +import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api' + +import { EditImageSideModalForm } from '~/forms/image-edit' +import { titleCrumb } from '~/hooks/use-crumbs' +import { getProjectImageSelector, useProjectImageSelector } from '~/hooks/use-params' +import { pb } from '~/util/path-builder' + +export async function clientLoader({ params }: LoaderFunctionArgs) { + const { project, image } = getProjectImageSelector(params) + await apiQueryClient.prefetchQuery('imageView', { path: { image }, query: { project } }) + return null +} + +export const handle = titleCrumb('Edit Image') + +export default function ProjectImageEdit() { + const { project, image } = useProjectImageSelector() + const { data } = usePrefetchedApiQuery('imageView', { + path: { image }, + query: { project }, + }) + + const dismissLink = pb.projectImages({ project }) + return +} diff --git a/app/pages/system/SiloImageEdit.tsx b/app/pages/system/SiloImageEdit.tsx new file mode 100644 index 0000000000..178a096152 --- /dev/null +++ b/app/pages/system/SiloImageEdit.tsx @@ -0,0 +1,30 @@ +/* + * 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 LoaderFunctionArgs } from 'react-router' + +import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api' + +import { EditImageSideModalForm } from '~/forms/image-edit' +import { titleCrumb } from '~/hooks/use-crumbs' +import { getSiloImageSelector, useSiloImageSelector } from '~/hooks/use-params' +import { pb } from '~/util/path-builder' + +export async function clientLoader({ params }: LoaderFunctionArgs) { + const { image } = getSiloImageSelector(params) + await apiQueryClient.prefetchQuery('imageView', { path: { image } }) + return null +} + +export const handle = titleCrumb('Edit Image') + +export default function SiloImageEdit() { + const { image } = useSiloImageSelector() + const { data } = usePrefetchedApiQuery('imageView', { path: { image } }) + + return +} diff --git a/app/routes.tsx b/app/routes.tsx index 852537cf8e..a14f8cc162 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -15,8 +15,7 @@ import { } from 'react-router' import { NotFound } from './components/ErrorPage' -import { ProjectImageEdit, SiloImageEdit } from './forms/image-edit' -import { makeCrumb, titleCrumb, type Crumb } from './hooks/use-crumbs' +import { makeCrumb, type Crumb } from './hooks/use-crumbs' import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' import * as ConnectTab from './pages/project/instances/ConnectTab' import { InstancePage } from './pages/project/instances/InstancePage' @@ -188,7 +187,10 @@ export const routes = createRoutesFromElements( import('./layouts/SiloLayout').then(convert)}> - + import('./pages/system/SiloImageEdit').then(convert)} + /> import('./pages/project/images/ProjectImageEdit').then(convert)} /> Date: Fri, 28 Feb 2025 11:30:15 -0600 Subject: [PATCH 6/8] fix ip pool edit crumb --- app/forms/ip-pool-edit.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/forms/ip-pool-edit.tsx b/app/forms/ip-pool-edit.tsx index e65ed367ff..903f038484 100644 --- a/app/forms/ip-pool-edit.tsx +++ b/app/forms/ip-pool-edit.tsx @@ -19,7 +19,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 { makeCrumb } from '~/hooks/use-crumbs' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' import { addToast } from '~/stores/toast' import { pb } from '~/util/path-builder' @@ -32,7 +32,7 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { return null } -export const handle = titleCrumb('Edit IP pool') +export const handle = makeCrumb('Edit IP pool') export default function EditIpPoolSideModalForm() { const queryClient = useApiQueryClient() From e4283a2b26755b5eec522b25cef8e57bb71e33a2 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Feb 2025 11:30:15 -0600 Subject: [PATCH 7/8] Convert InstancePage and all its tabs (all claude) --- app/pages/project/instances/ConnectTab.tsx | 7 +++-- app/pages/project/instances/InstancePage.tsx | 10 +++++-- app/pages/project/instances/NetworkingTab.tsx | 7 +++-- app/pages/project/instances/SettingsTab.tsx | 5 ++-- app/pages/project/instances/StorageTab.tsx | 7 +++-- app/routes.tsx | 29 ++++++++++++------- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/app/pages/project/instances/ConnectTab.tsx b/app/pages/project/instances/ConnectTab.tsx index ca0e518d42..c2cc5a6fa3 100644 --- a/app/pages/project/instances/ConnectTab.tsx +++ b/app/pages/project/instances/ConnectTab.tsx @@ -17,7 +17,7 @@ import { LearnMore, SettingsGroup } from '~/ui/lib/SettingsGroup' import { links } from '~/util/links' import { pb } from '~/util/path-builder' -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, instance } = getInstanceSelector(params) await apiQueryClient.prefetchQuery('instanceExternalIpList', { path: { instance }, @@ -26,8 +26,9 @@ export async function loader({ params }: LoaderFunctionArgs) { return null } -Component.displayName = 'ConnectTab' -export function Component() { +export const handle = { crumb: 'Connect' } + +export default function ConnectTab() { const { project, instance } = useInstanceSelector() const { data: externalIps } = usePrefetchedApiQuery('instanceExternalIpList', { path: { instance }, diff --git a/app/pages/project/instances/InstancePage.tsx b/app/pages/project/instances/InstancePage.tsx index 9b0e150561..d49370df9e 100644 --- a/app/pages/project/instances/InstancePage.tsx +++ b/app/pages/project/instances/InstancePage.tsx @@ -36,6 +36,7 @@ import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RefreshButton } from '~/components/RefreshButton' import { RouteTabs, Tab } from '~/components/RouteTabs' import { InstanceStateBadge } from '~/components/StateBadge' +import { makeCrumb } from '~/hooks/use-crumbs' import { getInstanceSelector, useInstanceSelector, @@ -73,7 +74,7 @@ async function refreshData() { ]) } -InstancePage.loader = async ({ params }: LoaderFunctionArgs) => { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, instance } = getInstanceSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('instanceView', { @@ -120,7 +121,12 @@ const PollingSpinner = () => ( ) -export function InstancePage() { +export const handle = makeCrumb( + (p) => p.instance!, + (p) => pb.instance(getInstanceSelector(p)) +) + +export default function InstancePage() { const instanceSelector = useInstanceSelector() const [resizeInstance, setResizeInstance] = useState(false) diff --git a/app/pages/project/instances/NetworkingTab.tsx b/app/pages/project/instances/NetworkingTab.tsx index de416e3242..0a5c685c70 100644 --- a/app/pages/project/instances/NetworkingTab.tsx +++ b/app/pages/project/instances/NetworkingTab.tsx @@ -84,7 +84,7 @@ const SubnetNameFromId = ({ value }: { value: string }) => { return {subnet.name} } -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, instance } = getInstanceSelector(params) await Promise.all([ apiQueryClient.prefetchQuery('instanceNetworkInterfaceList', { @@ -178,8 +178,9 @@ const staticIpCols = [ }), ] -Component.displayName = 'NetworkingTab' -export function Component() { +export const handle = { crumb: 'Networking' } + +export default function NetworkingTab() { const instanceSelector = useInstanceSelector() const { instance: instanceName, project } = instanceSelector diff --git a/app/pages/project/instances/SettingsTab.tsx b/app/pages/project/instances/SettingsTab.tsx index c75492fc86..9a20cca1bd 100644 --- a/app/pages/project/instances/SettingsTab.tsx +++ b/app/pages/project/instances/SettingsTab.tsx @@ -39,8 +39,9 @@ type FormValues = { autoRestartPolicy: FormPolicy } -Component.displayName = 'SettingsTab' -export function Component() { +export const handle = { crumb: 'Settings' } + +export default function SettingsTab() { const instanceSelector = useInstanceSelector() const { data: instance } = usePrefetchedApiQuery('instanceView', { diff --git a/app/pages/project/instances/StorageTab.tsx b/app/pages/project/instances/StorageTab.tsx index 8b790aa1ee..20d51afeff 100644 --- a/app/pages/project/instances/StorageTab.tsx +++ b/app/pages/project/instances/StorageTab.tsx @@ -41,7 +41,7 @@ import { links } from '~/util/links' import { fancifyStates } from './common' -export async function loader({ params }: LoaderFunctionArgs) { +export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, instance } = getInstanceSelector(params) const selector = { path: { instance }, query: { project } } await Promise.all([ @@ -75,8 +75,9 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -Component.displayName = 'StorageTab' -export function Component() { +export const handle = { crumb: 'Storage' } + +export default function StorageTab() { const [showDiskCreate, setShowDiskCreate] = useState(false) const [showDiskAttach, setShowDiskAttach] = useState(false) diff --git a/app/routes.tsx b/app/routes.tsx index a14f8cc162..9425f9e7e7 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -17,11 +17,6 @@ import { import { NotFound } from './components/ErrorPage' import { makeCrumb, type Crumb } from './hooks/use-crumbs' import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' -import * as ConnectTab from './pages/project/instances/ConnectTab' -import { InstancePage } from './pages/project/instances/InstancePage' -import * as NetworkingTab from './pages/project/instances/NetworkingTab' -import * as SettingsTab from './pages/project/instances/SettingsTab' -import * as StorageTab from './pages/project/instances/StorageTab' import * as VpcRoutersTab from './pages/project/vpcs//VpcRoutersTab' import { VpcPage } from './pages/project/vpcs/VpcPage' import { VpcsPage } from './pages/project/vpcs/VpcsPage' @@ -263,12 +258,18 @@ export const routes = createRoutesFromElements( )} > } /> - } loader={InstancePage.loader}> - + import('./pages/project/instances/InstancePage').then(convert)} + > + import('./pages/project/instances/StorageTab').then(convert)} + /> + import('./pages/project/instances/NetworkingTab').then(convert) + } /> - - + import('./pages/project/instances/ConnectTab').then(convert)} + /> + import('./pages/project/instances/SettingsTab').then(convert)} + /> From fc4fa45e04f37229a9eb8f20f7015ecf80caaf1c Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Feb 2025 11:59:29 -0600 Subject: [PATCH 8/8] remove extra crumbs --- app/pages/project/instances/InstancePage.tsx | 6 ------ app/pages/project/vpcs/VpcFirewallRulesTab.tsx | 3 --- 2 files changed, 9 deletions(-) diff --git a/app/pages/project/instances/InstancePage.tsx b/app/pages/project/instances/InstancePage.tsx index d49370df9e..6079755b93 100644 --- a/app/pages/project/instances/InstancePage.tsx +++ b/app/pages/project/instances/InstancePage.tsx @@ -36,7 +36,6 @@ import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { RefreshButton } from '~/components/RefreshButton' import { RouteTabs, Tab } from '~/components/RouteTabs' import { InstanceStateBadge } from '~/components/StateBadge' -import { makeCrumb } from '~/hooks/use-crumbs' import { getInstanceSelector, useInstanceSelector, @@ -121,11 +120,6 @@ const PollingSpinner = () => ( ) -export const handle = makeCrumb( - (p) => p.instance!, - (p) => pb.instance(getInstanceSelector(p)) -) - export default function InstancePage() { const instanceSelector = useInstanceSelector() const [resizeInstance, setResizeInstance] = useState(false) diff --git a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx index 498b04e9b2..87b21de381 100644 --- a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx +++ b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx @@ -19,7 +19,6 @@ import { } from '@oxide/api' import { ListPlusCell } from '~/components/ListPlusCell' -import { titleCrumb } from '~/hooks/use-crumbs' import { getVpcSelector, useVpcSelector } from '~/hooks/use-params' import { confirmDelete } from '~/stores/confirm-delete' import { EnabledCell } from '~/table/cells/EnabledCell' @@ -101,8 +100,6 @@ const staticColumns = [ const rulesView = (query: PP.Vpc) => apiq('vpcFirewallRulesView', { query }) -export const handle = titleCrumb('Firewall Rules') - export async function clientLoader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcSelector(params) await queryClient.prefetchQuery(rulesView({ project, vpc }))