@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.
@@ -45,15 +45,46 @@ const storageClassOptions = [
45
45
  label: 'Glacier Instant Retrieval'
46
46
  }
47
47
  ];
48
- const createSchema = (hasExistingRules, hasCustomDestination)=>joi.object({
49
- role: hasExistingRules ? joi.string().optional() : joi.string().required().messages({
50
- 'string.empty': 'Role ARN is required for first replication rule'
51
- }),
52
- ruleId: 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
- status: joi.string().valid(...STATUS_OPTIONS.map((o)=>o.value)).required(),
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: hasCustomDestination ? joi.string().allow('').optional() : joi.string().required().messages({
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(FilterFormSection, {
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 folderKey = prefix ? `${prefix}/${folderName}/` : `${folderName}/`;
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 { mutate: deleteObjects, status: deleteObjectsStatus } = useDeleteObjects();
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 { info: notificationText } = getDeletionMessages({
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
- const objectsToDelete = selectedObjects.filter((object)=>'folder' !== object.type && object.Key).map((object)=>{
69
- const deleteItem = {
70
- Key: object.Key
71
- };
72
- if (object.VersionId && 'string' == typeof object.VersionId) deleteItem.VersionId = object.VersionId;
73
- return deleteItem;
74
- });
75
- const foldersToDelete = selectedObjects.filter((object)=>'folder' === object.type && object.Key).map((object)=>({
76
- Key: object.Key
77
- }));
78
- deleteObjects({
79
- Bucket: bucketName,
80
- Delete: {
81
- Objects: [
82
- ...objectsToDelete,
83
- ...foldersToDelete
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
- setIsModalOpen(false);
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: 'pending' === deleteObjectsStatus,
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: (key)=>{
168
- setSelectedObjects(selectedObjects.filter((object)=>object.Key !== key));
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: prefix ? `${prefix}/${file.name}` : file.name,
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: (key: string) => void;
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
- height: ${({ height })=>height};
9
- min-height: 15.63rem;
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
- Cell: ({ value })=>/*#__PURE__*/ jsx(ConstrainedText, {
18
- text: value,
19
- lineClamp: 1
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 })=>/*#__PURE__*/ jsx(PrettyBytes, {
30
- bytes: Number(value)
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
- width: '0.625rem'
78
+ flex: '0 0 2rem'
39
79
  },
40
80
  Cell: (row)=>{
41
- const objectKey = row.row.original.Key;
81
+ const item = row.row.original;
42
82
  return /*#__PURE__*/ jsx("div", {
43
- onClick: ()=>onRemove(objectKey),
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 {};