@scality/data-browser-library 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/buckets/BucketCorsPage.js +1 -1
- package/dist/components/buckets/BucketCreate.js +4 -0
- package/dist/components/buckets/BucketLifecycleFormPage.js +20 -2
- package/dist/components/buckets/BucketLifecycleList.js +12 -14
- package/dist/components/buckets/BucketPolicyPage.js +2 -2
- package/dist/components/buckets/BucketReplicationList.js +6 -3
- package/dist/components/objects/ObjectDetails/ObjectMetadata.js +30 -4
- package/dist/components/objects/ObjectDetails/ObjectTags.js +10 -2
- package/dist/components/objects/ObjectDetails/formUtils.d.ts +15 -0
- package/dist/components/objects/ObjectDetails/formUtils.js +7 -0
- package/dist/types/index.d.ts +3 -3
- package/package.json +1 -1
|
@@ -80,6 +80,8 @@ const BucketCreate = ({ subTitle, validationSchema, validationContext, defaultVa
|
|
|
80
80
|
const { register, handleSubmit, formState, watch } = useFormMethods;
|
|
81
81
|
const { isValid, errors } = formState;
|
|
82
82
|
const isObjectLockEnabled = watch('isObjectLockEnabled');
|
|
83
|
+
const isEncryptionEnabled = watch('isEncryptionEnabled');
|
|
84
|
+
const isVersioning = watch('isVersioning');
|
|
83
85
|
const { s3Capabilities } = useDataBrowserConfig();
|
|
84
86
|
const { mutate: createBucket, isPending: isCreatingBucket } = useCreateBucket();
|
|
85
87
|
const { mutate: setBucketVersioning } = useSetBucketVersioning();
|
|
@@ -242,6 +244,7 @@ const BucketCreate = ({ subTitle, validationSchema, validationContext, defaultVa
|
|
|
242
244
|
helpErrorPosition: "bottom",
|
|
243
245
|
content: /*#__PURE__*/ jsx(Checkbox, {
|
|
244
246
|
id: "isEncryptionEnabled",
|
|
247
|
+
label: isEncryptionEnabled ? 'Enabled' : 'Disabled',
|
|
245
248
|
...register('isEncryptionEnabled')
|
|
246
249
|
})
|
|
247
250
|
}),
|
|
@@ -263,6 +266,7 @@ const BucketCreate = ({ subTitle, validationSchema, validationContext, defaultVa
|
|
|
263
266
|
helpErrorPosition: "bottom",
|
|
264
267
|
content: /*#__PURE__*/ jsx(Checkbox, {
|
|
265
268
|
id: "isVersioning",
|
|
269
|
+
label: isVersioning ? 'Enabled' : 'Disabled',
|
|
266
270
|
disabled: isObjectLockEnabled,
|
|
267
271
|
...register('isVersioning')
|
|
268
272
|
})
|
|
@@ -257,9 +257,27 @@ const generateStorageClassHelpText = ()=>{
|
|
|
257
257
|
const hints = [];
|
|
258
258
|
storageClassOptions.forEach((option)=>{
|
|
259
259
|
const minDays = STORAGE_CLASS_MIN_DAYS[option.value];
|
|
260
|
-
if (minDays > 0) hints.push(
|
|
260
|
+
if (minDays > 0) hints.push({
|
|
261
|
+
label: option.label,
|
|
262
|
+
minDays
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
if (0 === hints.length) return;
|
|
266
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
267
|
+
children: [
|
|
268
|
+
"Storage class requirements:",
|
|
269
|
+
/*#__PURE__*/ jsx("ul", {
|
|
270
|
+
children: hints.map((hint)=>/*#__PURE__*/ jsxs("li", {
|
|
271
|
+
children: [
|
|
272
|
+
hint.label,
|
|
273
|
+
": min ",
|
|
274
|
+
hint.minDays,
|
|
275
|
+
" days"
|
|
276
|
+
]
|
|
277
|
+
}, hint.label))
|
|
278
|
+
})
|
|
279
|
+
]
|
|
261
280
|
});
|
|
262
|
-
return hints.length > 0 ? `Storage class requirements:<ul>${hints.join('')}</ul>` : void 0;
|
|
263
281
|
};
|
|
264
282
|
function BucketLifecycleFormPage() {
|
|
265
283
|
const { bucketName, ruleId } = useParams();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ConstrainedText, FormattedDateTime, Icon, Stack, Tooltip,
|
|
2
|
+
import { ConstrainedText, FormattedDateTime, Icon, Stack, Tooltip, spacing } from "@scality/core-ui";
|
|
3
3
|
import { Box, Button, Table } from "@scality/core-ui/dist/next";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
5
|
import { useDeleteBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
|
|
@@ -242,20 +242,18 @@ function BucketLifecycleList({ bucketName, lifecycleRules, lifecycleStatus, onCr
|
|
|
242
242
|
}
|
|
243
243
|
},
|
|
244
244
|
children: [
|
|
245
|
-
/*#__PURE__*/ jsx(
|
|
245
|
+
/*#__PURE__*/ jsx(Box, {
|
|
246
|
+
display: "flex",
|
|
247
|
+
justifyContent: "flex-end",
|
|
246
248
|
padding: spacing.r16,
|
|
247
|
-
children: /*#__PURE__*/ jsx(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
variant: "primary",
|
|
256
|
-
onClick: onCreateRule,
|
|
257
|
-
type: "button"
|
|
258
|
-
})
|
|
249
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
250
|
+
icon: /*#__PURE__*/ jsx(Icon, {
|
|
251
|
+
name: "Create-add"
|
|
252
|
+
}),
|
|
253
|
+
label: "Create Rule",
|
|
254
|
+
variant: "primary",
|
|
255
|
+
onClick: onCreateRule,
|
|
256
|
+
type: "button"
|
|
259
257
|
})
|
|
260
258
|
}),
|
|
261
259
|
/*#__PURE__*/ jsx(Table.SingleSelectableContent, {
|
|
@@ -110,7 +110,7 @@ const BucketPolicyPage = ()=>{
|
|
|
110
110
|
},
|
|
111
111
|
children: [
|
|
112
112
|
/*#__PURE__*/ jsx(Icon, {
|
|
113
|
-
name: "Exclamation-
|
|
113
|
+
name: "Exclamation-circle",
|
|
114
114
|
size: "2x",
|
|
115
115
|
color: "statusWarning"
|
|
116
116
|
}),
|
|
@@ -158,7 +158,7 @@ const BucketPolicyPage = ()=>{
|
|
|
158
158
|
variant: "warning",
|
|
159
159
|
icon: /*#__PURE__*/ jsx(Icon, {
|
|
160
160
|
color: "statusWarning",
|
|
161
|
-
name: "Exclamation-
|
|
161
|
+
name: "Exclamation-circle"
|
|
162
162
|
}),
|
|
163
163
|
children: /*#__PURE__*/ jsxs(Text, {
|
|
164
164
|
children: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ConstrainedText, Icon,
|
|
2
|
+
import { ConstrainedText, Icon, spacing } from "@scality/core-ui";
|
|
3
3
|
import { Box, Button, Table } from "@scality/core-ui/dist/next";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
5
|
import { useDeleteBucketReplication, useSetBucketReplication } from "../../hooks/bucketConfiguration.js";
|
|
@@ -166,7 +166,9 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
|
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
168
|
children: [
|
|
169
|
-
/*#__PURE__*/ jsx(
|
|
169
|
+
/*#__PURE__*/ jsx(Box, {
|
|
170
|
+
display: "flex",
|
|
171
|
+
justifyContent: "flex-end",
|
|
170
172
|
padding: spacing.r16,
|
|
171
173
|
children: /*#__PURE__*/ jsx(Button, {
|
|
172
174
|
icon: /*#__PURE__*/ jsx(Icon, {
|
|
@@ -174,7 +176,8 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
|
|
|
174
176
|
}),
|
|
175
177
|
label: "Create Rule",
|
|
176
178
|
variant: "primary",
|
|
177
|
-
onClick: onCreateRule
|
|
179
|
+
onClick: onCreateRule,
|
|
180
|
+
type: "button"
|
|
178
181
|
})
|
|
179
182
|
}),
|
|
180
183
|
/*#__PURE__*/ jsx(Table.SingleSelectableContent, {
|
|
@@ -8,6 +8,7 @@ import { useCopyObject, useObjectMetadata } from "../../../hooks/index.js";
|
|
|
8
8
|
import { useInvalidateQueries } from "../../providers/DataBrowserProvider.js";
|
|
9
9
|
import { ArrayFieldActions } from "../../ui/ArrayFieldActions.js";
|
|
10
10
|
import { TableContainer } from "../../ui/Table.elements.js";
|
|
11
|
+
import { hasFormDataChanged } from "./formUtils.js";
|
|
11
12
|
const METADATA_KEYS = [
|
|
12
13
|
{
|
|
13
14
|
key: 'CacheControl',
|
|
@@ -47,6 +48,7 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
47
48
|
const { showToast } = useToast();
|
|
48
49
|
const invalidateQueries = useInvalidateQueries();
|
|
49
50
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
51
|
+
const [originalMetadata, setOriginalMetadata] = useState([]);
|
|
50
52
|
const { control, handleSubmit, setValue, watch, formState: { errors, isValid, isSubmitting } } = useForm({
|
|
51
53
|
mode: 'onChange',
|
|
52
54
|
defaultValues: {
|
|
@@ -57,6 +59,14 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
57
59
|
control,
|
|
58
60
|
name: 'metadata'
|
|
59
61
|
});
|
|
62
|
+
const normalizeMetadata = (metadata)=>metadata.filter((row)=>{
|
|
63
|
+
const hasKey = 'x-amz-meta' === row.keyType ? row.customKey.trim() : row.keyType;
|
|
64
|
+
return hasKey && row.value.trim();
|
|
65
|
+
}).map((row)=>({
|
|
66
|
+
key: 'x-amz-meta' === row.keyType ? `x-amz-meta-${row.customKey.trim()}` : row.keyType,
|
|
67
|
+
value: row.value.trim()
|
|
68
|
+
})).sort((a, b)=>a.key.localeCompare(b.key));
|
|
69
|
+
const hasChanges = ()=>hasFormDataChanged(watch('metadata'), originalMetadata, normalizeMetadata);
|
|
60
70
|
useEffect(()=>{
|
|
61
71
|
setIsInitialized(false);
|
|
62
72
|
copyObjectMutation.reset();
|
|
@@ -97,6 +107,7 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
97
107
|
value: ''
|
|
98
108
|
});
|
|
99
109
|
setValue('metadata', rows);
|
|
110
|
+
setOriginalMetadata(rows);
|
|
100
111
|
setIsInitialized(true);
|
|
101
112
|
}
|
|
102
113
|
}, [
|
|
@@ -113,7 +124,10 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
113
124
|
const onSubmit = async (data)=>{
|
|
114
125
|
if (!metadata) return;
|
|
115
126
|
try {
|
|
116
|
-
const validMetadata = data.metadata.filter((row)=>
|
|
127
|
+
const validMetadata = data.metadata.filter((row)=>{
|
|
128
|
+
const hasKey = 'x-amz-meta' === row.keyType ? row.customKey.trim() : row.keyType;
|
|
129
|
+
return hasKey && row.value.trim();
|
|
130
|
+
});
|
|
117
131
|
const encodedKey = encodeURIComponent(objectKey);
|
|
118
132
|
const copySource = versionId ? `${bucketName}/${encodedKey}?versionId=${versionId}` : `${bucketName}/${encodedKey}`;
|
|
119
133
|
const copyParams = {
|
|
@@ -189,7 +203,7 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
189
203
|
/*#__PURE__*/ jsx(Button, {
|
|
190
204
|
variant: "primary",
|
|
191
205
|
label: "Save",
|
|
192
|
-
disabled: !isValid || isSubmitting,
|
|
206
|
+
disabled: !isValid || isSubmitting || !hasChanges(),
|
|
193
207
|
type: "submit",
|
|
194
208
|
icon: /*#__PURE__*/ jsx(Icon, {
|
|
195
209
|
name: "Save"
|
|
@@ -321,8 +335,20 @@ const ObjectMetadata = ({ bucketName, objectKey, versionId })=>{
|
|
|
321
335
|
customKey: '',
|
|
322
336
|
value: ''
|
|
323
337
|
}),
|
|
324
|
-
canRemove:
|
|
325
|
-
|
|
338
|
+
canRemove: (()=>{
|
|
339
|
+
const keyType = watch(`metadata.${index}.keyType`);
|
|
340
|
+
const customKey = watch(`metadata.${index}.customKey`);
|
|
341
|
+
const value = watch(`metadata.${index}.value`);
|
|
342
|
+
const hasKey = 'x-amz-meta' === keyType ? customKey : keyType;
|
|
343
|
+
return !!hasKey && !!value;
|
|
344
|
+
})(),
|
|
345
|
+
canAdd: (()=>{
|
|
346
|
+
const keyType = watch(`metadata.${index}.keyType`);
|
|
347
|
+
const customKey = watch(`metadata.${index}.customKey`);
|
|
348
|
+
const value = watch(`metadata.${index}.value`);
|
|
349
|
+
const hasKey = 'x-amz-meta' === keyType ? customKey : keyType;
|
|
350
|
+
return !!hasKey && !!value;
|
|
351
|
+
})(),
|
|
326
352
|
removeLabel: "Remove metadata",
|
|
327
353
|
addLabel: "Add metadata"
|
|
328
354
|
})
|
|
@@ -7,6 +7,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
|
|
7
7
|
import { useDeleteObjectTagging, useObjectTagging, useSetObjectTagging } from "../../../hooks/index.js";
|
|
8
8
|
import { useInvalidateQueries } from "../../providers/DataBrowserProvider.js";
|
|
9
9
|
import { ArrayFieldActions } from "../../ui/ArrayFieldActions.js";
|
|
10
|
+
import { hasFormDataChanged } from "./formUtils.js";
|
|
10
11
|
const MAX_TAGS_PER_OBJECT = 10;
|
|
11
12
|
const MAX_TAG_KEY_LENGTH = 128;
|
|
12
13
|
const MAX_TAG_VALUE_LENGTH = 256;
|
|
@@ -24,6 +25,7 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
24
25
|
const { showToast } = useToast();
|
|
25
26
|
const invalidateQueries = useInvalidateQueries();
|
|
26
27
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
28
|
+
const [originalTags, setOriginalTags] = useState([]);
|
|
27
29
|
const { control, handleSubmit, setValue, watch, formState: { errors, isValid, isSubmitting } } = useForm({
|
|
28
30
|
mode: 'onChange',
|
|
29
31
|
defaultValues: {
|
|
@@ -34,6 +36,11 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
34
36
|
control,
|
|
35
37
|
name: 'tags'
|
|
36
38
|
});
|
|
39
|
+
const normalizeTags = (tags)=>tags.filter((tag)=>tag.key.trim() && tag.value.trim()).map((tag)=>({
|
|
40
|
+
key: tag.key.trim(),
|
|
41
|
+
value: tag.value.trim()
|
|
42
|
+
})).sort((a, b)=>a.key.localeCompare(b.key));
|
|
43
|
+
const hasChanges = ()=>hasFormDataChanged(watch('tags'), originalTags, normalizeTags);
|
|
37
44
|
useEffect(()=>{
|
|
38
45
|
setIsInitialized(false);
|
|
39
46
|
setTaggingMutation.reset();
|
|
@@ -55,6 +62,7 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
55
62
|
value: ''
|
|
56
63
|
});
|
|
57
64
|
setValue('tags', rows);
|
|
65
|
+
setOriginalTags(rows);
|
|
58
66
|
setIsInitialized(true);
|
|
59
67
|
}
|
|
60
68
|
}, [
|
|
@@ -138,7 +146,7 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
138
146
|
/*#__PURE__*/ jsx(Button, {
|
|
139
147
|
variant: "primary",
|
|
140
148
|
label: "Save",
|
|
141
|
-
disabled: !isValid || isSubmitting,
|
|
149
|
+
disabled: !isValid || isSubmitting || !hasChanges(),
|
|
142
150
|
type: "submit",
|
|
143
151
|
icon: /*#__PURE__*/ jsx(Icon, {
|
|
144
152
|
name: "Save"
|
|
@@ -241,7 +249,7 @@ const ObjectTags = ({ bucketName, objectKey, versionId })=>{
|
|
|
241
249
|
key: '',
|
|
242
250
|
value: ''
|
|
243
251
|
}),
|
|
244
|
-
canRemove:
|
|
252
|
+
canRemove: !!watch(`tags.${index}.key`) && !!watch(`tags.${index}.value`),
|
|
245
253
|
canAdd: fields.length < MAX_TAGS_PER_OBJECT && !!watch(`tags.${index}.key`) && !!watch(`tags.${index}.value`),
|
|
246
254
|
removeLabel: "Remove tag",
|
|
247
255
|
addLabel: "Add tag"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface NormalizedItem {
|
|
2
|
+
key: string;
|
|
3
|
+
value: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Detects if form data has actually changed by comparing normalized values.
|
|
7
|
+
* Filters out empty entries and compares trimmed, sorted values.
|
|
8
|
+
*
|
|
9
|
+
* @param currentData - current form values
|
|
10
|
+
* @param originalData - original form values
|
|
11
|
+
* @param normalizer - function to normalize field values for comparison
|
|
12
|
+
* @returns true if data has changed, false otherwise
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasFormDataChanged<T>(currentData: T[], originalData: T[], normalizer: (data: T[]) => NormalizedItem[]): boolean;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
function hasFormDataChanged(currentData, originalData, normalizer) {
|
|
2
|
+
const normalizedCurrent = normalizer(currentData);
|
|
3
|
+
const normalizedOriginal = normalizer(originalData);
|
|
4
|
+
if (normalizedCurrent.length !== normalizedOriginal.length) return true;
|
|
5
|
+
return normalizedCurrent.some((item, index)=>item.key !== normalizedOriginal[index]?.key || item.value !== normalizedOriginal[index]?.value);
|
|
6
|
+
}
|
|
7
|
+
export { hasFormDataChanged };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { S3ClientConfig } from
|
|
2
|
-
import type { AwsCredentialIdentity } from
|
|
3
|
-
import type { ProxyConfiguration, S3EventType } from
|
|
1
|
+
import type { S3ClientConfig } from '@aws-sdk/client-s3';
|
|
2
|
+
import type { AwsCredentialIdentity } from '@aws-sdk/types';
|
|
3
|
+
import type { ProxyConfiguration, S3EventType } from '../config/types';
|
|
4
4
|
export type { _Object, Bucket, BucketCannedACL, BucketLocationConstraint, CopyObjectCommandInput, CopyObjectCommandOutput, CreateBucketCommandInput, CreateBucketCommandOutput, DeleteBucketCommandInput, DeleteBucketCommandOutput, DeleteBucketCorsCommandOutput, DeleteBucketLifecycleCommandOutput, DeleteBucketPolicyCommandOutput, DeleteBucketReplicationCommandOutput, DeleteBucketTaggingCommandOutput, DeleteMarkerEntry, DeleteObjectCommandInput, DeleteObjectCommandOutput, DeleteObjectsCommandInput, DeleteObjectsCommandOutput, DeleteObjectTaggingCommandOutput, GetBucketAclCommandOutput, GetBucketCorsCommandOutput, GetBucketEncryptionCommandOutput, GetBucketLifecycleConfigurationCommandOutput, GetBucketLocationCommandInput, GetBucketLocationCommandOutput, GetBucketNotificationConfigurationCommandOutput, GetBucketPolicyCommandOutput, GetBucketReplicationCommandOutput, GetBucketTaggingCommandOutput, GetBucketVersioningCommandOutput, GetObjectAclCommandOutput, GetObjectAttributesCommandInput, GetObjectAttributesCommandOutput, GetObjectCommandInput, GetObjectCommandOutput, GetObjectLegalHoldCommandOutput, GetObjectLockConfigurationCommandOutput, GetObjectRetentionCommandOutput, GetObjectTaggingCommandOutput, GetObjectTorrentCommandOutput, HeadObjectCommandInput, HeadObjectCommandOutput, ListBucketsCommandOutput, ListMultipartUploadsCommandInput, ListMultipartUploadsCommandOutput, ListObjectsV2CommandInput, ListObjectsV2CommandOutput, ListObjectVersionsCommandInput, ListObjectVersionsCommandOutput, ObjectCannedACL, ObjectVersion, Owner, PutBucketAclCommandInput, PutBucketCorsCommandInput, PutBucketEncryptionCommandInput, PutBucketLifecycleConfigurationCommandInput, PutBucketNotificationConfigurationCommandInput, PutBucketPolicyCommandInput, PutBucketReplicationCommandInput, PutBucketTaggingCommandInput, PutBucketTaggingCommandOutput, PutBucketVersioningCommandInput, PutObjectAclCommandInput, PutObjectCommandInput, PutObjectCommandOutput, PutObjectLegalHoldCommandInput, PutObjectLockConfigurationCommandInput, PutObjectRetentionCommandInput, PutObjectTaggingCommandInput, RestoreObjectCommandInput, RestoreObjectCommandOutput, SelectObjectContentCommandInput, SelectObjectContentCommandOutput, Tag, } from '@aws-sdk/client-s3';
|
|
5
5
|
export type { MonacoJsonDefaults, MonacoLanguagesJson } from './monaco';
|
|
6
6
|
/**
|