diff --git a/packages/manager/.changeset/pr-10218-upcoming-features-1708616641655.md b/packages/manager/.changeset/pr-10218-upcoming-features-1708616641655.md new file mode 100644 index 00000000000..203895b0000 --- /dev/null +++ b/packages/manager/.changeset/pr-10218-upcoming-features-1708616641655.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Add scrolling for S3 hostnames in the Access Keys modal. ([#10218](https://github.com/linode/manager/pull/10218)) diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx index 639b5a6d6ac..38d48b1d327 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx @@ -30,8 +30,8 @@ import { AccessKeyDrawer } from './AccessKeyDrawer'; import { AccessKeyTable } from './AccessKeyTable/AccessKeyTable'; import { OMC_AccessKeyDrawer } from './OMC_AccessKeyDrawer'; import { RevokeAccessKeyDialog } from './RevokeAccessKeyDialog'; -import { MODE, OpenAccessDrawer } from './types'; import ViewPermissionsDrawer from './ViewPermissionsDrawer'; +import { MODE, OpenAccessDrawer } from './types'; interface Props { accessDrawerOpen: boolean; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.test.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.test.tsx new file mode 100644 index 00000000000..2056065a2bb --- /dev/null +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.test.tsx @@ -0,0 +1,40 @@ +import { screen } from '@testing-library/react'; +import React from 'react'; + +import { regionFactory } from 'src/factories'; +import { makeResourcePage } from 'src/mocks/serverHandlers'; +import { rest, server } from 'src/mocks/testServer'; +import { renderWithTheme } from 'src/utilities/testHelpers'; + +import { HostNamesList } from './HostNamesList'; + +const mockObjectStorageKey = { + access_key: 'test', + bucket_access: null, + id: 0, + label: 'test-label', + limited: false, + regions: [{ id: 'us-central', s3_endpoint: 'http://example.com' }], + secret_key: 'testKey', +}; + +describe('HostNamesList', () => { + it('renders without crashing', async () => { + server.use( + rest.get('*/regions', (req, res, ctx) => { + const regions = regionFactory.buildList(1, { + id: 'us-central', + label: 'Fake Region, NC', + }); + return res(ctx.json(makeResourcePage(regions))); + }) + ); + + renderWithTheme(); + + const copyButton = await screen.findByLabelText( + 'Copy Fake Region, NC: http://example.com to clipboard' + ); + expect(copyButton).toBeInTheDocument(); + }); +}); diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx new file mode 100644 index 00000000000..965740e38d8 --- /dev/null +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesList.tsx @@ -0,0 +1,84 @@ +import { styled } from '@mui/material/styles'; +import React, { useRef } from 'react'; + +import { Box } from 'src/components/Box'; +import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField'; +import { List } from 'src/components/List'; +import { useRegionsQuery } from 'src/queries/regions'; +import { omittedProps } from 'src/utilities/omittedProps'; +import { getRegionsByRegionId } from 'src/utilities/regions'; + +import type { ObjectStorageKey } from '@linode/api-v4/lib/object-storage'; + +const maxHeight = 200; + +interface Props { + objectStorageKey: ObjectStorageKey; +} + +export const HostNamesList = ({ objectStorageKey }: Props) => { + const { data: regionsData } = useRegionsQuery(); + const regionsLookup = regionsData && getRegionsByRegionId(regionsData); + + const listRef = useRef(null); + const currentListHeight = listRef?.current?.clientHeight || 0; + + return ( + + ({ + backgroundColor: theme.bg.main, + border: `1px solid ${theme.name === 'light' ? '#ccc' : '#222'}`, + minHeight: '34px', + })} + displayShadow={currentListHeight > maxHeight} + > + + + {objectStorageKey?.regions.map((region, index) => ( + + ))} + + + + + ); +}; + +export const StyledScrollBox = styled(Box, { + label: 'StyledScrollBox', +})<{ maxHeight: string }>(({ maxHeight }) => ({ + maxHeight: `${maxHeight}px`, + overflow: 'auto', +})); + +export const StyledBoxShadowWrapper = styled(Box, { + label: 'StyledBoxShadowWrapper', + shouldForwardProp: omittedProps(['displayShadow']), +})<{ displayShadow: boolean }>(({ displayShadow, theme }) => ({ + '&:after': { + bottom: 0, + content: '""', + height: '15px', + position: 'absolute', + width: '100%', + ...(displayShadow && { + boxShadow: `${theme.color.boxShadow} 0px -15px 10px -10px inset`, + }), + }, + position: 'relative', +})); + +export const StyledHostNamesList = styled(List, { + label: 'RegionsList', +})(({ theme }) => ({ + background: theme.name === 'light' ? theme.bg.main : theme.bg.app, +})); diff --git a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx index f49b977a3a0..c4a747978e3 100644 --- a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx +++ b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx @@ -5,8 +5,8 @@ import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; import { Box } from 'src/components/Box'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; import { CopyableAndDownloadableTextField } from 'src/components/CopyableAndDownloadableTextField'; -import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField'; import { Notice } from 'src/components/Notice/Notice'; +import { HostNamesList } from 'src/features/ObjectStorage/AccessKeyLanding/HostNamesList'; import { CopyAllHostnames } from 'src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames'; import { useAccountManagement } from 'src/hooks/useAccountManagement'; import { useFlags } from 'src/hooks/useFlags'; @@ -15,6 +15,7 @@ import { isFeatureEnabled } from 'src/utilities/accountCapabilities'; import { getRegionsByRegionId } from 'src/utilities/regions'; import type { ObjectStorageKey } from '@linode/api-v4/lib/object-storage'; + interface Props { objectStorageKey?: ObjectStorageKey | null; onClose: () => void; @@ -59,6 +60,11 @@ export const SecretTokenDialog = (props: Props) => { return ( ({ + '.MuiPaper-root': { + overflow: 'hidden', + }, + })} actions={actions} disableEscapeKeyDown fullWidth @@ -106,31 +112,9 @@ export const SecretTokenDialog = (props: Props) => { {isObjMultiClusterEnabled && objectStorageKey && objectStorageKey?.regions?.length > 0 && ( - ({ - '.copyIcon': { - marginRight: 0, - paddingRight: 0, - }, - backgroundColor: theme.bg.main, - border: `1px solid ${theme.color.grey3}`, - borderColor: theme.name === 'light' ? '#ccc' : '#222', - padding: theme.spacing(1), - })} - > - {objectStorageKey?.regions.map((region, index) => ( - - ))} - + )} + {objectStorageKey ? ( <>