@scality/data-browser-library 1.1.5 → 1.1.7
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/__tests__/BucketReplicationFormPage.test.js +207 -2
- package/dist/components/__tests__/CreateFolderButton.test.js +67 -0
- package/dist/components/__tests__/DeleteObjectButton.test.js +249 -26
- package/dist/components/__tests__/ObjectList.test.js +65 -22
- package/dist/components/__tests__/UploadButton.test.js +80 -0
- package/dist/components/buckets/BucketReplicationFormPage.js +114 -22
- package/dist/components/objects/CreateFolderButton.js +2 -1
- package/dist/components/objects/DeleteObjectButton.js +89 -43
- package/dist/components/objects/ObjectList.js +21 -0
- package/dist/components/objects/UploadButton.js +2 -1
- package/dist/components/ui/DeleteObjectModalContent.d.ts +4 -1
- package/dist/components/ui/DeleteObjectModalContent.js +57 -18
- package/dist/hooks/__tests__/useDeleteFolder.test.d.ts +1 -0
- package/dist/hooks/__tests__/useDeleteFolder.test.js +203 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/useDeleteFolder.d.ts +6 -0
- package/dist/hooks/useDeleteFolder.js +40 -0
- package/package.json +1 -1
|
@@ -45,15 +45,46 @@ const storageClassOptions = [
|
|
|
45
45
|
label: 'Glacier Instant Retrieval'
|
|
46
46
|
}
|
|
47
47
|
];
|
|
48
|
-
const createSchema = (hasExistingRules, hasCustomDestination)=>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
const createSchema = (hasExistingRules, hasCustomDestination, isV1)=>{
|
|
49
|
+
const roleSchema = hasExistingRules ? joi.string().optional() : joi.string().required().messages({
|
|
50
|
+
'string.empty': 'Role ARN is required for first replication rule'
|
|
51
|
+
});
|
|
52
|
+
const ruleIdSchema = joi.string().required().max(AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH).messages({
|
|
53
|
+
'string.empty': 'Rule ID is required',
|
|
54
|
+
'string.max': `Rule ID must not exceed ${AWS_RULE_LIMITS.RULE_ID_MAX_LENGTH} characters`
|
|
55
|
+
});
|
|
56
|
+
const statusSchema = joi.string().valid(...STATUS_OPTIONS.map((o)=>o.value)).required();
|
|
57
|
+
const targetBucketSchema = hasCustomDestination ? joi.string().allow('').optional() : joi.string().required().messages({
|
|
58
|
+
'string.empty': 'Target bucket is required'
|
|
59
|
+
});
|
|
60
|
+
if (isV1) return joi.object({
|
|
61
|
+
role: roleSchema,
|
|
62
|
+
ruleId: ruleIdSchema,
|
|
63
|
+
status: statusSchema,
|
|
64
|
+
prefix: joi.string().allow('').optional(),
|
|
65
|
+
targetBucket: targetBucketSchema,
|
|
66
|
+
storageClass: joi.string().allow('').optional(),
|
|
67
|
+
sameAccount: joi.any().optional(),
|
|
68
|
+
targetAccountId: joi.any().optional(),
|
|
69
|
+
priority: joi.any().optional(),
|
|
70
|
+
filterType: joi.any().optional(),
|
|
71
|
+
tags: joi.any().optional(),
|
|
72
|
+
includeEncryptedObjects: joi.any().optional(),
|
|
73
|
+
replicaModifications: joi.any().optional(),
|
|
74
|
+
encryptReplicatedObjects: joi.any().optional(),
|
|
75
|
+
replicaKmsKeyId: joi.any().optional(),
|
|
76
|
+
enforceRTC: joi.any().optional(),
|
|
77
|
+
enableRTCNotification: joi.any().optional(),
|
|
78
|
+
deleteMarkerReplication: joi.any().optional(),
|
|
79
|
+
switchObjectOwnership: joi.any().optional(),
|
|
80
|
+
understandISVRisk: joi.boolean().invalid(false).messages({
|
|
81
|
+
'any.invalid': 'You must acknowledge the risk'
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
return joi.object({
|
|
85
|
+
role: roleSchema,
|
|
86
|
+
ruleId: ruleIdSchema,
|
|
87
|
+
status: statusSchema,
|
|
57
88
|
priority: joi.number().integer().min(0).allow(null).optional().messages({
|
|
58
89
|
'number.base': 'Priority must be a number',
|
|
59
90
|
'number.min': 'Priority must be at least 0'
|
|
@@ -62,9 +93,7 @@ const createSchema = (hasExistingRules, hasCustomDestination)=>joi.object({
|
|
|
62
93
|
includeEncryptedObjects: joi.boolean(),
|
|
63
94
|
replicaModifications: joi.boolean(),
|
|
64
95
|
sameAccount: joi.boolean(),
|
|
65
|
-
targetBucket:
|
|
66
|
-
'string.empty': 'Target bucket is required'
|
|
67
|
-
}),
|
|
96
|
+
targetBucket: targetBucketSchema,
|
|
68
97
|
targetAccountId: hasCustomDestination ? joi.string().allow('').optional() : joi.when('sameAccount', {
|
|
69
98
|
is: false,
|
|
70
99
|
then: joi.string().required().messages({
|
|
@@ -101,6 +130,7 @@ const createSchema = (hasExistingRules, hasCustomDestination)=>joi.object({
|
|
|
101
130
|
'any.invalid': 'You must acknowledge the risk'
|
|
102
131
|
})
|
|
103
132
|
});
|
|
133
|
+
};
|
|
104
134
|
const ruleToFormValues = (rule, role)=>{
|
|
105
135
|
const formValues = {
|
|
106
136
|
role,
|
|
@@ -238,7 +268,21 @@ const buildSourceSelectionCriteria = (data)=>{
|
|
|
238
268
|
}
|
|
239
269
|
};
|
|
240
270
|
};
|
|
241
|
-
const buildReplicationRule = (data, sourceBucketName)=>{
|
|
271
|
+
const buildReplicationRule = (data, sourceBucketName, useV1Format)=>{
|
|
272
|
+
if (useV1Format) {
|
|
273
|
+
const bucketName = data.targetBucket || sourceBucketName;
|
|
274
|
+
return {
|
|
275
|
+
ID: data.ruleId,
|
|
276
|
+
Status: data.status,
|
|
277
|
+
Prefix: data.prefix || '',
|
|
278
|
+
Destination: {
|
|
279
|
+
Bucket: `arn:aws:s3:::${bucketName}`,
|
|
280
|
+
...data.storageClass && '' !== data.storageClass.trim() && {
|
|
281
|
+
StorageClass: data.storageClass
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
242
286
|
const rule = {
|
|
243
287
|
ID: data.ruleId,
|
|
244
288
|
Status: data.status,
|
|
@@ -339,7 +383,9 @@ function BucketReplicationFormPage() {
|
|
|
339
383
|
const { showToast } = useToast();
|
|
340
384
|
const isEditMode = !!ruleId;
|
|
341
385
|
const showAdvancedReplication = useFeatures('replicationAdvanced');
|
|
386
|
+
const useV1Format = useFeatures('replicationV1');
|
|
342
387
|
const { isISVManaged, isvApplication, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
|
|
388
|
+
const { data: bucketsData } = useBuckets();
|
|
343
389
|
const { data: replicationData, status: replicationStatus } = useGetBucketReplication({
|
|
344
390
|
Bucket: bucketName
|
|
345
391
|
});
|
|
@@ -367,7 +413,7 @@ function BucketReplicationFormPage() {
|
|
|
367
413
|
}, [
|
|
368
414
|
replicationData
|
|
369
415
|
]);
|
|
370
|
-
const dynamicSchema = useMemo(()=>createSchema(hasExistingRules, !!CustomDestinationFields).keys({
|
|
416
|
+
const dynamicSchema = useMemo(()=>createSchema(hasExistingRules, !!CustomDestinationFields, !!useV1Format).keys({
|
|
371
417
|
ruleId: joi.string().required().invalid(...existingRuleIds).messages({
|
|
372
418
|
'string.empty': 'Rule ID is required',
|
|
373
419
|
'any.invalid': 'A rule with this ID already exists'
|
|
@@ -375,7 +421,8 @@ function BucketReplicationFormPage() {
|
|
|
375
421
|
}), [
|
|
376
422
|
existingRuleIds,
|
|
377
423
|
hasExistingRules,
|
|
378
|
-
CustomDestinationFields
|
|
424
|
+
CustomDestinationFields,
|
|
425
|
+
useV1Format
|
|
379
426
|
]);
|
|
380
427
|
const methods = useForm({
|
|
381
428
|
resolver: joiResolver(dynamicSchema),
|
|
@@ -532,7 +579,7 @@ function BucketReplicationFormPage() {
|
|
|
532
579
|
]);
|
|
533
580
|
const onSubmit = useCallback((data)=>{
|
|
534
581
|
if (!bucketName) return;
|
|
535
|
-
const rule = buildReplicationRule(data, bucketName);
|
|
582
|
+
const rule = buildReplicationRule(data, bucketName, !!useV1Format);
|
|
536
583
|
const existingRules = replicationData?.ReplicationConfiguration?.Rules || [];
|
|
537
584
|
const updatedRules = isEditMode ? existingRules.map((r)=>r.ID === existingRule?.ID ? rule : r) : [
|
|
538
585
|
...existingRules,
|
|
@@ -569,7 +616,8 @@ function BucketReplicationFormPage() {
|
|
|
569
616
|
showToast,
|
|
570
617
|
replicationData,
|
|
571
618
|
isEditMode,
|
|
572
|
-
existingRule
|
|
619
|
+
existingRule,
|
|
620
|
+
useV1Format
|
|
573
621
|
]);
|
|
574
622
|
if ('pending' === replicationStatus || isISVLoading) return /*#__PURE__*/ jsx(Loader, {
|
|
575
623
|
centered: true,
|
|
@@ -718,7 +766,7 @@ function BucketReplicationFormPage() {
|
|
|
718
766
|
})
|
|
719
767
|
})
|
|
720
768
|
}),
|
|
721
|
-
/*#__PURE__*/ jsx(FormGroup, {
|
|
769
|
+
useV1Format ? /*#__PURE__*/ jsx(Fragment, {}) : /*#__PURE__*/ jsx(FormGroup, {
|
|
722
770
|
label: "Rule priority",
|
|
723
771
|
id: "priority",
|
|
724
772
|
direction: "horizontal",
|
|
@@ -743,7 +791,25 @@ function BucketReplicationFormPage() {
|
|
|
743
791
|
})
|
|
744
792
|
]
|
|
745
793
|
}),
|
|
746
|
-
/*#__PURE__*/ jsx(
|
|
794
|
+
useV1Format ? /*#__PURE__*/ jsx(FormSection, {
|
|
795
|
+
title: {
|
|
796
|
+
name: 'Prefix'
|
|
797
|
+
},
|
|
798
|
+
forceLabelWidth: convertRemToPixels(15),
|
|
799
|
+
children: /*#__PURE__*/ jsx(FormGroup, {
|
|
800
|
+
label: "Prefix",
|
|
801
|
+
id: "prefix",
|
|
802
|
+
direction: "horizontal",
|
|
803
|
+
error: errors?.prefix?.message,
|
|
804
|
+
helpErrorPosition: "bottom",
|
|
805
|
+
labelHelpTooltip: "Object key prefix to filter which objects are replicated",
|
|
806
|
+
content: /*#__PURE__*/ jsx(Input, {
|
|
807
|
+
id: "prefix",
|
|
808
|
+
placeholder: "folder/",
|
|
809
|
+
...register('prefix')
|
|
810
|
+
})
|
|
811
|
+
})
|
|
812
|
+
}) : /*#__PURE__*/ jsx(FilterFormSection, {
|
|
747
813
|
filterType: filterType,
|
|
748
814
|
onFilterTypeChange: (value)=>methods.setValue('filterType', value),
|
|
749
815
|
prefixRegister: register('prefix'),
|
|
@@ -766,6 +832,32 @@ function BucketReplicationFormPage() {
|
|
|
766
832
|
storageClassField,
|
|
767
833
|
/*#__PURE__*/ jsx(CustomDestinationFields, {})
|
|
768
834
|
]
|
|
835
|
+
}) : useV1Format ? /*#__PURE__*/ jsxs(Fragment, {
|
|
836
|
+
children: [
|
|
837
|
+
/*#__PURE__*/ jsx(FormGroup, {
|
|
838
|
+
label: "Target Bucket",
|
|
839
|
+
id: "targetBucket",
|
|
840
|
+
direction: "horizontal",
|
|
841
|
+
error: errors?.targetBucket?.message,
|
|
842
|
+
helpErrorPosition: "bottom",
|
|
843
|
+
required: true,
|
|
844
|
+
content: /*#__PURE__*/ jsx(Controller, {
|
|
845
|
+
name: "targetBucket",
|
|
846
|
+
control: control,
|
|
847
|
+
render: ({ field })=>/*#__PURE__*/ jsx(Select, {
|
|
848
|
+
id: "targetBucket",
|
|
849
|
+
value: field.value,
|
|
850
|
+
onChange: field.onChange,
|
|
851
|
+
placeholder: "Select a bucket",
|
|
852
|
+
children: (bucketsData?.Buckets || []).filter((bucket)=>bucket.Name !== bucketName).map((bucket)=>/*#__PURE__*/ jsx(Select.Option, {
|
|
853
|
+
value: bucket.Name || '',
|
|
854
|
+
children: bucket.Name
|
|
855
|
+
}, bucket.Name))
|
|
856
|
+
})
|
|
857
|
+
})
|
|
858
|
+
}),
|
|
859
|
+
storageClassField
|
|
860
|
+
]
|
|
769
861
|
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
770
862
|
children: [
|
|
771
863
|
/*#__PURE__*/ jsx(DefaultReplicationDestinationFields, {}),
|
|
@@ -773,7 +865,7 @@ function BucketReplicationFormPage() {
|
|
|
773
865
|
]
|
|
774
866
|
})
|
|
775
867
|
}),
|
|
776
|
-
/*#__PURE__*/ jsxs(FormSection, {
|
|
868
|
+
useV1Format ? null : /*#__PURE__*/ jsxs(FormSection, {
|
|
777
869
|
title: {
|
|
778
870
|
name: 'Encryption'
|
|
779
871
|
},
|
|
@@ -837,7 +929,7 @@ function BucketReplicationFormPage() {
|
|
|
837
929
|
}) : /*#__PURE__*/ jsx(Fragment, {})
|
|
838
930
|
]
|
|
839
931
|
}),
|
|
840
|
-
showAdvancedReplication && /*#__PURE__*/ jsxs(FormSection, {
|
|
932
|
+
showAdvancedReplication && !useV1Format ? /*#__PURE__*/ jsxs(FormSection, {
|
|
841
933
|
title: {
|
|
842
934
|
name: 'Additional Options'
|
|
843
935
|
},
|
|
@@ -915,7 +1007,7 @@ function BucketReplicationFormPage() {
|
|
|
915
1007
|
})
|
|
916
1008
|
})
|
|
917
1009
|
]
|
|
918
|
-
})
|
|
1010
|
+
}) : null
|
|
919
1011
|
]
|
|
920
1012
|
})
|
|
921
1013
|
});
|
|
@@ -21,7 +21,8 @@ const CreateFolderButton = ({ bucket, prefix = '', label = 'Folder', variant = '
|
|
|
21
21
|
}, []);
|
|
22
22
|
const handleSave = useCallback(()=>{
|
|
23
23
|
if (!folderName.trim()) return;
|
|
24
|
-
const
|
|
24
|
+
const normalizedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
25
|
+
const folderKey = normalizedPrefix ? `${normalizedPrefix}/${folderName}/` : `${folderName}/`;
|
|
25
26
|
createFolderMutation.mutate({
|
|
26
27
|
Bucket: bucket,
|
|
27
28
|
Key: folderKey,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Banner, Icon, Modal, PrettyBytes, Stack, Text, Wrap, spacing, useToast } from "@scality/core-ui";
|
|
2
|
+
import { Banner, Checkbox, Icon, Modal, PrettyBytes, Stack, Text, Wrap, spacing, useToast } from "@scality/core-ui";
|
|
3
3
|
import { Box, Button } from "@scality/core-ui/dist/next";
|
|
4
4
|
import { useCallback, useEffect, useState } from "react";
|
|
5
5
|
import { useDeleteObjects, useGetBucketVersioning } from "../../hooks/index.js";
|
|
6
|
+
import { useDeleteFolder } from "../../hooks/useDeleteFolder.js";
|
|
6
7
|
import { getDeletionMessages } from "../../utils/deletion/index.js";
|
|
8
|
+
import { useInvalidateQueries } from "../providers/DataBrowserProvider.js";
|
|
7
9
|
import { DeleteObjectModalContent } from "../ui/DeleteObjectModalContent.js";
|
|
8
10
|
const Title = ({ objects, isCurrentSelectionPermanentlyDeleted })=>{
|
|
9
11
|
const foldersSize = objects.filter((object)=>'folder' === object.type).length;
|
|
@@ -43,14 +45,18 @@ const Title = ({ objects, isCurrentSelectionPermanentlyDeleted })=>{
|
|
|
43
45
|
const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
|
|
44
46
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
45
47
|
const [selectedObjects, setSelectedObjects] = useState(objects);
|
|
48
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
46
49
|
const { data: versioningData } = useGetBucketVersioning({
|
|
47
50
|
Bucket: bucketName
|
|
48
51
|
});
|
|
49
|
-
const {
|
|
52
|
+
const { mutateAsync: deleteObjects } = useDeleteObjects();
|
|
53
|
+
const { mutateAsync: deleteFolder } = useDeleteFolder();
|
|
54
|
+
const invalidateQueries = useInvalidateQueries();
|
|
50
55
|
const { showToast } = useToast();
|
|
51
56
|
const isVersioningEnabled = versioningData?.Status === 'Enabled';
|
|
52
57
|
const isCurrentSelectionPermanentlyDeleted = !isVersioningEnabled || selectedObjects.some((object)=>!!object.VersionId);
|
|
53
|
-
const
|
|
58
|
+
const [isCheckboxToggled, setIsCheckboxToggled] = useState(false);
|
|
59
|
+
const { info: notificationText, checkboxRequired } = getDeletionMessages({
|
|
54
60
|
numberOfObjects: selectedObjects.length,
|
|
55
61
|
selectedObjectsAreSpecificVersions: isCurrentSelectionPermanentlyDeleted,
|
|
56
62
|
isBucketVersioned: isVersioningEnabled
|
|
@@ -61,51 +67,82 @@ const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
|
|
|
61
67
|
const cancel = useCallback(()=>{
|
|
62
68
|
setIsModalOpen(false);
|
|
63
69
|
setSelectedObjects(objects);
|
|
70
|
+
setIsCheckboxToggled(false);
|
|
64
71
|
}, [
|
|
65
72
|
objects
|
|
66
73
|
]);
|
|
67
|
-
const deleteSelectedFiles = useCallback(()=>{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
}, {
|
|
87
|
-
onSuccess: ()=>{
|
|
88
|
-
showToast({
|
|
89
|
-
open: true,
|
|
90
|
-
message: 'Objects deleted successfully',
|
|
91
|
-
status: 'success'
|
|
92
|
-
});
|
|
93
|
-
setIsModalOpen(false);
|
|
94
|
-
onDeleteSuccess?.();
|
|
95
|
-
},
|
|
96
|
-
onError: (error)=>{
|
|
97
|
-
showToast({
|
|
98
|
-
open: true,
|
|
99
|
-
message: error instanceof Error ? error.message : 'Objects deleted failed',
|
|
100
|
-
status: 'error'
|
|
74
|
+
const deleteSelectedFiles = useCallback(async ()=>{
|
|
75
|
+
setIsDeleting(true);
|
|
76
|
+
try {
|
|
77
|
+
const folderItems = selectedObjects.filter((object)=>'folder' === object.type && object.Key);
|
|
78
|
+
const nonFolderItems = selectedObjects.filter((object)=>'folder' !== object.type && object.Key);
|
|
79
|
+
if (nonFolderItems.length > 0) {
|
|
80
|
+
const result = await deleteObjects({
|
|
81
|
+
Bucket: bucketName,
|
|
82
|
+
Delete: {
|
|
83
|
+
Objects: nonFolderItems.map((object)=>{
|
|
84
|
+
const deleteItem = {
|
|
85
|
+
Key: object.Key
|
|
86
|
+
};
|
|
87
|
+
if (object.VersionId && 'string' == typeof object.VersionId) deleteItem.VersionId = object.VersionId;
|
|
88
|
+
return deleteItem;
|
|
89
|
+
})
|
|
90
|
+
}
|
|
101
91
|
});
|
|
102
|
-
|
|
92
|
+
if (result.Errors && result.Errors.length > 0) {
|
|
93
|
+
const firstError = result.Errors[0];
|
|
94
|
+
throw new Error(firstError.Message ?? 'Failed to delete objects');
|
|
95
|
+
}
|
|
103
96
|
}
|
|
104
|
-
|
|
97
|
+
for (const folder of folderItems)await deleteFolder({
|
|
98
|
+
Bucket: bucketName,
|
|
99
|
+
FolderKey: folder.Key
|
|
100
|
+
});
|
|
101
|
+
const hasFolders = folderItems.length > 0;
|
|
102
|
+
const hasObjects = nonFolderItems.length > 0;
|
|
103
|
+
const message = hasFolders && hasObjects ? 'Objects and folders deleted successfully' : hasFolders ? 'Folders deleted successfully' : 'Objects deleted successfully';
|
|
104
|
+
showToast({
|
|
105
|
+
open: true,
|
|
106
|
+
message,
|
|
107
|
+
status: 'success'
|
|
108
|
+
});
|
|
109
|
+
onDeleteSuccess?.();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
showToast({
|
|
112
|
+
open: true,
|
|
113
|
+
message: error instanceof Error ? error.message : 'Failed to delete objects',
|
|
114
|
+
status: 'error'
|
|
115
|
+
});
|
|
116
|
+
} finally{
|
|
117
|
+
invalidateQueries({
|
|
118
|
+
queryKey: [
|
|
119
|
+
'ListObjects'
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
invalidateQueries({
|
|
123
|
+
queryKey: [
|
|
124
|
+
'ListObjectVersions'
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
invalidateQueries({
|
|
128
|
+
queryKey: [
|
|
129
|
+
'SearchObjects'
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
invalidateQueries({
|
|
133
|
+
queryKey: [
|
|
134
|
+
'SearchObjectsVersions'
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
setIsModalOpen(false);
|
|
138
|
+
setIsDeleting(false);
|
|
139
|
+
}
|
|
105
140
|
}, [
|
|
106
141
|
bucketName,
|
|
107
142
|
selectedObjects,
|
|
108
143
|
deleteObjects,
|
|
144
|
+
deleteFolder,
|
|
145
|
+
invalidateQueries,
|
|
109
146
|
showToast,
|
|
110
147
|
onDeleteSuccess
|
|
111
148
|
]);
|
|
@@ -142,11 +179,11 @@ const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
|
|
|
142
179
|
label: "Cancel"
|
|
143
180
|
}),
|
|
144
181
|
/*#__PURE__*/ jsx(Button, {
|
|
145
|
-
isLoading:
|
|
182
|
+
isLoading: isDeleting,
|
|
146
183
|
id: "object-delete-delete-button",
|
|
147
184
|
variant: "danger",
|
|
148
185
|
onClick: deleteSelectedFiles,
|
|
149
|
-
disabled: 0 === selectedObjects.length,
|
|
186
|
+
disabled: 0 === selectedObjects.length || checkboxRequired && !isCheckboxToggled,
|
|
150
187
|
label: "Delete"
|
|
151
188
|
})
|
|
152
189
|
]
|
|
@@ -164,8 +201,8 @@ const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
|
|
|
164
201
|
}),
|
|
165
202
|
/*#__PURE__*/ jsx(DeleteObjectModalContent, {
|
|
166
203
|
objects: selectedObjects,
|
|
167
|
-
onRemove: (
|
|
168
|
-
setSelectedObjects(selectedObjects.filter((object)
|
|
204
|
+
onRemove: (item)=>{
|
|
205
|
+
setSelectedObjects(selectedObjects.filter((object)=>!(object.Key === item.Key && object.VersionId === item.VersionId)));
|
|
169
206
|
}
|
|
170
207
|
}),
|
|
171
208
|
/*#__PURE__*/ jsxs(Box, {
|
|
@@ -185,6 +222,15 @@ const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
|
|
|
185
222
|
children: /*#__PURE__*/ jsx("span", {
|
|
186
223
|
children: notificationText
|
|
187
224
|
})
|
|
225
|
+
}),
|
|
226
|
+
checkboxRequired && selectedObjects.length > 0 && /*#__PURE__*/ jsx(Box, {
|
|
227
|
+
mt: spacing.r12,
|
|
228
|
+
children: /*#__PURE__*/ jsx(Checkbox, {
|
|
229
|
+
id: "confirm-deletion-checkbox",
|
|
230
|
+
label: "Confirm the deletion",
|
|
231
|
+
checked: isCheckboxToggled,
|
|
232
|
+
onChange: ()=>setIsCheckboxToggled((prev)=>!prev)
|
|
233
|
+
})
|
|
188
234
|
})
|
|
189
235
|
]
|
|
190
236
|
})
|
|
@@ -275,7 +275,27 @@ function createOverrideMap(customItems) {
|
|
|
275
275
|
const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSelectedObjectsChange })=>{
|
|
276
276
|
const { extraObjectListColumns, extraObjectListActions } = useDataBrowserUICustomization();
|
|
277
277
|
const invalidateQueries = useInvalidateQueries();
|
|
278
|
+
const versionCheck = useListObjectVersions({
|
|
279
|
+
Bucket: bucketName,
|
|
280
|
+
MaxKeys: 10
|
|
281
|
+
}, {
|
|
282
|
+
enabled: Boolean(bucketName)
|
|
283
|
+
});
|
|
284
|
+
const hasObjectVersions = useMemo(()=>{
|
|
285
|
+
const firstPage = versionCheck.data?.pages?.[0];
|
|
286
|
+
if (!firstPage) return false;
|
|
287
|
+
const versions = firstPage.Versions || [];
|
|
288
|
+
const deleteMarkers = firstPage.DeleteMarkers || [];
|
|
289
|
+
return versions.some((v)=>v.VersionId && 'null' !== v.VersionId) || deleteMarkers.length > 0;
|
|
290
|
+
}, [
|
|
291
|
+
versionCheck.data
|
|
292
|
+
]);
|
|
278
293
|
const [showVersions, setShowVersions] = useState(false);
|
|
294
|
+
useEffect(()=>{
|
|
295
|
+
if (!hasObjectVersions) setShowVersions(false);
|
|
296
|
+
}, [
|
|
297
|
+
hasObjectVersions
|
|
298
|
+
]);
|
|
279
299
|
const isMetadataSearchEnabled = useFeatures('metadatasearch');
|
|
280
300
|
const queryParams = useQueryParams();
|
|
281
301
|
const metadataSearchQuery = queryParams.get('metadatasearch');
|
|
@@ -787,6 +807,7 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
|
|
|
787
807
|
toggle: showVersions,
|
|
788
808
|
onChange: (e)=>setShowVersions(e.target.checked),
|
|
789
809
|
label: "List Versions",
|
|
810
|
+
disabled: !hasObjectVersions,
|
|
790
811
|
"aria-label": showVersions ? 'Hide object versions' : 'Show object versions',
|
|
791
812
|
"aria-pressed": showVersions
|
|
792
813
|
})
|
|
@@ -155,9 +155,10 @@ const UploadButton = ({ bucket, prefix = '', uploadOptions = {}, onUploadSuccess
|
|
|
155
155
|
const failedFiles = [];
|
|
156
156
|
for (const file of acceptedFiles)try {
|
|
157
157
|
const fileBuffer = await file.arrayBuffer();
|
|
158
|
+
const normalizedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
158
159
|
await uploadMutation.mutateAsync({
|
|
159
160
|
Bucket: bucket,
|
|
160
|
-
Key:
|
|
161
|
+
Key: normalizedPrefix ? `${normalizedPrefix}/${file.name}` : file.name,
|
|
161
162
|
Body: new Uint8Array(fileBuffer),
|
|
162
163
|
ContentType: file.type,
|
|
163
164
|
...uploadOptions
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Objects } from '../objects/DeleteObjectButton';
|
|
2
2
|
export declare const DeleteObjectModalContent: ({ objects, onRemove, }: {
|
|
3
3
|
objects: Objects;
|
|
4
|
-
onRemove: (
|
|
4
|
+
onRemove: (item: {
|
|
5
|
+
Key: string;
|
|
6
|
+
VersionId?: string;
|
|
7
|
+
}) => void;
|
|
5
8
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,46 +1,89 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ConstrainedText, Icon, PrettyBytes, spacing } from "@scality/core-ui";
|
|
3
|
-
import { tableRowHeight } from "@scality/core-ui/dist/components/tablev2/TableUtils";
|
|
4
3
|
import { Box, Table } from "@scality/core-ui/dist/next";
|
|
5
4
|
import { useMemo } from "react";
|
|
6
5
|
import styled_components from "styled-components";
|
|
6
|
+
const NAME_COLUMN_FLEX = '3';
|
|
7
|
+
const SIZE_COLUMN_FLEX = '1';
|
|
7
8
|
const Container = styled_components(Box)`
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
width: 31.25rem;
|
|
10
|
+
height: 15.63rem;
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
border: 1px solid ${({ theme })=>theme.border};
|
|
10
13
|
margin: ${spacing.r8} 0rem;
|
|
11
14
|
`;
|
|
15
|
+
const VersionId = styled_components.div`
|
|
16
|
+
font-size: 0.75rem;
|
|
17
|
+
color: ${({ theme })=>theme.textSecondary};
|
|
18
|
+
`;
|
|
12
19
|
const DeleteObjectModalContent = ({ objects, onRemove })=>{
|
|
13
20
|
const columns = useMemo(()=>[
|
|
14
21
|
{
|
|
15
22
|
Header: 'Name',
|
|
16
23
|
accessor: 'Key',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
cellStyle: {
|
|
25
|
+
flex: NAME_COLUMN_FLEX,
|
|
26
|
+
minWidth: 0
|
|
27
|
+
},
|
|
28
|
+
Cell: ({ value, row })=>{
|
|
29
|
+
const versionId = 'VersionId' in row.original ? row.original.VersionId : void 0;
|
|
30
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
31
|
+
style: {
|
|
32
|
+
minWidth: 0
|
|
33
|
+
},
|
|
34
|
+
children: [
|
|
35
|
+
/*#__PURE__*/ jsx(ConstrainedText, {
|
|
36
|
+
text: value,
|
|
37
|
+
lineClamp: 1
|
|
38
|
+
}),
|
|
39
|
+
versionId && /*#__PURE__*/ jsx(VersionId, {
|
|
40
|
+
children: versionId
|
|
41
|
+
})
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
},
|
|
21
45
|
id: 'name'
|
|
22
46
|
},
|
|
23
47
|
{
|
|
24
48
|
Header: 'Size',
|
|
25
49
|
accessor: 'Size',
|
|
26
50
|
cellStyle: {
|
|
51
|
+
flex: SIZE_COLUMN_FLEX,
|
|
27
52
|
textAlign: 'right'
|
|
28
53
|
},
|
|
29
|
-
Cell: ({ value })
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
Cell: ({ value, row })=>{
|
|
55
|
+
const isLegalHoldEnabled = 'isLegalHoldEnabled' in row.original && row.original.isLegalHoldEnabled;
|
|
56
|
+
return /*#__PURE__*/ jsxs(Box, {
|
|
57
|
+
display: "flex",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
gap: spacing.r4,
|
|
60
|
+
justifyContent: "flex-end",
|
|
61
|
+
children: [
|
|
62
|
+
void 0 !== value && /*#__PURE__*/ jsx(PrettyBytes, {
|
|
63
|
+
bytes: Number(value)
|
|
64
|
+
}),
|
|
65
|
+
isLegalHoldEnabled && /*#__PURE__*/ jsx(Icon, {
|
|
66
|
+
name: "Rebalance",
|
|
67
|
+
size: "sm"
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
},
|
|
32
72
|
id: 'size'
|
|
33
73
|
},
|
|
34
74
|
{
|
|
35
75
|
Header: '',
|
|
36
76
|
accessor: 'type',
|
|
37
77
|
cellStyle: {
|
|
38
|
-
|
|
78
|
+
flex: '0 0 2rem'
|
|
39
79
|
},
|
|
40
80
|
Cell: (row)=>{
|
|
41
|
-
const
|
|
81
|
+
const item = row.row.original;
|
|
42
82
|
return /*#__PURE__*/ jsx("div", {
|
|
43
|
-
onClick: ()=>onRemove(
|
|
83
|
+
onClick: ()=>onRemove({
|
|
84
|
+
Key: item.Key,
|
|
85
|
+
VersionId: 'VersionId' in item ? item.VersionId : void 0
|
|
86
|
+
}),
|
|
44
87
|
children: /*#__PURE__*/ jsx(Icon, {
|
|
45
88
|
name: "Close",
|
|
46
89
|
color: "buttonSecondary"
|
|
@@ -51,11 +94,7 @@ const DeleteObjectModalContent = ({ objects, onRemove })=>{
|
|
|
51
94
|
], [
|
|
52
95
|
onRemove
|
|
53
96
|
]);
|
|
54
|
-
const HEADER_AND_SPACING_ROWS = 3;
|
|
55
|
-
const rowHeight = 'h40';
|
|
56
|
-
const tableRowHeightInRem = tableRowHeight[rowHeight];
|
|
57
97
|
return /*#__PURE__*/ jsx(Container, {
|
|
58
|
-
height: `calc(${objects.length + HEADER_AND_SPACING_ROWS} * (${tableRowHeightInRem}rem + 1px))`,
|
|
59
98
|
children: /*#__PURE__*/ jsx(Table, {
|
|
60
99
|
columns: columns,
|
|
61
100
|
data: objects,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|