Skip to content

Commit 2fd97bf

Browse files
committed
improvement(crr): redesign the Configure Location form per UX review
1 parent 03ca9b8 commit 2fd97bf

5 files changed

Lines changed: 173 additions & 100 deletions

File tree

src/react/locations/CRRSetupWizard/steps/ConfigureStep/ConfigureStep.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Form, Icon, Stack, useToast } from '@scality/core-ui';
1+
import { Form, Icon, InfoMessage, Stack, useToast } from '@scality/core-ui';
22
import { useStepper } from '@scality/core-ui/dist/components/steppers/Stepper.component';
33
import { Button } from '@scality/core-ui/dist/next';
44
import { useBasenameRelativeNavigate } from '@scality/module-federation';
@@ -155,13 +155,19 @@ export const ConfigureStep = () => {
155155
</Stack>
156156
}
157157
>
158-
<SourceSection />
158+
<InfoMessage
159+
title="Cross-Region location"
160+
content="A location is created here, on the source site, pointing to a destination site. The destination automatically receives the resources required for replication."
161+
link="/artesca/docs/data_management/location_management/add_a_crr_location.html"
162+
linkText="Learn more"
163+
/>
159164
<DestinationConnectionSection
160165
isCheckingConnection={verify.isLoading}
161166
onCheckConnection={onCheckConnection}
162167
isConnected={isConnected}
163168
connectedInstanceName={connectedInstanceName}
164169
/>
170+
<SourceSection />
165171
<DestinationAccountSection />
166172
<ReplicationSection />
167173
</Form>

src/react/locations/CRRSetupWizard/steps/ConfigureStep/DestinationAccountSection.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1-
import { FormGroup, FormSection } from '@scality/core-ui';
2-
import { Input } from '@scality/core-ui/dist/next';
1+
import { FormGroup, FormSection, Icon, Stack, Text } from '@scality/core-ui';
2+
import { Button, Input } from '@scality/core-ui/dist/next';
33
import { useFormContext } from 'react-hook-form';
44
import type { ConfigureFormValues } from './schema';
55

