Skip to content

Commit 8e5bb02

Browse files
cpathipamjac0bs
andauthored
upcoming: [M3-7694] - OBJ Multi Cluster Copy updates (linode#10188)
* Remove text "S3 Endpoint" * Update Error message in Add / Edit Access Key Drawer * Add tooltip and change column name "None" to "No Access" * Added changeset: OBJ Multi Cluster Copy updates. * Update packages/manager/.changeset/pr-10188-upcoming-features-1707837776464.md Co-authored-by: Mariah Jacobs <[email protected]> * Update packages/manager/src/features/ObjectStorage/AccessKeyLanding/LimitedAccessControls.tsx Co-authored-by: Mariah Jacobs <[email protected]> * Hide copy all when one host name exists * Adjust styles for hostname label * Adjust padding --------- Co-authored-by: Mariah Jacobs <[email protected]>
1 parent 42137a0 commit 8e5bb02

File tree

10 files changed

+113
-50
lines changed

10 files changed

+113
-50
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Update OBJ Multi-Cluster copy ([#10188](https://github.com/linode/manager/pull/10188))

packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,16 @@ import { styled } from '@mui/material/styles';
77
import React, { useState } from 'react';
88

99
import { Table } from 'src/components/Table';
10-
10+
import { TableBody } from 'src/components/TableBody';
1111
import { TableCell } from 'src/components/TableCell';
1212
import { TableHead } from 'src/components/TableHead';
1313
import { TableRow } from 'src/components/TableRow';
14-
import { TableBody } from 'src/components/TableBody';
15-
1614
import { useAccountManagement } from 'src/hooks/useAccountManagement';
1715
import { useFlags } from 'src/hooks/useFlags';
1816
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';
1917

2018
import { HostNamesDrawer } from '../HostNamesDrawer';
2119
import { OpenAccessDrawer } from '../types';
22-
2320
import { AccessKeyTableBody } from './AccessKeyTableBody';
2421

2522
export interface AccessKeyTableProps {

packages/manager/src/features/ObjectStorage/AccessKeyLanding/BucketPermissionsTable.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,15 @@ export const BucketPermissionsTable = React.memo((props: Props) => {
8686
return (
8787
<StyledTableRoot
8888
aria-label="Object Storage Access Key Permissions"
89-
spacingTop={24}
89+
spacingTop={16}
9090
>
9191
<TableHead>
9292
<TableRow>
9393
<TableCell data-qa-perm-region>Region</TableCell>
9494
<TableCell data-qa-perm-bucket>Bucket</TableCell>
95-
<TableCell data-qa-perm-none>None</TableCell>
95+
<TableCell data-qa-perm-none sx={{ minWidth: '100px' }}>
96+
No Access
97+
</TableCell>
9698
<TableCell data-qa-perm-read sx={{ minWidth: '100px' }}>
9799
Read Only
98100
</TableCell>
@@ -170,7 +172,10 @@ export const BucketPermissionsTable = React.memo((props: Props) => {
170172
key={scopeName}
171173
mode={mode}
172174
>
173-
<StyledClusterCell padding="checkbox">
175+
<StyledClusterCell
176+
padding="checkbox"
177+
sx={{ minWidth: '150px' }}
178+
>
174179
{regionsLookup[thisScope.region ?? '']?.label}
175180
</StyledClusterCell>
176181
<StyledBucketCell padding="checkbox">

packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAll.tsx renamed to packages/manager/src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames.tsx

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import { InputLabel } from 'src/components/InputLabel';
88
import { Tooltip } from 'src/components/Tooltip';
99

1010
export interface Props {
11+
hideShowAll?: boolean;
1112
text: string;
1213
}
1314

14-
export const CopyAll = (props: Props) => {
15+
export const CopyAllHostnames = (props: Props) => {
1516
const [copied, setCopied] = React.useState<boolean>(false);
16-
const { text } = props;
17+
const { hideShowAll = false, text } = props;
1718

1819
const handleIconClick = () => {
1920
setCopied(true);
@@ -23,28 +24,31 @@ export const CopyAll = (props: Props) => {
2324

2425
return (
2526
<StyledBox>
26-
<InputLabel>S3 Endpoint Hostnames</InputLabel>
27-
<Tooltip
28-
className="copy-tooltip"
29-
data-qa-copied
30-
placement="top"
31-
title={copied ? 'Copied!' : 'Copy'}
32-
>
33-
<StyledLinkButton
34-
aria-label={`Copy ${text} to clipboard`}
35-
name={text}
36-
onClick={handleIconClick}
37-
type="button"
27+
<InputLabel sx={{ margin: 0 }}>S3 Endpoint Hostnames</InputLabel>
28+
{!hideShowAll && (
29+
<Tooltip
30+
className="copy-tooltip"
31+
data-qa-copied
32+
placement="top"
33+
title={copied ? 'Copied!' : 'Copy'}
3834
>
39-
Copy all
40-
</StyledLinkButton>
41-
</Tooltip>
35+
<StyledLinkButton
36+
aria-label={`Copy ${text} to clipboard`}
37+
name={text}
38+
onClick={handleIconClick}
39+
type="button"
40+
>
41+
Copy all
42+
</StyledLinkButton>
43+
</Tooltip>
44+
)}
4245
</StyledBox>
4346
);
4447
};
4548

4649
const StyledBox = styled(Box, { label: 'StyledBox' })(({ theme }) => ({
47-
borderColor: theme.name === 'light' ? '#ccc' : '#222',
50+
borderColor: theme.name === 'light' ? theme.color.grey3 : theme.color.black,
4851
display: 'flex',
4952
justifyContent: 'space-between',
53+
marginBottom: theme.spacing(1),
5054
}));

packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ describe('HostNamesDrawer', () => {
5353
).toBeInTheDocument();
5454
expect(
5555
screen.getByRole('button', {
56-
name: 'Copy S3 Endpoint: Atlanta, GA: endpoint2 to clipboard',
56+
name: 'Copy Atlanta, GA: endpoint2 to clipboard',
5757
})
5858
).toBeInTheDocument();
5959
expect(
6060
screen.getByRole('button', {
61-
name: 'Copy S3 Endpoint: Newark, NJ: endpoint1 to clipboard',
61+
name: 'Copy Newark, NJ: endpoint1 to clipboard',
6262
})
6363
).toBeInTheDocument();
6464
});

packages/manager/src/features/ObjectStorage/AccessKeyLanding/HostNamesDrawer.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Drawer } from 'src/components/Drawer';
77
import { useRegionsQuery } from 'src/queries/regions';
88
import { getRegionsByRegionId } from 'src/utilities/regions';
99

10-
import { CopyAll } from './CopyAll';
10+
import { CopyAllHostnames } from './CopyAllHostnames';
1111

1212
interface Props {
1313
onClose: () => void;
@@ -27,14 +27,12 @@ export const HostNamesDrawer = (props: Props) => {
2727
return (
2828
<Drawer onClose={onClose} open={open} title="Regions / S3 Hostnames">
2929
<Box sx={(theme) => ({ marginTop: theme.spacing(3) })}>
30-
<CopyAll
30+
<CopyAllHostnames
3131
text={
3232
regions
3333
.map(
3434
(region) =>
35-
`S3 Endpoint: ${regionsLookup[region.id]?.label}: ${
36-
region.s3_endpoint
37-
}`
35+
`${regionsLookup[region.id]?.label}: ${region.s3_endpoint}`
3836
)
3937
.join('\n') ?? ''
4038
}
@@ -49,13 +47,11 @@ export const HostNamesDrawer = (props: Props) => {
4947
>
5048
{regions.map((region, index) => (
5149
<CopyableTextField
52-
value={`S3 Endpoint: ${regionsLookup[region.id]?.label}: ${
53-
region.s3_endpoint
54-
}`}
5550
hideLabel
5651
key={index}
5752
label={`${region.id}: ${region.s3_endpoint}`}
5853
sx={{ border: 'none', maxWidth: '100%' }}
54+
value={`${regionsLookup[region.id]?.label}: ${region.s3_endpoint}`}
5955
/>
6056
))}
6157
</Box>

packages/manager/src/features/ObjectStorage/AccessKeyLanding/LimitedAccessControls.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,28 @@ import { Toggle } from 'src/components/Toggle/Toggle';
66
import { Typography } from 'src/components/Typography';
77
import { useAccountManagement } from 'src/hooks/useAccountManagement';
88
import { useFlags } from 'src/hooks/useFlags';
9+
import { TooltipIcon } from 'src/components/TooltipIcon';
910
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';
1011

1112
import { AccessTable } from './AccessTable';
1213
import { BucketPermissionsTable } from './BucketPermissionsTable';
1314
import { MODE } from './types';
1415

16+
type LabelWithTooltipProps = {
17+
labelText: string;
18+
tooltipText: string;
19+
};
20+
21+
const LabelWithTooltip = ({
22+
labelText,
23+
tooltipText,
24+
}: LabelWithTooltipProps) => (
25+
<React.Fragment>
26+
<Typography component="span">{labelText}</Typography>
27+
{tooltipText && <TooltipIcon status="help" text={tooltipText} />}
28+
</React.Fragment>
29+
);
30+
1531
interface Props {
1632
bucket_access: Scope[] | null;
1733
checked: boolean;
@@ -36,6 +52,10 @@ export const LimitedAccessControls = React.memo((props: Props) => {
3652
return (
3753
<>
3854
<FormControlLabel
55+
sx={(theme) => ({
56+
marginTop: theme.spacing(0.5),
57+
marginBottom: theme.spacing(0.5),
58+
})}
3959
control={
4060
<Toggle
4161
checked={checked}
@@ -44,7 +64,16 @@ export const LimitedAccessControls = React.memo((props: Props) => {
4464
onChange={handleToggle}
4565
/>
4666
}
47-
label={'Limited Access'}
67+
label={
68+
isObjMultiClusterEnabled ? (
69+
<LabelWithTooltip
70+
labelText="Limited Access"
71+
tooltipText="A Limited Access key has no permissions and you can manually set them. If you don't turn on Limited Access, the key is granted full permission in all regions."
72+
/>
73+
) : (
74+
'Limited Access'
75+
)
76+
}
4877
/>
4978
<Typography>
5079
Limited access keys can list all buckets, regardless of access. They can

packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import {
77
Scope,
88
UpdateObjectStorageKeyRequest,
99
} from '@linode/api-v4/lib/object-storage';
10-
import { createObjectStorageKeysSchema } from '@linode/validation/lib/objectStorageKeys.schema';
11-
import { useFormik, FormikProps } from 'formik';
10+
import {
11+
createObjectStorageKeysSchema,
12+
updateObjectStorageKeysSchema,
13+
} from '@linode/validation/lib/objectStorageKeys.schema';
14+
import { FormikProps, useFormik } from 'formik';
1215
import React, { useEffect, useState } from 'react';
1316

1417
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
@@ -166,7 +169,9 @@ export const OMC_AccessKeyDrawer = (props: AccessKeyDrawerProps) => {
166169
},
167170
validateOnBlur: true,
168171
validateOnChange: false,
169-
validationSchema: createObjectStorageKeysSchema,
172+
validationSchema: createMode
173+
? createObjectStorageKeysSchema
174+
: updateObjectStorageKeysSchema,
170175
});
171176

172177
const isSaveDisabled =
@@ -295,6 +300,17 @@ export const OMC_AccessKeyDrawer = (props: AccessKeyDrawerProps) => {
295300
required
296301
selectedRegion={formik.values.regions}
297302
/>
303+
{createMode && (
304+
<Typography
305+
sx={(theme) => ({
306+
marginTop: theme.spacing(2),
307+
})}
308+
>
309+
Unlimited S3 access key can be used to create buckets in the
310+
selected region using S3 Endpoint returned on successful creation
311+
of the key.
312+
</Typography>
313+
)}
298314
{createMode && !bucketsError && (
299315
<LimitedAccessControls
300316
bucket_access={formik.values.bucket_access}

packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { ConfirmationDialog } from 'src/components/ConfirmationDialog/Confirmati
77
import { CopyableAndDownloadableTextField } from 'src/components/CopyableAndDownloadableTextField';
88
import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField';
99
import { Notice } from 'src/components/Notice/Notice';
10-
import { CopyAll } from 'src/features/ObjectStorage/AccessKeyLanding/CopyAll';
10+
import { CopyAllHostnames } from 'src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames';
1111
import { useAccountManagement } from 'src/hooks/useAccountManagement';
1212
import { useFlags } from 'src/hooks/useFlags';
13+
import { useRegionsQuery } from 'src/queries/regions';
1314
import { isFeatureEnabled } from 'src/utilities/accountCapabilities';
15+
import { getRegionsByRegionId } from 'src/utilities/regions';
1416

1517
import type { ObjectStorageKey } from '@linode/api-v4/lib/object-storage';
1618
interface Props {
@@ -37,6 +39,9 @@ const renderActions = (
3739
export const SecretTokenDialog = (props: Props) => {
3840
const { objectStorageKey, onClose, open, title, value } = props;
3941

42+
const { data: regionsData } = useRegionsQuery();
43+
const regionsLookup = regionsData && getRegionsByRegionId(regionsData);
44+
4045
const flags = useFlags();
4146
const { account } = useAccountManagement();
4247

@@ -73,11 +78,17 @@ export const SecretTokenDialog = (props: Props) => {
7378
/>
7479
{isObjMultiClusterEnabled && (
7580
<div>
76-
<CopyAll
81+
<CopyAllHostnames
82+
hideShowAll={Boolean(
83+
objectStorageKey && objectStorageKey?.regions?.length <= 1
84+
)}
7785
text={
7886
objectStorageKey?.regions
7987
.map(
80-
(region) => `S3 Endpoint: ${region.id}: ${region.s3_endpoint}`
88+
(region) =>
89+
`${regionsLookup?.[region.id]?.label}: ${
90+
region.s3_endpoint
91+
}`
8192
)
8293
.join('\n') ?? ''
8394
}
@@ -87,23 +98,20 @@ export const SecretTokenDialog = (props: Props) => {
8798
{isObjMultiClusterEnabled && (
8899
<Box
89100
sx={(theme) => ({
90-
'.copyIcon': {
91-
marginRight: 0,
92-
paddingRight: 0,
93-
},
94101
backgroundColor: theme.bg.main,
95102
border: `1px solid ${theme.color.grey3}`,
96103
borderColor: theme.name === 'light' ? '#ccc' : '#222',
97-
padding: theme.spacing(1),
98104
})}
99105
>
100106
{objectStorageKey?.regions.map((region, index) => (
101107
<CopyableTextField
108+
value={`${regionsLookup?.[region.id]?.label}: ${
109+
region.s3_endpoint
110+
}`}
102111
hideLabel
103112
key={index}
104113
label="Create a Filesystem"
105114
sx={{ border: 'none', maxWidth: '100%' }}
106-
value={`S3 Endpoint: ${region.id}: ${region.s3_endpoint}`}
107115
/>
108116
))}
109117
</Box>

packages/validation/src/objectStorageKeys.schema.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const createObjectStorageKeysSchema = object({
1010
.trim(),
1111
regions: array()
1212
.of(string())
13-
.min(1, 'Regions must include at least one region')
13+
.min(1, 'Select at least one region to continue')
1414
.notRequired(),
1515
});
1616

@@ -22,6 +22,9 @@ export const updateObjectStorageKeysSchema = object({
2222
.trim(),
2323
regions: array()
2424
.of(string())
25-
.min(1, 'Regions must include at least one region')
25+
.min(
26+
1,
27+
'You need to select at least one region. To delete all keys, go to the Access Keys page in Cloud Manager and select Revoke.'
28+
)
2629
.notRequired(),
2730
});

0 commit comments

Comments
 (0)