@scality/data-browser-library 1.1.9 → 1.1.11

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.
Files changed (41) hide show
  1. package/dist/components/__tests__/BucketCorsPage.test.js +14 -6
  2. package/dist/components/__tests__/BucketLifecycleFormPage.test.js +131 -0
  3. package/dist/components/__tests__/BucketNotificationFormPage.test.js +15 -3
  4. package/dist/components/buckets/BucketCorsPage.js +5 -7
  5. package/dist/components/buckets/BucketLifecycleFormPage.js +32 -44
  6. package/dist/components/buckets/BucketLifecycleList.js +8 -4
  7. package/dist/components/buckets/BucketOverview.js +1 -1
  8. package/dist/components/buckets/BucketPolicyPage.js +4 -7
  9. package/dist/components/buckets/BucketReplicationFormPage.js +12 -6
  10. package/dist/components/buckets/BucketReplicationList.js +12 -9
  11. package/dist/components/buckets/BucketVersioning.js +1 -1
  12. package/dist/components/buckets/notifications/BucketNotificationFormPage.js +2 -2
  13. package/dist/components/buckets/notifications/BucketNotificationList.js +1 -1
  14. package/dist/components/objects/DeleteObjectButton.js +1 -1
  15. package/dist/components/objects/GetPresignedUrlButton.js +1 -1
  16. package/dist/components/objects/ObjectDetails/ObjectMetadata.js +1 -1
  17. package/dist/components/objects/ObjectDetails/ObjectSummary.js +2 -2
  18. package/dist/components/objects/ObjectDetails/ObjectTags.js +1 -1
  19. package/dist/components/objects/ObjectDetails/__tests__/ObjectSummary.test.js +37 -1
  20. package/dist/components/objects/ObjectLock/ObjectLockSettings.js +1 -1
  21. package/dist/components/objects/__tests__/GetPresignedUrlButton.test.js +30 -1
  22. package/dist/components/providers/QueryProvider.js +2 -1
  23. package/dist/hooks/bucketConfiguration.js +15 -15
  24. package/dist/hooks/bucketOperations.js +1 -1
  25. package/dist/hooks/factories/__tests__/useCreateS3FunctionMutationHook.test.js +7 -35
  26. package/dist/hooks/factories/__tests__/useCreateS3InfiniteQueryHook.test.js +1 -1
  27. package/dist/hooks/factories/__tests__/useCreateS3MutationHook.test.js +1 -1
  28. package/dist/hooks/factories/__tests__/useCreateS3QueryHook.test.js +1 -1
  29. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.d.ts +1 -1
  30. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -2
  31. package/dist/hooks/factories/useCreateS3MutationHook.d.ts +3 -3
  32. package/dist/hooks/factories/useCreateS3MutationHook.js +20 -8
  33. package/dist/hooks/factories/useCreateS3QueryHook.d.ts +1 -1
  34. package/dist/hooks/factories/useCreateS3QueryHook.js +2 -2
  35. package/dist/hooks/objectOperations.js +6 -6
  36. package/dist/hooks/presignedOperations.js +1 -1
  37. package/dist/hooks/useDeleteFolder.js +1 -1
  38. package/dist/test/utils/errorHandling.test.js +45 -33
  39. package/dist/utils/errorHandling.d.ts +2 -1
  40. package/dist/utils/errorHandling.js +8 -7
  41. package/package.json +2 -2
@@ -306,16 +306,24 @@ describe('BucketCorsPage', ()=>{
306
306
  replace: true
307
307
  });
308
308
  });