66
export const DestinationAccountSection = () => {
77
const {
88
register,
9+
setValue,
10+
getValues,
911
formState: { errors, touchedFields },
1012
} = useFormContext<ConfigureFormValues>();
1113
const nameError = touchedFields.destinationAccountName ? errors.destinationAccountName?.message : undefined;
1214

1315
return (
14-
<FormSection forceLabelWidth={280} title={{ name: 'Destination Account' }}>
16+
<FormSection forceLabelWidth={280} title={{ name: 'Destination site' }}>
17+
<Text color="textSecondary">An account will be created on the destination site with this name.</Text>
1518
<FormGroup
1619
id="destinationAccountName"
1720
direction="horizontal"
18-
label="Account Name"
21+
label="Account name"
1922
required
2023
helpErrorPosition="bottom"
2124
error={nameError}
22-
content={<Input id="destinationAccountName" autoComplete="off" {...register('destinationAccountName')} />}
25+
content={
26+
<Stack direction="vertical" gap="r8">
27+
<Input id="destinationAccountName" autoComplete="off" {...register('destinationAccountName')} />
28+
<Button
29+
type="button"
30+
variant="outline"
31+
label="Copy from Source site Account name"
32+
icon={<Icon name="Copy" />}
33+
onClick={() =>
34+
setValue('destinationAccountName', getValues('accountName'), {
35+
shouldValidate: true,
36+
shouldDirty: true,
37+
})
38+
}
39+
/>
40+
</Stack>
41+
}
2342
/>
2443
</FormSection>
2544
);

src/react/locations/CRRSetupWizard/steps/ConfigureStep/DestinationConnectionSection.tsx

Lines changed: 97 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { FormGroup, FormSection, Icon, Stack, Text, Wrap } from '@scality/core-ui';
1+
import { FormGroup, FormSection, Icon, Stack, spacing, Text, Wrap } from '@scality/core-ui';
22
import { Button, Input } from '@scality/core-ui/dist/next';
33
import { Controller, useFormContext } from 'react-hook-form';
4+
import styled from 'styled-components';
45
import { RadioGroup } from '../../../../ISV/components/RadioGroup';
56
import { CertificateSection } from '../../../../ui-elements/CertificateSection';
67
import type { ConfigureFormValues } from './schema';
78

9+
const ConnectionBox = styled.div`
10+
background: ${(props) => props.theme.backgroundLevel2};
11+
border: 1px solid ${(props) => props.theme.border};
12+
border-radius: 6px;
13+
padding: ${spacing.r16};
14+
`;
15+
816
type Props = {
917
isCheckingConnection: boolean;
1018
onCheckConnection: () => void;
@@ -37,73 +45,102 @@ export const DestinationConnectionSection = ({
3745

3846
return (
3947
<FormSection forceLabelWidth={280} title={{ name: 'Destination Connection' }}>
40-
<FormGroup
41-
id="connectionMode"
42-
direction="horizontal"
43-
label="Mode"
44-
required
45-
helpErrorPosition="bottom"
46-
content={
47-
<Controller
48-
name="connectionMode"
49-
control={control}
50-
render={({ field }) => (
51-
<RadioGroup
52-
name="connectionMode"
53-
options={[
54-
{ value: 'management-network', label: 'Management Network' },
55-
{ value: 'data-network', label: 'Data Network' },
56-
]}
57-
value={field.value}
58-
onChange={(next) => field.onChange(next)}
59-
direction="horizontal"
60-
/>
61-
)}
62-
/>
63-
}
64-
/>
65-
{connectionMode === 'management-network' && (
48+
<ConnectionBox>
6649
<FormGroup
67-
id="url"
50+
id="connectionMode"
6851
direction="horizontal"
69-
label="URL"
52+
label="Mode"
7053
required
7154
helpErrorPosition="bottom"
72-
error={errorIfTouched('url')}
73-
content={<Input id="url" noPlaceholderPrefix placeholder="https://<IP>:8443" {...register('url')} />}
74-
/>
75-
)}
76-
{connectionMode === 'data-network' && (
77-
<FormGroup
78-
id="baseDomain"
79-
direction="horizontal"
80-
label="Base Domain"
81-
required
82-
helpErrorPosition="bottom"
83-
error={errorIfTouched('baseDomain')}
8455
content={
85-
<Input id="baseDomain" noPlaceholderPrefix placeholder="ui.<base-domain>" {...register('baseDomain')} />
86-
}
87-
/>
88-
)}
89-
{connectionMode === 'data-network' && (
90-
<FormGroup
91-
id="s3Endpoint"
92-
direction="horizontal"
93-
label="S3 Endpoint"
94-
required
95-
helpErrorPosition="bottom"
96-
error={errorIfTouched('s3Endpoint')}
97-
content={
98-
<Input
99-
id="s3Endpoint"
100-
noPlaceholderPrefix
101-
placeholder="https://s3.example.com"
102-
{...register('s3Endpoint')}
56+
<Controller
57+
name="connectionMode"
58+
control={control}
59+
render={({ field }) => (
60+
<RadioGroup
61+
name="connectionMode"
62+
options={[
63+
{
64+
value: 'management-network',
65+
label: 'Management Network',
66+
description: 'Connects directly to the management IP.',
67+
},
68+
{
69+
value: 'data-network',
70+
label: 'Data Network',
71+
description: 'Goes through the public S3 endpoint instead.',
72+
},
73+
]}
74+
value={field.value}
75+
onChange={(next) => field.onChange(next)}
76+
direction="vertical"
77+
/>
78+
)}
10379
/>
10480
}
10581
/>
106-
)}
82+
{connectionMode === 'management-network' && (
83+
<FormGroup
84+
id="url"
85+
direction="horizontal"
86+
label="URL"
87+
required
88+
helpErrorPosition="bottom"
89+
error={errorIfTouched('url')}
90+
content={
91+
<Input
92+
id="url"
93+
noPlaceholderPrefix
94+
placeholder="https://<IP>:8443"
95+
style={{ maxWidth: '22rem' }}
96+
{...register('url')}
97+
/>
98+
}
99+
/>
100+
)}
101+
{connectionMode === 'data-network' && (
102+
<FormGroup
103+
id="baseDomain"
104+
direction="horizontal"
105+
label="Base Domain"
106+
required
107+
helpErrorPosition="bottom"
108+
error={errorIfTouched('baseDomain')}
109+
content={
110+
<Input
111+
id="baseDomain"
112+
noPlaceholderPrefix
113+
placeholder="ui.<base-domain>"
114+
style={{ maxWidth: '22rem' }}
115+
{...register('baseDomain')}
116+
/>
117+
}
118+
/>
119+
)}
120+
{connectionMode === 'data-network' && (
121+
<FormGroup
122+
id="s3Endpoint"
123+
direction="horizontal"
124+
label="S3 Endpoint"
125+
required
126+
helpErrorPosition="bottom"
127+
error={errorIfTouched('s3Endpoint')}
128+
content={
129+
<Input
130+
id="s3Endpoint"
131+
noPlaceholderPrefix
132+
placeholder="https://s3.example.com"
133+
style={{ maxWidth: '22rem' }}
134+
{...register('s3Endpoint')}
135+
/>
136+
}
137+
/>
138+
)}
139+
</ConnectionBox>
140+
<Text color="textSecondary">
141+
Credentials: these must belong to a user with at least the Storage Manager role on the destination site
142+
deployment.
143+
</Text>
107144
<FormGroup
108145
id="username"
109146
direction="horizontal"

src/react/locations/CRRSetupWizard/steps/ConfigureStep/ReplicationSection.tsx

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Checkbox, FormGroup, FormSection } from '@scality/core-ui';
22
import { Input } from '@scality/core-ui/dist/next';
3+
import { useEffect, useRef } from 'react';
34
import { useFormContext } from 'react-hook-form';
45
import type { ConfigureFormValues } from './schema';
56

@@ -12,47 +13,53 @@ export const ReplicationSection = () => {
1213
const enabled = watch('createReplicationRule');
1314
const errorIfTouched = (field: keyof ConfigureFormValues) =>
1415
touchedFields[field] ? errors[field]?.message : undefined;
16+
const ruleFieldsRef = useRef<HTMLDivElement>(null);
17+
18+
useEffect(() => {
19+
if (enabled) {
20+
ruleFieldsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
21+
}
22+
}, [enabled]);
1523

1624
return (
1725
<FormSection forceLabelWidth={280} title={{ name: 'Replication' }}>
1826
<FormGroup
1927
id="createReplicationRule"
2028
direction="horizontal"
2129
label="Create Replication Rule"
30+
help="Optional — creating a rule now is not required, it can also be set up later from the bucket."
2231
helpErrorPosition="bottom"
2332
content={<Checkbox id="createReplicationRule" {...register('createReplicationRule')} />}
2433
/>
2534
{enabled && (
26-
<FormGroup
27-
id="sourceBucketName"
28-
direction="horizontal"
29-
label="Source Bucket Name"
30-
required
31-
helpErrorPosition="bottom"
32-
error={errorIfTouched('sourceBucketName')}
33-
content={<Input id="sourceBucketName" autoComplete="off" {...register('sourceBucketName')} />}
34-
/>
35-
)}
36-
{enabled && (
37-
<FormGroup
38-
id="targetBucketName"
39-
direction="horizontal"
40-
label="Target Bucket Name"
41-
required
42-
helpErrorPosition="bottom"
43-
error={errorIfTouched('targetBucketName')}
44-
content={<Input id="targetBucketName" autoComplete="off" {...register('targetBucketName')} />}
45-
/>
46-
)}
47-
{enabled && (
48-
<FormGroup
49-
id="prefix"
50-
direction="horizontal"
51-
label="Prefix (optional)"
52-
helpErrorPosition="bottom"
53-
error={errorIfTouched('prefix')}
54-
content={<Input id="prefix" autoComplete="off" {...register('prefix')} />}
55-
/>
35+
<div ref={ruleFieldsRef}>
36+
<FormGroup
37+
id="sourceBucketName"
38+
direction="horizontal"
39+
label="Source Bucket name"
40+
required
41+
helpErrorPosition="bottom"
42+
error={errorIfTouched('sourceBucketName')}
43+
content={<Input id="sourceBucketName" autoComplete="off" {...register('sourceBucketName')} />}
44+
/>
45+
<FormGroup
46+
id="targetBucketName"
47+
direction="horizontal"
48+
label="Target Bucket name"
49+
required
50+
helpErrorPosition="bottom"
51+
error={errorIfTouched('targetBucketName')}
52+
content={<Input id="targetBucketName" autoComplete="off" {...register('targetBucketName')} />}
53+
/>
54+
<FormGroup
55+
id="prefix"
56+
direction="horizontal"
57+
label="Prefix (optional)"
58+
helpErrorPosition="bottom"
59+
error={errorIfTouched('prefix')}
60+
content={<Input id="prefix" autoComplete="off" {...register('prefix')} />}
61+
/>
62+
</div>
5663
)}
5764
</FormSection>
5865
);

src/react/locations/CRRSetupWizard/steps/ConfigureStep/SourceSection.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FormGroup, FormSection } from '@scality/core-ui';
1+
import { FormGroup, FormSection, Text } from '@scality/core-ui';
22
import { Input, Select } from '@scality/core-ui/dist/next';
33
import { useMemo } from 'react';
44
import { Controller, useFormContext } from 'react-hook-form';
@@ -42,7 +42,11 @@ export const SourceSection = () => {
4242
);
4343

4444
return (
45-
<FormSection forceLabelWidth={280} title={{ name: 'Source' }}>
45+
<FormSection forceLabelWidth={280} title={{ name: 'Source site' }}>
46+
<Text color="textSecondary">
47+
Use an existing account or create a new one. If you choose an existing account, data already present in its
48+
buckets won't be recovered by the replication.
49+
</Text>
4650
<FormGroup
4751
id="accountNameType"
4852
direction="horizontal"
@@ -70,7 +74,7 @@ export const SourceSection = () => {
7074
<FormGroup
7175
id="accountName"
7276
direction="horizontal"
73-
label="Account Name"
77+
label="Account name"
7478
required
7579
helpErrorPosition="bottom"
7680
error={accountNameError}

0 commit comments

Comments
 (0)