diff --git a/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md b/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md new file mode 100644 index 00000000000..d2c9b40cba3 --- /dev/null +++ b/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Migrate Object Storage to Tanstack Router ([#11924](https://github.com/linode/manager/pull/11924)) diff --git a/packages/manager/.eslintrc.cjs b/packages/manager/.eslintrc.cjs index 44587982dd4..8c743b2bb8e 100644 --- a/packages/manager/.eslintrc.cjs +++ b/packages/manager/.eslintrc.cjs @@ -147,6 +147,7 @@ module.exports = { 'src/features/Images/**/*', 'src/features/Longview/**/*', 'src/features/NodeBalancers/**/*', + 'src/features/ObjectStorage/**/*', 'src/features/PlacementGroups/**/*', 'src/features/StackScripts/**/*', 'src/features/Volumes/**/*', diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts index 71d45be1a66..6a014990a23 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts @@ -100,9 +100,7 @@ describe('object storage end-to-end tests', () => { cy.findByLabelText('Content is loading').should('not.exist'); }); - ui.entityHeader.find().within(() => { - ui.button.findByTitle('Create Bucket').should('be.visible').click(); - }); + ui.button.findByTitle('Create Bucket').should('be.visible').click(); ui.drawer .findByTitle('Create Bucket') @@ -170,31 +168,31 @@ describe('object storage end-to-end tests', () => { interceptUpdateBucketAccess(bucketLabel, bucketCluster).as( 'updateBucketAccess' ); - }); - // Navigate to new bucket page, upload and delete an object. - cy.visitWithLogin(bucketAccessPage); + // Navigate to new bucket page, upload and delete an object. + cy.visitWithLogin(bucketAccessPage); - cy.wait('@getBucketAccess'); + cy.wait('@getBucketAccess'); - // Make object public, confirm it can be accessed. - cy.findByLabelText('Access Control List (ACL)') - .should('be.visible') - .should('not.have.value', 'Loading access...') - .should('have.value', 'Private') - .click(); - cy.focused().type('Public Read'); + // Make object public, confirm it can be accessed. + cy.findByLabelText('Access Control List (ACL)') + .should('be.visible') + .should('not.have.value', 'Loading access...') + .should('have.value', 'Private') + .click(); + cy.focused().type('Public Read'); - ui.autocompletePopper - .findByTitle('Public Read') - .should('be.visible') - .click(); + ui.autocompletePopper + .findByTitle('Public Read') + .should('be.visible') + .click(); - ui.button.findByTitle('Save').should('be.visible').click(); + ui.button.findByTitle('Save').should('be.visible').click(); - // TODO Confirm that outgoing API request contains expected values. - cy.wait('@updateBucketAccess'); + // TODO Confirm that outgoing API request contains expected values. + cy.wait('@updateBucketAccess'); - cy.findByText('Bucket access updated successfully.'); + cy.findByText('Bucket access updated successfully.'); + }); }); }); diff --git a/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts b/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts index 3c597b9def5..8bfd54021dd 100644 --- a/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts @@ -65,9 +65,7 @@ describe('Object Storage Multicluster Bucket create', () => { cy.visitWithLogin('/object-storage'); cy.wait(['@getRegions', '@getBuckets']); - ui.entityHeader.find().within(() => { - ui.button.findByTitle('Create Bucket').should('be.visible').click(); - }); + ui.button.findByTitle('Create Bucket').should('be.visible').click(); ui.drawer .findByTitle('Create Bucket') diff --git a/packages/manager/src/MainContent.tsx b/packages/manager/src/MainContent.tsx index 4dd480491df..7f9a2bb6434 100644 --- a/packages/manager/src/MainContent.tsx +++ b/packages/manager/src/MainContent.tsx @@ -123,7 +123,6 @@ const Kubernetes = React.lazy(() => default: module.Kubernetes, })) ); -const ObjectStorage = React.lazy(() => import('src/features/ObjectStorage')); const Profile = React.lazy(() => import('src/features/Profile/Profile').then((module) => ({ default: module.Profile, @@ -365,10 +364,6 @@ export const MainContent = () => { - {isIAMEnabled && ( diff --git a/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx b/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx index 89701c8b0ce..5e8573b48cc 100644 --- a/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx +++ b/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx @@ -25,6 +25,7 @@ export const TanStackTabLinkList = ({ noLink, tabs }: TabLinkListProps) => { { @@ -76,15 +67,13 @@ export const BucketDetail = (props: Props) => { * @note If `Object Storage Access Key Regions` is enabled, clusterId will actually contain * the bucket's region id */ - const match = useRouteMatch( - '/object-storage/buckets/:clusterId/:bucketName' - ); - const location = useLocation(); - const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); - const bucketName = match?.params.bucketName || ''; - const clusterId = match?.params.clusterId || ''; - const prefix = getQueryParamFromQueryString(location.search, 'prefix'); + const { bucketName, clusterId } = useParams({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); + const { prefix = '' } = useSearch({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); const queryClient = useQueryClient(); const flags = useFlags(); diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx index aed134be018..c1435a5718d 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx @@ -1,6 +1,5 @@ -import { getQueryParamFromQueryString } from '@linode/utilities'; +import { useSearch } from '@tanstack/react-router'; import * as React from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; import { BucketRateLimitTable } from '../BucketLanding/BucketRateLimitTable'; import { BucketBreadcrumb } from './BucketBreadcrumb'; @@ -19,10 +18,9 @@ interface Props { export const BucketProperties = React.memo((props: Props) => { const { bucket } = props; const { endpoint_type, hostname, label } = bucket; - - const location = useLocation(); - const history = useHistory(); - const prefix = getQueryParamFromQueryString(location.search, 'prefix'); + const { prefix = '' } = useSearch({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); return ( <> diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index 0cd7065df01..1c820a90538 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -1,26 +1,22 @@ import { useAccount } from '@linode/queries'; import { isFeatureEnabledV2 } from '@linode/utilities'; -import { createLazyRoute } from '@tanstack/react-router'; +import { useParams } from '@tanstack/react-router'; import * as React from 'react'; -import { matchPath } from 'react-router-dom'; import { LandingHeader } from 'src/components/LandingHeader'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; -import { TabLinkList } from 'src/components/Tabs/TabLinkList'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; +import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; import { useFlags } from 'src/hooks/useFlags'; +import { useTabs } from 'src/hooks/useTabs'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; import { BucketAccess } from './BucketAccess'; -import type { ObjectStorageClusterID } from '@linode/api-v4/lib/object-storage'; -import type { ComponentType, LazyExoticComponent } from 'react'; -import type { RouteComponentProps } from 'react-router-dom'; - -const ObjectList: LazyExoticComponent> = React.lazy(() => +const ObjectList = React.lazy(() => import('./BucketDetail').then((module) => ({ default: module.BucketDetail })) ); const BucketSSL = React.lazy(() => @@ -29,14 +25,10 @@ const BucketSSL = React.lazy(() => })) ); -interface MatchProps { - bucketName: string; - clusterId: ObjectStorageClusterID; -} - -type Props = RouteComponentProps; - -export const BucketDetailLanding = React.memo((props: Props) => { +export const BucketDetailLanding = React.memo(() => { + const { bucketName, clusterId } = useParams({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); const { data: account } = useAccount(); const flags = useFlags(); @@ -50,44 +42,28 @@ export const BucketDetailLanding = React.memo((props: Props) => { isObjectStorageGen2Enabled ); - const matches = (p: string) => { - return Boolean(matchPath(p, { path: props.location.pathname })); - }; - const { bucketName, clusterId } = props.match.params; - const bucket = bucketsData?.buckets.find(({ label }) => label === bucketName); const { endpoint_type } = bucket ?? {}; const isGen2Endpoint = endpoint_type === 'E2' || endpoint_type === 'E3'; - const tabs = [ + const { handleTabChange, tabIndex, tabs } = useTabs([ { - routeName: `${props.match.url}/objects`, title: 'Objects', + to: `/object-storage/buckets/$clusterId/$bucketName/objects`, }, { - routeName: `${props.match.url}/access`, title: 'Access', + to: `/object-storage/buckets/$clusterId/$bucketName/access`, }, - ...(!isGen2Endpoint - ? [ - { - routeName: `${props.match.url}/ssl`, - title: 'SSL/TLS', - }, - ] - : []), - ]; - - const [index, setIndex] = React.useState( - tabs.findIndex((tab) => matches(tab.routeName)) || 0 - ); - const handleTabChange = (index: number) => { - setIndex(index); - props.history.push(tabs[index].routeName); - }; + { + hide: isGen2Endpoint, + title: 'SSL/TLS', + to: `/object-storage/buckets/$clusterId/$bucketName/ssl`, + }, + ]); return ( <> @@ -108,13 +84,13 @@ export const BucketDetailLanding = React.memo((props: Props) => { docsLink="https://www.linode.com/docs/platform/object-storage/" /> - - + + }> - + { ); }); -export const bucketDetailLandingLazyRoute = createLazyRoute( - '/object-storage/buckets/$clusterId/$bucketName' -)({ - component: BucketDetailLanding, -}); - export default BucketDetailLanding; diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx index 615e640db1f..3a14872baa5 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx @@ -11,8 +11,37 @@ import { renderWithTheme } from 'src/utilities/testHelpers'; import { BucketLanding } from './BucketLanding'; +const queryMocks = vi.hoisted(() => ({ + useNavigate: vi.fn(() => vi.fn()), + useOrderV2: vi.fn().mockReturnValue({}), + useSearch: vi.fn(), +})); + +vi.mock('@tanstack/react-router', async () => { + const actual = await vi.importActual('@tanstack/react-router'); + return { + ...actual, + useNavigate: queryMocks.useNavigate, + useSearch: queryMocks.useSearch, + }; +}); + +vi.mock('src/hooks/useOrderV2', async () => { + const actual = await vi.importActual('src/hooks/useOrderV2'); + return { + ...actual, + useOrderV2: queryMocks.useOrderV2, + }; +}); + describe('ObjectStorageLanding', () => { - beforeAll(() => server.listen()); + beforeAll(() => { + server.listen(); + queryMocks.useSearch.mockReturnValue({ + order: 'asc', + orderBy: 'label', + }); + }); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); @@ -123,6 +152,11 @@ describe('ObjectStorageLanding', () => { it('renders rows for each Bucket', async () => { const buckets = objectStorageBucketFactory.buildList(2); + queryMocks.useOrderV2.mockReturnValue({ + order: 'asc', + orderBy: 'label', + sortedData: buckets, + }); // Mock Clusters server.use( diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx index 67d208c7ef4..74494b5e81d 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx @@ -7,9 +7,9 @@ import { makeStyles } from 'tss-react/mui'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { Link } from 'src/components/Link'; -import OrderBy from 'src/components/OrderBy'; import { TransferDisplay } from 'src/components/TransferDisplay/TransferDisplay'; import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog'; +import { useOrderV2 } from 'src/hooks/useOrderV2'; import { useDeleteBucketMutation, useObjectStorageBuckets, @@ -113,6 +113,23 @@ export const BucketLanding = (props: Props) => { }); }; + const { + handleOrderChange, + order, + orderBy, + sortedData: orderedData, + } = useOrderV2({ + data: objectStorageBucketsResponse?.buckets, + initialRoute: { + defaultOrder: { + order: 'asc', + orderBy: 'label', + }, + from: '/object-storage/buckets', + }, + preferenceKey: 'object-storage-buckets', + }); + const closeRemoveBucketConfirmationDialog = React.useCallback(() => { removeBucketConfirmationDialog.close(); }, [removeBucketConfirmationDialog]); @@ -164,23 +181,14 @@ export const BucketLanding = (props: Props) => { )} - - {({ data: orderedData, handleOrderChange, order, orderBy }) => { - const bucketTableProps = { - data: orderedData, - handleClickDetails, - handleClickRemove, - handleOrderChange, - order, - orderBy, - }; - return ; - }} - + {/* If there's more than one Bucket, display the total usage. */} {objectStorageBucketsResponse.buckets.length > 1 ? ( { - const history = useHistory(); - + const navigate = useNavigate(); const isBucketCreationRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_buckets', }); @@ -33,7 +32,7 @@ export const BucketLandingEmptyState = () => { category: linkAnalyticsEvent.category, label: 'Create Bucket', }); - history.replace('/object-storage/buckets/create'); + navigate({ to: '/object-storage/buckets/create' }); }, tooltipText: getRestrictedResourceText({ action: 'create', diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_BucketLanding.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_BucketLanding.tsx index 1ecf2b44180..cf074588211 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_BucketLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_BucketLanding.tsx @@ -7,10 +7,10 @@ import { makeStyles } from 'tss-react/mui'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { Link } from 'src/components/Link'; -import OrderBy from 'src/components/OrderBy'; import { TransferDisplay } from 'src/components/TransferDisplay/TransferDisplay'; import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog'; import { useObjectStorageRegions } from 'src/features/ObjectStorage/hooks/useObjectStorageRegions'; +import { useOrderV2 } from 'src/hooks/useOrderV2'; import { useDeleteBucketWithRegionMutation, useObjectStorageBuckets, @@ -142,6 +142,27 @@ export const OMC_BucketLanding = (props: Props) => { return Array.from(regionMap.values()); }, [objectStorageBucketsResponse, availableStorageRegions]); + const buckets = objectStorageBucketsResponse?.buckets ?? []; + const totalUsage = sumBucketUsage(buckets); + const bucketLabel = selectedBucket ? selectedBucket.label : ''; + + const { + handleOrderChange, + order, + orderBy, + sortedData: orderedData, + } = useOrderV2({ + data: buckets, + initialRoute: { + defaultOrder: { + order: 'asc', + orderBy: 'label', + }, + from: '/object-storage/buckets', + }, + preferenceKey: 'object-storage-buckets', + }); + if (isRestrictedUser) { return ; } @@ -170,10 +191,6 @@ export const OMC_BucketLanding = (props: Props) => { ); } - const buckets = objectStorageBucketsResponse.buckets; - const totalUsage = sumBucketUsage(buckets); - const bucketLabel = selectedBucket ? selectedBucket.label : ''; - return ( { )} - - {({ data: orderedData, handleOrderChange, order, orderBy }) => { - const bucketTableProps = { - data: orderedData, - handleClickDetails, - handleClickRemove, - handleOrderChange, - order, - orderBy, - }; - return ; - }} - + {/* If there's more than one Bucket, display the total usage. */} {buckets.length > 1 ? ( ); export const ObjectStorageLanding = () => { - const history = useHistory(); + const navigate = useNavigate(); + const match = useMatch({ strict: false }); const [mode, setMode] = React.useState('creating'); - const { action, tab } = useParams<{ - action?: 'create'; - tab?: 'access-keys' | 'buckets'; - }>(); const { _isRestrictedUser, @@ -69,19 +66,10 @@ export const ObjectStorageLanding = () => { const userHasNoBucketCreated = objectStorageBucketsResponse?.buckets.length === 0; - const openDrawer = useOpenClose(); - - const tabs = [ - { routeName: `/object-storage/buckets`, title: 'Buckets' }, - { routeName: `/object-storage/access-keys`, title: 'Access Keys' }, - ]; - - const handleOpenAccessDrawer = (mode: MODE) => { - setMode(mode); - openDrawer.open(); - }; - - const navToURL = (index: number) => history.push(tabs[index].routeName); + const { handleTabChange, tabIndex, tabs } = useTabs([ + { title: 'Buckets', to: `/object-storage/buckets` }, + { title: 'Access Keys', to: `/object-storage/access-keys` }, + ]); const objPromotionalOffers = flags.promotionalOffers?.filter((offer) => @@ -101,28 +89,32 @@ export const ObjectStorageLanding = () => { }); const shouldHideDocsAndCreateButtons = - !areBucketsLoading && tab === 'buckets' && userHasNoBucketCreated; + !areBucketsLoading && tabIndex === 0 && userHasNoBucketCreated; - const isAccessKeysTab = tab === 'access-keys'; - const isCreateAction = action === 'create'; + const isAccessKeysTab = tabIndex === 1; const createButtonText = isAccessKeysTab ? 'Create Access Key' : 'Create Bucket'; + const openDrawer = useOpenClose(); + + const handleOpenAccessDrawer = (mode: MODE) => { + setMode(mode); + openDrawer.open(); + }; + const createButtonAction = () => { if (isAccessKeysTab) { - setMode('creating'); - history.replace('/object-storage/access-keys/create'); - openDrawer.open(); + navigate({ to: '/object-storage/access-keys/create' }); } else { - history.replace('/object-storage/buckets/create'); + navigate({ to: '/object-storage/buckets/create' }); } }; - const tabIndex = tab === 'access-keys' ? 1 : 0; - const isCreateBucketOpen = !isAccessKeysTab && isCreateAction; - const isCreateAccessKeyOpen = isAccessKeysTab && isCreateAction; + const isCreateBucketOpen = match.routeId === '/object-storage/buckets/create'; + const isCreateAccessKeyOpen = + match.routeId === '/object-storage/access-keys/create'; return ( @@ -151,8 +143,8 @@ export const ObjectStorageLanding = () => { shouldHideDocsAndCreateButtons={shouldHideDocsAndCreateButtons} title="Object Storage" /> - - + + {objPromotionalOffers.map((promotionalOffer) => ( { { + navigate({ to: '/object-storage/access-keys' }); openDrawer.close(); - history.replace('/object-storage/access-keys'); }} accessDrawerOpen={isCreateAccessKeyOpen || openDrawer.isOpen} isRestrictedUser={_isRestrictedUser} @@ -190,12 +182,12 @@ export const ObjectStorageLanding = () => { {isObjMultiClusterEnabled ? ( history.replace('/object-storage/buckets')} + onClose={() => navigate({ to: '/object-storage/buckets' })} /> ) : ( history.replace('/object-storage/buckets')} + onClose={() => navigate({ to: '/object-storage/buckets' })} /> )} @@ -206,7 +198,7 @@ export const ObjectStorageLanding = () => { const NOTIFICATION_KEY = 'obj-billing-notification'; export const BillingNotice = React.memo(() => { - const history = useHistory(); + const navigate = useNavigate(); return ( { can cancel Object Storage in your{' '} Account Settings, or{' '} history.replace('/object-storage/buckets/create')} + onClick={() => navigate({ to: '/object-storage/buckets/create' })} > create a Bucket. @@ -237,9 +229,3 @@ const StyledPromotionalOfferCard = styled(PromotionalOfferCard, { })(({ theme }) => ({ marginBottom: theme.spacing(0.5), })); - -export const objectStorageLandingLazyRoute = createLazyRoute('/object-storage')( - { - component: ObjectStorageLanding, - } -); diff --git a/packages/manager/src/features/ObjectStorage/index.tsx b/packages/manager/src/features/ObjectStorage/index.tsx deleted file mode 100644 index a20c0dd1cd2..00000000000 --- a/packages/manager/src/features/ObjectStorage/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { Route, Switch } from 'react-router-dom'; - -import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; -import { SuspenseLoader } from 'src/components/SuspenseLoader'; - -import type { RouteComponentProps } from 'react-router-dom'; - -const ObjectStorageLanding = React.lazy(() => - import('./ObjectStorageLanding').then((module) => ({ - default: module.ObjectStorageLanding, - })) -); - -const BucketDetail = React.lazy(() => import('./BucketDetail')); - -type CombinedProps = RouteComponentProps; - -export const ObjectStorage: React.FC = (props) => { - const path = props.match.path; - - return ( - }> - - - - - - - ); -}; - -export default ObjectStorage; diff --git a/packages/manager/src/routes/index.tsx b/packages/manager/src/routes/index.tsx index 7e77a0ee4fd..733c1c17de4 100644 --- a/packages/manager/src/routes/index.tsx +++ b/packages/manager/src/routes/index.tsx @@ -93,6 +93,7 @@ export const migrationRouteTree = migrationRootRoute.addChildren([ imagesRouteTree, longviewRouteTree, nodeBalancersRouteTree, + objectStorageRouteTree, placementGroupsRouteTree, stackScriptsRouteTree, volumesRouteTree, diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index f2ccbfaaec3..0238df938e7 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -3,6 +3,10 @@ import { createRoute } from '@tanstack/react-router'; import { rootRoute } from '../root'; import { ObjectStorageRoute } from './ObjectStorageRoute'; +export interface ObjectStorageDetailSearchParams { + prefix?: string; +} + export const objectStorageRoute = createRoute({ component: ObjectStorageRoute, getParentRoute: () => rootRoute, @@ -13,43 +17,53 @@ const objectStorageIndexRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: '/', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); -const objectStorageBucketsRoute = createRoute({ +const objectStorageBucketsLandingRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'buckets', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); -const objectStorageAccessKeysRoute = createRoute({ +const objectStorageAccessKeysLandingRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'access-keys', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); const objectStorageBucketCreateRoute = createRoute({ - getParentRoute: () => objectStorageBucketsRoute, - path: 'create', + getParentRoute: () => objectStorageRoute, + path: 'buckets/create', +}).lazy(() => + import('./objectStorageLazyRoutes').then( + (m) => m.objectStorageLandingLazyRoute + ) +); + +const objectStorageAccessKeyCreateRoute = createRoute({ + getParentRoute: () => objectStorageRoute, + path: 'access-keys/create', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); const objectStorageBucketDetailRoute = createRoute({ - getParentRoute: () => objectStorageBucketsRoute, - path: '$clusterId/$bucketName', + getParentRoute: () => objectStorageRoute, + path: 'buckets/$clusterId/$bucketName', + validateSearch: (search: ObjectStorageDetailSearchParams) => search, }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -58,7 +72,7 @@ const objectStorageBucketDetailObjectsRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'objects', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -67,7 +81,7 @@ const objectStorageBucketDetailAccessRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'access', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -76,19 +90,20 @@ const objectStorageBucketSSLRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'ssl', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); export const objectStorageRouteTree = objectStorageRoute.addChildren([ - objectStorageIndexRoute, - objectStorageBucketsRoute.addChildren([ - objectStorageBucketDetailRoute.addChildren([ - objectStorageBucketDetailObjectsRoute, - objectStorageBucketDetailAccessRoute, - objectStorageBucketSSLRoute, - ]), + objectStorageIndexRoute.addChildren([ objectStorageBucketCreateRoute, + objectStorageAccessKeyCreateRoute, + objectStorageBucketsLandingRoute, + objectStorageAccessKeysLandingRoute + ]), + objectStorageBucketDetailRoute.addChildren([ + objectStorageBucketDetailObjectsRoute, + objectStorageBucketDetailAccessRoute, + objectStorageBucketSSLRoute, ]), - objectStorageAccessKeysRoute, ]); diff --git a/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts b/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts new file mode 100644 index 00000000000..68b112b495f --- /dev/null +++ b/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts @@ -0,0 +1,16 @@ +import { createLazyRoute } from '@tanstack/react-router'; + +import { BucketDetailLanding } from 'src/features/ObjectStorage/BucketDetail'; +import { ObjectStorageLanding } from 'src/features/ObjectStorage/ObjectStorageLanding'; + +export const objectStorageLandingLazyRoute = createLazyRoute('/object-storage')( + { + component: ObjectStorageLanding, + } +); + +export const bucketDetailLandingLazyRoute = createLazyRoute( + '/object-storage/buckets/$clusterId/$bucketName' +)({ + component: BucketDetailLanding, +});