309
- it('shows error message when fetching CORS fails with unexpected error', ()=>{
310
- const accessDeniedError = new Error('Access Denied');
311
- accessDeniedError.name = 'AccessDenied';
309
+ it('shows permission message when fetching CORS fails with 403 error', ()=>{
310
+ const authError = new EnhancedS3Error("You don't have permission to load CORS configuration. Contact your administrator.", 'AccessDenied', ErrorCategory.AUTHORIZATION, new Error('Access Denied'), 403);
312
311
  mockUseGetBucketCors.mockReturnValue(createMockQueryResult({
313
312
  status: 'error',
314
- error: accessDeniedError
313
+ error: authError
315
314
  }));
316
315
  renderBucketCorsPage();
317
- expect(screen.getByText(/Failed to load CORS configuration/i)).toBeInTheDocument();
318
- expect(screen.getByText(/Access Denied/i)).toBeInTheDocument();
316
+ expect(screen.getByText("You don't have permission to load CORS configuration. Contact your administrator.")).toBeInTheDocument();
317
+ expect(screen.queryByTestId('cors-editor')).not.toBeInTheDocument();
318
+ });
319
+ it('shows original error message when fetching CORS fails with non-403 error', ()=>{
320
+ const serverError = new EnhancedS3Error('Internal Server Error', 'InternalError', ErrorCategory.SERVER_ERROR, new Error('Internal Server Error'), 500);
321
+ mockUseGetBucketCors.mockReturnValue(createMockQueryResult({
322
+ status: 'error',
323
+ error: serverError
324
+ }));
325
+ renderBucketCorsPage();
326
+ expect(screen.getByText('Internal Server Error')).toBeInTheDocument();
319
327
  expect(screen.queryByTestId('cors-editor')).not.toBeInTheDocument();
320
328
  });
321
329
  });
@@ -441,6 +441,31 @@ describe('BucketLifecycleFormPage', ()=>{
441
441
  });
442
442
  });
443
443
  describe('Rule Data Loading', ()=>{
444
+ it('loads rule with empty prefix filter as "All objects" (no filter)', async ()=>{
445
+ const rule = {
446
+ ID: 'no-filter-rule',
447
+ Status: 'Enabled',
448
+ Filter: {
449
+ Prefix: ''
450
+ },
451
+ Expiration: {
452
+ Days: 30
453
+ }
454
+ };
455
+ mockUseGetBucketLifecycle.mockReturnValue({
456
+ data: {
457
+ Rules: [
458
+ rule
459
+ ]
460
+ },
461
+ status: 'success'
462
+ });
463
+ renderBucketLifecycleFormPage('test-bucket', 'no-filter-rule');
464
+ await waitFor(()=>{
465
+ expect(screen.getByText('All objects')).toBeInTheDocument();
466
+ });
467
+ expect(screen.queryByPlaceholderText('folder/')).not.toBeInTheDocument();
468
+ });
444
469
  it('loads rule with prefix filter correctly', async ()=>{
445
470
  const rule = {
446
471
  ID: 'prefix-rule',
@@ -598,6 +623,112 @@ describe('BucketLifecycleFormPage', ()=>{
598
623
  expect(transitionToggle).toBeChecked();
599
624
  });
600
625
  });
626
+ it('does not add extra transition rows when loading a rule with existing transitions', async ()=>{
627
+ const rule = {
628
+ ID: 'transition-rule',
629
+ Status: 'Enabled',
630
+ Filter: {},
631
+ Transitions: [
632
+ {
633
+ Days: 60,
634
+ StorageClass: 'STANDARD_IA'
635
+ }
636
+ ]
637
+ };
638
+ mockUseGetBucketLifecycle.mockReturnValue({
639
+ data: {
640
+ Rules: [
641
+ rule
642
+ ]
643
+ },
644
+ status: 'success'
645
+ });
646
+ renderBucketLifecycleFormPage('test-bucket', 'transition-rule');
647
+ await waitFor(()=>{
648
+ const transitionToggle = findToggleByLabel('Transition current version');
649
+ expect(transitionToggle).toBeChecked();
650
+ expect(screen.getByDisplayValue('60')).toBeInTheDocument();
651
+ });
652
+ expect(document.getElementById('transition-days-1')).not.toBeInTheDocument();
653
+ });
654
+ it('auto-adds a transition row when enabling transitions in edit mode on a rule without transitions', async ()=>{
655
+ const rule = {
656
+ ID: 'delete-marker-rule',
657
+ Status: 'Enabled',
658
+ Filter: {},
659
+ Expiration: {
660
+ ExpiredObjectDeleteMarker: true
661
+ }
662
+ };
663
+ mockUseGetBucketLifecycle.mockReturnValue({
664
+ data: {
665
+ Rules: [
666
+ rule
667
+ ]
668
+ },
669
+ status: 'success'
670
+ });
671
+ renderBucketLifecycleFormPage('test-bucket', 'delete-marker-rule');
672
+ await waitFor(()=>{
673
+ expect(screen.getByText('Edit Lifecycle Rule')).toBeInTheDocument();
674
+ });
675
+ const transitionToggle = findToggleByLabel('Transition current version');
676
+ fireEvent.click(transitionToggle);
677
+ await waitFor(()=>{
678
+ expect(screen.getByText('Time Type')).toBeInTheDocument();
679
+ expect(screen.getByText('Storage Class')).toBeInTheDocument();
680
+ expect(screen.getByDisplayValue('30')).toBeInTheDocument();
681
+ });
682
+ });
683
+ it('auto-adds a noncurrent transition row when enabling noncurrent transitions in edit mode', async ()=>{
684
+ const rule = {
685
+ ID: 'expiration-only-rule',
686
+ Status: 'Enabled',
687
+ Filter: {},
688
+ Expiration: {
689
+ Days: 30
690
+ }
691
+ };
692
+ mockUseGetBucketLifecycle.mockReturnValue({
693
+ data: {
694
+ Rules: [
695
+ rule
696
+ ]
697
+ },
698
+ status: 'success'
699
+ });
700
+ renderBucketLifecycleFormPage('test-bucket', 'expiration-only-rule');
701
+ await waitFor(()=>{
702
+ expect(screen.getByText('Edit Lifecycle Rule')).toBeInTheDocument();
703
+ });
704
+ const noncurrentToggle = findToggleByLabel('Transition noncurrent version');
705
+ fireEvent.click(noncurrentToggle);
706
+ await waitFor(()=>{
707
+ expect(screen.getByText('Noncurrent Days')).toBeInTheDocument();
708
+ expect(document.getElementById('noncurrent-transition-days-0')).toBeInTheDocument();
709
+ });
710
+ });
711
+ });
712
+ describe('Transition Auto-Add in Create Mode', ()=>{
713
+ it('auto-adds a transition row when enabling transitions', async ()=>{
714
+ renderBucketLifecycleFormPage();
715
+ const transitionToggle = findToggleByLabel('Transition current version');
716
+ fireEvent.click(transitionToggle);
717
+ await waitFor(()=>{
718
+ expect(screen.getByText('Time Type')).toBeInTheDocument();
719
+ expect(screen.getByText('Storage Class')).toBeInTheDocument();
720
+ expect(screen.getByDisplayValue('30')).toBeInTheDocument();
721
+ });
722
+ });
723
+ it('auto-adds a noncurrent transition row when enabling noncurrent transitions', async ()=>{
724
+ renderBucketLifecycleFormPage();
725
+ const noncurrentToggle = findToggleByLabel('Transition noncurrent version');
726
+ fireEvent.click(noncurrentToggle);
727
+ await waitFor(()=>{
728
+ expect(screen.getByText('Noncurrent Days')).toBeInTheDocument();
729
+ expect(document.getElementById('noncurrent-transition-days-0')).toBeInTheDocument();
730
+ });
731
+ });
601
732
  });
602
733
  describe('Navigation', ()=>{
603
734
  it('navigates back to bucket lifecycle tab when cancel is clicked', ()=>{
@@ -5,6 +5,7 @@ import { MemoryRouter, Route, Routes } from "react-router";
5
5
  import { useGetBucketNotification, useSetBucketNotification } from "../../hooks/index.js";
6
6
  import { useSupportedNotificationEvents } from "../../hooks/useSupportedNotificationEvents.js";
7
7
  import { createTestWrapper } from "../../test/testUtils.js";
8
+ import { EnhancedS3Error, ErrorCategory } from "../../utils/errorHandling.js";
8
9
  import { BucketNotificationFormPage } from "../buckets/notifications/BucketNotificationFormPage.js";
9
10
  jest.mock('../../hooks', ()=>({
10
11
  useGetBucketNotification: jest.fn(),
@@ -235,14 +236,25 @@ describe('BucketNotificationFormPage', ()=>{
235
236
  renderCreatePage();
236
237
  expect(screen.getByText(/failed to fetch notification configuration/i)).toBeInTheDocument();
237
238
  });
238
- it('shows generic error message when error is not an Error instance', ()=>{
239
+ it('shows permission message when notification data fails to load with 403 error', ()=>{
240
+ const authError = new EnhancedS3Error("You don't have permission to load the notification configuration. Contact your administrator.", 'AccessDenied', ErrorCategory.AUTHORIZATION, new Error('Access Denied'), 403);
239
241
  mockUseGetBucketNotification.mockReturnValue({
240
242
  data: void 0,
241
243
  status: 'error',
242
- error: 'Unknown error'
244
+ error: authError
243
245
  });
244
246
  renderCreatePage();
245
- expect(screen.getByText(/failed to load notification configuration/i)).toBeInTheDocument();
247
+ expect(screen.getByText("You don't have permission to load the notification configuration. Contact your administrator.")).toBeInTheDocument();
248
+ });
249
+ it('shows error message from EnhancedS3Error', ()=>{
250
+ const error = new EnhancedS3Error('Something went wrong', 'InternalError', ErrorCategory.SERVER_ERROR, new Error('Something went wrong'), 500);
251
+ mockUseGetBucketNotification.mockReturnValue({
252
+ data: void 0,
253
+ status: 'error',
254
+ error
255
+ });
256
+ renderCreatePage();
257
+ expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
246
258
  });
247
259
  });
248
260
  describe('Edit Mode', ()=>{
@@ -102,7 +102,7 @@ const BucketCorsPage = ()=>{
102
102
  onError: (error)=>{
103
103
  setError('content', {
104
104
  type: 'server',
105
- message: error instanceof Error ? error.message : 'Failed to delete'
105
+ message: error.message
106
106
  });
107
107
  }
108
108
  });
@@ -123,7 +123,7 @@ const BucketCorsPage = ()=>{
123
123
  onError: (error)=>{
124
124
  setError('content', {
125
125
  type: 'server',
126
- message: error instanceof Error ? error.message : 'Failed to save'
126
+ message: error.message
127
127
  });
128
128
  }
129
129
  });
@@ -163,12 +163,9 @@ const BucketCorsPage = ()=>{
163
163
  size: "2x",
164
164
  color: "statusWarning"
165
165
  }),
166
- /*#__PURE__*/ jsxs(Text, {
166
+ /*#__PURE__*/ jsx(Text, {
167
167
  variant: "Large",
168
- children: [
169
- "Failed to load CORS configuration: ",
170
- corsError.message || 'Unknown error'
171
- ]
168
+ children: corsError.message
172
169
  })
173
170
  ]
174
171
  })
@@ -214,6 +211,7 @@ const BucketCorsPage = ()=>{
214
211
  id: "corsConfiguration",
215
212
  label: "CORS Rules",
216
213
  direction: "vertical",
214
+ helpErrorPosition: "bottom",
217
215
  error: errors.content?.message,
218
216
  content: /*#__PURE__*/ jsxs(Stack, {
219
217
  direction: "horizontal",
@@ -9,8 +9,8 @@ import { Controller, FormProvider, useFieldArray, useForm } from "react-hook-for
9
9
  import { useParams } from "react-router";
10
10
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
11
11
  import { useGetBucketLifecycle, useSetBucketLifecycle } from "../../hooks/bucketConfiguration.js";
12
- import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
13
12
  import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
13
+ import { useISVBucketStatus } from "../../hooks/useISVBucketDetection.js";
14
14
  import { AWS_RULE_LIMITS, STATUS_OPTIONS, buildS3Filter } from "../../utils/s3RuleUtils.js";
15
15
  import { ArrayFieldActions } from "../ui/ArrayFieldActions.js";
16
16
  import { FilterFormSection, createFilterValidationSchema } from "../ui/FilterFormSection.js";
@@ -209,7 +209,7 @@ const ruleToFormValues = (rule)=>{
209
209
  key: tag.Key || '',
210
210
  value: tag.Value || ''
211
211
  })) || [];
212
- } else if (void 0 !== rule.Filter.Prefix) {
212
+ } else if (void 0 !== rule.Filter.Prefix && '' !== rule.Filter.Prefix) {
213
213
  formValues.filterType = 'prefix';
214
214
  formValues.prefix = rule.Filter.Prefix;
215
215
  } else if (rule.Filter.Tag) {
@@ -347,7 +347,8 @@ function BucketLifecycleFormPage() {
347
347
  understandISVRisk: true
348
348
  }
349
349
  });
350
- const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
350
+ const { handleSubmit, register, control, watch, reset, formState: { isValid, dirtyFields, errors } } = methods;
351
+ const hasRuleChanges = Object.keys(dirtyFields).some((key)=>'understandISVRisk' !== key);
351
352
  const { fields: transitionFields, append: appendTransition, remove: removeTransition } = useFieldArray({
352
353
  control,
353
354
  name: 'transitions'
@@ -390,54 +391,26 @@ function BucketLifecycleFormPage() {
390
391
  useEffect(()=>{
391
392
  if (isEditMode && existingRule) {
392
393
  const formValues = ruleToFormValues(existingRule);
393
- reset(formValues);
394
+ reset({
395
+ ...formValues,
396
+ understandISVRisk: !isISVManaged
397
+ });
394
398
  }
395
399
  }, [
396
400
  isEditMode,
397
401
  existingRule,
398
- reset
402
+ reset,
403
+ isISVManaged
399
404
  ]);
400
405
  useEffect(()=>{
401
- methods.setValue('understandISVRisk', !isISVManaged, {
406
+ if (!isEditMode) methods.setValue('understandISVRisk', !isISVManaged, {
402
407
  shouldValidate: true
403
408
  });
404
409
  }, [
410
+ isEditMode,
405
411
  isISVManaged,
406
412
  methods
407
413
  ]);
408
- const prevTransitionsEnabledRef = useRef(null);
409
- const prevNoncurrentTransitionsEnabledRef = useRef(null);
410
- useEffect(()=>{
411
- const prevValue = prevTransitionsEnabledRef.current;
412
- prevTransitionsEnabledRef.current = transitionsEnabled;
413
- if (!isEditMode && null !== prevValue && !prevValue && transitionsEnabled && 0 === transitionFields.length) appendTransition({
414
- timeType: 'days',
415
- days: 30,
416
- date: '',
417
- storageClass: hasCustomSelector ? '' : 'STANDARD_IA'
418
- });
419
- }, [
420
- isEditMode,
421
- transitionsEnabled,
422
- transitionFields.length,
423
- appendTransition,
424
- hasCustomSelector
425
- ]);
426
- useEffect(()=>{
427
- const prevValue = prevNoncurrentTransitionsEnabledRef.current;
428
- prevNoncurrentTransitionsEnabledRef.current = noncurrentTransitionsEnabled;
429
- if (!isEditMode && null !== prevValue && !prevValue && noncurrentTransitionsEnabled && 0 === noncurrentTransitionFields.length) appendNoncurrentTransition({
430
- noncurrentDays: 30,
431
- storageClass: hasCustomSelector ? '' : 'GLACIER',
432
- newerNoncurrentVersions: 0
433
- });
434
- }, [
435
- isEditMode,
436
- noncurrentTransitionsEnabled,
437
- noncurrentTransitionFields.length,
438
- appendNoncurrentTransition,
439
- hasCustomSelector
440
- ]);
441
414
  const prevFilterTypeRef = useRef();
442
415
  useEffect(()=>{
443
416
  const prevFilterType = prevFilterTypeRef.current;
@@ -529,7 +502,7 @@ function BucketLifecycleFormPage() {
529
502
  navigate(`/buckets/${bucketName}?tab=lifecycle`);
530
503
  },
531
504
  onError: (error)=>{
532
- const errorMessage = error instanceof Error ? error.message : `Failed to ${isEditMode ? 'update' : 'create'} lifecycle rule`;
505
+ const errorMessage = error.message;
533
506
  showToast({
534
507
  open: true,
535
508
  message: errorMessage,
@@ -587,7 +560,7 @@ function BucketLifecycleFormPage() {
587
560
  icon: isEditMode ? /*#__PURE__*/ jsx(Icon, {
588
561
  name: "Save"
589
562
  }) : void 0,
590
- disabled: isEditMode ? !isDirty || !isValid || isSaving : !isValid || isSaving
563
+ disabled: isEditMode ? !hasRuleChanges || !isValid || isSaving : !isValid || isSaving
591
564
  })
592
565
  ]
593
566
  }),
@@ -693,7 +666,15 @@ function BucketLifecycleFormPage() {
693
666
  control: control,
694
667
  render: ({ field })=>/*#__PURE__*/ jsx(Toggle, {
695
668
  toggle: field.value,
696
- onChange: field.onChange,
669
+ onChange: (e)=>{
670
+ field.onChange(e);
671
+ if (e.target.checked && 0 === transitionFields.length) appendTransition({
672
+ timeType: 'days',
673
+ days: 30,
674
+ date: '',
675
+ storageClass: hasCustomSelector ? '' : 'STANDARD_IA'
676
+ });
677
+ },
697
678
  label: field.value ? 'Enabled' : 'Disabled'
698
679
  })
699
680
  })
@@ -964,13 +945,20 @@ function BucketLifecycleFormPage() {
964
945
  label: "Transition noncurrent version",
965
946
  id: "noncurrentTransitionsEnabled",
966
947
  direction: "horizontal",
967
- labelHelpTooltip: noncurrentTransitionsHelpText || "Transition noncurrent object versions to a different storage class after a specified number of days",
948
+ labelHelpTooltip: noncurrentTransitionsHelpText || 'Transition noncurrent object versions to a different storage class after a specified number of days',
968
949
  content: /*#__PURE__*/ jsx(Controller, {
969
950
  name: "noncurrentTransitionsEnabled",
970
951
  control: control,
971
952
  render: ({ field })=>/*#__PURE__*/ jsx(Toggle, {
972
953
  toggle: field.value,
973
- onChange: field.onChange,
954
+ onChange: (e)=>{
955
+ field.onChange(e);
956
+ if (e.target.checked && 0 === noncurrentTransitionFields.length) appendNoncurrentTransition({
957
+ noncurrentDays: 30,
958
+ storageClass: hasCustomSelector ? '' : 'GLACIER',
959
+ newerNoncurrentVersions: 0
960
+ });
961
+ },
974
962
  label: field.value ? 'Enabled' : 'Disabled'
975
963
  })
976
964
  })
@@ -77,7 +77,8 @@ function BucketLifecycleList({ bucketName, lifecycleRules, lifecycleStatus, onCr
77
77
  }),
78
78
  cellStyle: {
79
79
  flex: '1',
80
- width: 'unset'
80
+ width: 'unset',
81
+ minWidth: 0
81
82
  }
82
83
  },
83
84
  {
@@ -92,7 +93,8 @@ function BucketLifecycleList({ bucketName, lifecycleRules, lifecycleStatus, onCr
92
93
  },
93
94
  cellStyle: {
94
95
  width: 'unset',
95
- flex: '0.5'
96
+ flex: '0.5',
97
+ minWidth: 0
96
98
  }
97
99
  },
98
100
  {
@@ -165,7 +167,8 @@ function BucketLifecycleList({ bucketName, lifecycleRules, lifecycleStatus, onCr
165
167
  },
166
168
  cellStyle: {
167
169
  width: 'unset',
168
- flex: '1.5'
170
+ flex: '1.5',
171
+ minWidth: 0
169
172
  }
170
173
  },
171
174
  {
@@ -176,7 +179,8 @@ function BucketLifecycleList({ bucketName, lifecycleRules, lifecycleStatus, onCr
176
179
  flex: '0.5',
177
180
  textAlign: 'right',
178
181
  paddingRight: spacing.r16,
179
- width: 'unset'
182
+ width: 'unset',
183
+ minWidth: 0
180
184
  },
181
185
  Cell: ({ value })=>/*#__PURE__*/ jsxs(Box, {
182
186
  display: "flex",
@@ -476,7 +476,7 @@ const PermissionsSection = /*#__PURE__*/ memo(({ onEditPolicy, onEditCors, owner
476
476
  onError: (error)=>{
477
477
  showToast({
478
478
  open: true,
479
- message: error instanceof Error ? error.message : 'Failed to update bucket visibility',
479
+ message: error.message,
480
480
  status: 'error'
481
481
  });
482
482
  }
@@ -94,7 +94,7 @@ const BucketPolicyPage = ()=>{
94
94
  onError: (error)=>{
95
95
  setError('content', {
96
96
  type: 'server',
97
- message: error instanceof Error ? error.message : 'Failed to delete'
97
+ message: error.message
98
98
  });
99
99
  }
100
100
  });
@@ -114,7 +114,7 @@ const BucketPolicyPage = ()=>{
114
114
  onError: (error)=>{
115
115
  setError('content', {
116
116
  type: 'server',
117
- message: error instanceof Error ? error.message : 'Failed to save'
117
+ message: error.message
118
118
  });
119
119
  }
120
120
  });
@@ -153,12 +153,9 @@ const BucketPolicyPage = ()=>{
153
153
  size: "2x",
154
154
  color: "statusWarning"
155
155
  }),
156
- /*#__PURE__*/ jsxs(Text, {
156
+ /*#__PURE__*/ jsx(Text, {
157
157
  variant: "Large",
158
- children: [
159
- "Failed to load bucket policy: ",
160
- policyError.message || 'Unknown error'
161
- ]
158
+ children: policyError.message
162
159
  })
163
160
  ]
164
161
  })
@@ -450,7 +450,8 @@ function BucketReplicationFormPage() {
450
450
  understandISVRisk: true
451
451
  }
452
452
  });
453
- const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, errors } } = methods;
453
+ const { handleSubmit, register, control, watch, reset, formState: { isValid, isDirty, dirtyFields, errors } } = methods;
454
+ const hasRuleChanges = Object.keys(dirtyFields).some((key)=>'understandISVRisk' !== key);
454
455
  const { fields: tagFields, append: appendTag, remove: removeTag } = useFieldArray({
455
456
  control,
456
457
  name: 'tags'
@@ -491,7 +492,10 @@ function BucketReplicationFormPage() {
491
492
  if (isEditMode && existingRule) {
492
493
  if (loadedRuleIdRef.current !== existingRule.ID) {
493
494
  const formValues = ruleToFormValues(existingRule, existingRole);
494
- reset(formValues);
495
+ reset({
496
+ ...formValues,
497
+ understandISVRisk: !isISVManaged
498
+ });
495
499
  loadedRuleIdRef.current = existingRule.ID || null;
496
500
  }
497
501
  } else if (!isEditMode && !isDirty) {
@@ -509,7 +513,8 @@ function BucketReplicationFormPage() {
509
513
  replicationRoleDefault,
510
514
  nextAvailablePriority,
511
515
  isDirty,
512
- reset
516
+ reset,
517
+ isISVManaged
513
518
  ]);
514
519
  const prevFilterTypeRef = useRef();
515
520
  useEffect(()=>{
@@ -564,10 +569,11 @@ function BucketReplicationFormPage() {
564
569
  methods
565
570
  ]);
566
571
  useEffect(()=>{
567
- methods.setValue('understandISVRisk', !isISVManaged, {
572
+ if (!isEditMode) methods.setValue('understandISVRisk', !isISVManaged, {
568
573
  shouldValidate: true
569
574
  });
570
575
  }, [
576
+ isEditMode,
571
577
  isISVManaged,
572
578
  methods
573
579
  ]);
@@ -601,7 +607,7 @@ function BucketReplicationFormPage() {
601
607
  navigate(`/buckets/${bucketName}?tab=replication`);
602
608
  },
603
609
  onError: (error)=>{
604
- const errorMessage = error instanceof Error ? error.message : `Failed to ${isEditMode ? 'update' : 'create'} replication rule`;
610
+ const errorMessage = error.message;
605
611
  showToast({
606
612
  open: true,
607
613
  message: errorMessage,
@@ -660,7 +666,7 @@ function BucketReplicationFormPage() {
660
666
  icon: isEditMode ? /*#__PURE__*/ jsx(Icon, {
661
667
  name: "Save"
662
668
  }) : void 0,
663
- disabled: !isDirty || !isValid || isSaving
669
+ disabled: isEditMode ? !hasRuleChanges || !isValid || isSaving : !isValid || isSaving
664
670
  })
665
671
  ]
666
672
  }),
@@ -19,7 +19,8 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
19
19
  }),
20
20
  cellStyle: {
21
21
  flex: '1',
22
- width: 'unset'
22
+ width: 'unset',
23
+ minWidth: 0
23
24
  }
24
25
  },
25
26
  {
@@ -34,7 +35,8 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
34
35
  },
35
36
  cellStyle: {
36
37
  width: 'unset',
37
- flex: '0.5'
38
+ flex: '0.6',
39
+ minWidth: 0
38
40
  }
39
41
  },
40
42
  {
@@ -46,9 +48,8 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
46
48
  }),
47
49
  cellStyle: {
48
50
  width: 'unset',
49
- flex: '0.5',
50
- textAlign: 'right',
51
- paddingRight: spacing.r16
51
+ flex: '0.8',
52
+ minWidth: 0
52
53
  }
53
54
  },
54
55
  {
@@ -84,7 +85,8 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
84
85
  },
85
86
  cellStyle: {
86
87
  width: 'unset',
87
- flex: '1.5'
88
+ flex: '1.2',
89
+ minWidth: 0
88
90
  }
89
91
  },
90
92
  {
@@ -92,10 +94,11 @@ function BucketReplicationList({ bucketName, replicationRules, replicationRole,
92
94
  accessor: 'ID',
93
95
  id: 'operations',
94
96
  cellStyle: {
95
- flex: '0.5',
97
+ flex: '0.8',
98
+ width: 'unset',
99
+ minWidth: 0,
96
100
  textAlign: 'right',
97
- paddingRight: spacing.r16,
98
- width: 'unset'
101
+ paddingRight: spacing.r16
99
102
  },
100
103
  Cell: ({ value })=>/*#__PURE__*/ jsxs(Box, {
101
104
  display: "flex",
@@ -35,7 +35,7 @@ function BucketVersioning({ tooltipOverlay }) {
35
35
  onError: (error)=>{
36
36
  showToast({
37
37
  open: true,
38
- message: error instanceof Error ? error.message : 'Failed to update bucket versioning',
38
+ message: error.message,
39
39
  status: 'error'
40
40
  });
41
41
  }
@@ -157,7 +157,7 @@ function BucketNotificationFormPage() {
157
157
  navigate(`/buckets/${bucketName}?tab=notification`);
158
158
  },
159
159
  onError: (error)=>{
160
- const errorMessage = error instanceof Error ? error.message : `Failed to ${isEditMode ? 'update' : 'create'} notification rule`;
160
+ const errorMessage = error.message;
161
161
  showToast({
162
162
  open: true,
163
163
  message: errorMessage,
@@ -181,7 +181,7 @@ function BucketNotificationFormPage() {
181
181
  })
182
182
  });
183
183
  if ('error' === notificationStatus && notificationError) {
184
- const errorMessage = notificationError instanceof Error ? notificationError.message : 'Failed to load notification configuration';
184
+ const errorMessage = notificationError.message;
185
185
  return /*#__PURE__*/ jsx(Form, {
186
186
  layout: {
187
187
  kind: 'page',
@@ -45,7 +45,7 @@ function BucketNotificationList({ bucketName, notificationRules, notificationSta
45
45
  });
46
46
  },
47
47
  onError: (error)=>{
48
- const errorMessage = error instanceof Error ? error.message : 'Failed to delete notification rule';
48
+ const errorMessage = error.message;
49
49
  showToast({
50
50
  open: true,
51
51
  message: errorMessage,
@@ -110,7 +110,7 @@ const DeleteObjectButton = ({ objects, bucketName, onDeleteSuccess })=>{
110
110
  } catch (error) {
111
111
  showToast({
112
112
  open: true,
113
- message: error instanceof Error ? error.message : 'Failed to delete objects',
113
+ message: error instanceof Error ? error.message : 'Unknown error',
114
114
  status: 'error'
115
115
  });
116
116
  } finally{